元组和可变参数模板,这是如何工作的?
Tuple and variadic templates, how does this work?
我看到有人写(关于堆栈溢出本身,询问一些甚至更高级的概念)类似的东西:
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
作为参数构造每个类型。然后留给每个类型的构造函数从流中读取。
另请注意,未指定构造函数的评估顺序,因此您不能期望可变参数列表中的第一个类型会首先从流中读取等等。
代码原样 不 使用内置类型 int
、float
和 char
,因为没有转换从 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 来 return 它。 ...
确保只有在第一个失败时才使用第二个。
无论如何,这打开了一个定制点。在类型 X
的命名空间中我们可以写一个 read_from_stream( std::istream&, X* )
函数,它会自动被调用而不是上面的默认实现。我们还可以编写 read_from_stream( std::istream&, int* )
(等),它可以知道如何从 istream
.
解析整数
这种定制点也可以使用特征 class 来完成,但是用重载来做有很多优点:你可以注入与类型相邻的定制,而不是必须打开一个完全不同的命名空间。自定义操作也更短(没有 class 环绕声)。
我看到有人写(关于堆栈溢出本身,询问一些甚至更高级的概念)类似的东西:
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
作为参数构造每个类型。然后留给每个类型的构造函数从流中读取。
另请注意,未指定构造函数的评估顺序,因此您不能期望可变参数列表中的第一个类型会首先从流中读取等等。
代码原样 不 使用内置类型 int
、float
和 char
,因为没有转换从 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 来 return 它。 ...
确保只有在第一个失败时才使用第二个。
无论如何,这打开了一个定制点。在类型 X
的命名空间中我们可以写一个 read_from_stream( std::istream&, X* )
函数,它会自动被调用而不是上面的默认实现。我们还可以编写 read_from_stream( std::istream&, int* )
(等),它可以知道如何从 istream
.
这种定制点也可以使用特征 class 来完成,但是用重载来做有很多优点:你可以注入与类型相邻的定制,而不是必须打开一个完全不同的命名空间。自定义操作也更短(没有 class 环绕声)。