元组和可变参数模板,这是如何工作的

Tuple and variadic templates, how does this work?

本文关键字:何工作 工作 变参 参数 元组      更新时间:2023-10-16

我见过人们写(关于堆栈溢出本身,询问一些甚至高级概念)如下的内容:

template<typename... args>
std::tuple<args...> parse(istream stream) 
{
    return std::make_tuple(args(stream)...);
}

并将其用作

auto tup = parse<int, float, char>(stream);

上面的代码如何通过解析流来构造元组?对于如何将数据放入流中是否有任何具体要求?

为此,

必须从 std::istream 隐式转换为指定为模板参数的所有类型。

struct A {
    A(std::istream&) {} // A can be constructed from 'std::istream'.
};
struct B {
    B(std::istream&) {} // B can be constructed from 'std::istream'.
};
int main() {
    std::istringstream stream{"t1 t2"};
    auto tup = parse<A, B>(stream);
}

它通过扩展类型的可变参数列表来工作,并使用提供的std::istream作为参数构造每个类型。然后留给每种类型的构造函数从流中读取。

另请注意,构造函数的求值顺序未指定,因此您不能期望可变参数列表中的第一种类型首先从流中读取等。

代码本身不适用于内置类型,intfloatchar,因为没有从std::istream到任何这些类型的转换。

它工作得很差。 它依赖于具有采用std::istream的构造函数的目标类型。

由于许多类型没有这个,你不能把它添加到类似的东西int,这是一个糟糕的计划。

而是这样做:

template<class T>
auto read_from_stream( std::istream& stream, T* unused_type_tag )
-> typename std::decay<decltype( T{stream} )>::type
{
  return {stream};
}
template<class T>
auto read_from_stream( std::istream& stream, T* unused_type_tag, ... )
-> typename std::decay<decltype( T(stream) )>::type
{
  return T(stream);
}
template<typename... args>
std::tuple<args...> parse(std::istream& stream)  {
  return std::tuple<args...>{
    read_from_stream(stream, (args*)nullptr)...
  };
}

现在我们不是直接构造参数,而是调用 read_from_stream

read_from_stream上面有两个重载。 第一个尝试从istream直接和隐式地构造我们的对象。 第二个从istream显式构造我们的对象,然后使用 RVO 返回它。 ...确保仅在第一个失败时使用第二个。

无论如何,这打开了一个定制点。 在类型X的命名空间中,我们可以编写一个read_from_stream( std::istream&, X* )函数,它将被自动调用,而不是上面的默认实现。 我们还可以编写read_from_stream( std::istream&, int* )(等),它可以知道如何从istream解析整数。

这种自定义点也可以使用 traits 类来完成,但使用重载执行此操作具有许多优点:您可以注入与类型相邻的自定义项,而不必打开完全不同的命名空间。 自定义操作也更短(没有类包装噪音)。