简体   繁体   English

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

[英]Tuple and variadic templates, how does this work?

I have seen people write (on stack overflow itself, asking some even advanced concepts) something along the lines of: 我见过人们写(在堆栈溢出本身,询问一些甚至高级概念)的东西:

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

and use it as 并用它作为

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

How does the above code construct the tuple by parsing the stream? 上面的代码如何通过解析流来构造元组? Is there any specific requirement on how data is to be put into the stream? 是否有关于如何将数据放入流中的具体要求?

For this to work there must be an implicit conversion from std::istream to all the types specified as template arguments. 为此,必须有一个从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);
}

It works by expanding the variadic list of types and constructs each type with the supplied std::istream as argument. 它的工作原理是扩展可变参数类型列表,并使用提供的std::istream作为参数构造每个类型。 It is then left to the constructor of each type to read from the stream. 然后将其留给每种类型的构造函数以从流中读取。

Also be aware that the evaluation order of the constructors is not specified so you can't expect that the first type in the variadic list will read from the stream first etc. 另请注意,未指定构造函数的求值顺序,因此您不能指望可变参数列表中的第一个类型将首先从流中读取。

The code as it is does not work with built in types as int , float and char as there is no conversion from std::istream to any of those types. 代码本身不适用于intfloatchar内置类型,因为没有从std::istream到任何这些类型的转换。

It works poorly. 它的效果很差。 It relies on the target type having a constructor that takes an std::istream . 它依赖于具有带std::istream的构造函数的目标类型。

As many types don't have this, and you cannot add it to something like int , this is a bad plan. 由于许多类型没有这个,并且你不能将它添加到像int这样的东西,这是一个糟糕的计划。

Instead do this: 而是这样做:

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)...
  };
}

now instead of directly constructing the arguments, we call read_from_stream . 现在我们不是直接构造参数,而是调用read_from_stream

read_from_stream has two overloads above. read_from_stream上面有两个重载。 The first tries to directly and implicitly construct our object from an istream . 第一个尝试直接和隐式地从istream构造我们的对象。 The second explicitly constructs our object from an istream , then uses RVO to return it. 第二个显式从istream构造我们的对象,然后使用RVO返回它。 The ... ensures that the 2nd one is only used if the 1st one fails. ...确保第二个仅在第一个失败时使用。

In any case, this opens up a point of customization. 无论如何,这开辟了一个定制点。 In the namespace of a type X we can write a read_from_stream( std::istream&, X* ) function, and it will automatically be called instead of the default implementation above. 在类型X的命名空间中,我们可以编写一个read_from_stream( std::istream&, X* )函数,它将自动被调用,而不是上面的默认实现。 We can also write read_from_stream( std::istream&, int* ) (etc) which can know how to parse integers from an istream . 我们还可以编写read_from_stream( std::istream&, int* ) (等),它可以知道如何解析istream整数。

This kind of point of customization can also be done using a traits class, but doing it with overloads has a number of advantages: you can inject the customizations adjacent to the type, instead of having to open a completely different namespace. 这种自定义点也可以使用traits类来完成,但是使用重载执行它有许多优点:您可以将自定义注入到类型旁边,而不必打开完全不同的命名空间。 The custom action is also shorter (no class wrapping noise). 自定义操作也更短(没有类包装噪音)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM