如果构造函数的执行顺序很重要,我如何使用std::make_tuple?
例如,我猜A类的构造函数和B类的构造函数的执行顺序是未定义的:
std::tuple<A, B> t(std::make_tuple(A(std::cin), B(std::cin)));
我是在读完对这个问题的评论后得出这个结论的。
Translating a std::tuple into a template parameter pack
那就是说这个
template<typename... args>
std::tuple<args...> parse(std::istream &stream) {
return std::make_tuple(args(stream)...);
}
实现具有未定义的构造函数的执行顺序。
提供一些上下文的更新:
为了给我正在尝试做的事情提供更多的背景,这里有一个草图:
我想在二进制解析/序列化的帮助下从标准输入中读入一些序列化的对象。下面是一个如何进行这种解析和序列化的示例:example/cxx/tree/binary/xdr/driver.cxx
xml_schema::istream<XDR> ixdr (xdr);
std::auto_ptr<catalog> copy (new catalog (ixdr));
我希望能够指定序列化对象所具有的类的列表(例如,catalog、catalog、3个序列化对象的someOtherSerializableClass ),并将该信息存储为类型定义
template <typename... Args>
struct variadic_typedef {};
typedef variadic_typedef<catalog, catalog, someOtherSerializableClass> myTypes;
按照Is it possible to “store” a template parameter pack without expanding it?中的建议
并找到一种方法来获得一个std::tuple,以便在解析完成后使用。一个素描:
auto serializedObjects(binaryParse<myTypes>(std::cin));
其中serializedObjects的类型为
std::tuple<catalog, catalog, someOtherSerializableClass>
发布于 2012-12-27 23:28:52
简单的解决方案不是首先使用std::make_tuple(...)
,而是直接构造std::tuple<...>
:调用成员的构造函数的顺序是定义良好的:
template <typename>
std::istream& dummy(std::istream& in) {
return in;
}
template <typename... T>
std::tuple<T...> parse(std::istream& in) {
return std::tuple<T...>(dummy<T>(in)...);
}
函数模板dummy<T>()
仅用于扩展某些内容。该顺序由std::tuple<T...>
中元素的构造顺序决定
template <typename... T>
template <typename... U>
std::tuple<T...>::tuple(U...&& arg)
: members_(std::forward<U>(arg)...) { // NOTE: pseudo code - the real code is
} // somewhat more complex
根据下面的讨论和Xeo的评论,似乎更好的替代方案是使用
template <typename... T>
std::tuple<T...> parse(std::istream& in) {
return std::tuple<T...>{ T(in)... };
}
使用大括号初始化是可行的,因为大括号初始化器列表中参数的求值顺序就是它们出现的顺序。T{...}
的语义在12.6.1 class.explicit.init第2段中进行了描述,声明它遵循列表初始化语义的规则(注意:这与std::initializer_list无关,它只适用于同构类型)。排序约束在8.5.4 dcl.init.list第4段中。
发布于 2012-12-27 23:43:10
正如评论所说,您可以只使用initializer-list:
return std::tuple<args...>{args(stream)...};
这将适用于std::tuple
和诸如此类的(支持初始化器列表)。
但我得到了另一个更通用的解决方案,在不能使用initializer-list的情况下很有用。所以让我们不使用initializer-list来解决这个问题:
template<typename... args>
std::tuple<args...> parse(std::istream &stream) {
return std::make_tuple(args(stream)...);
}
在我解释我的解决方案之前,我想先讨论一下这个问题。事实上,一步一步地思考这个问题也会帮助我们最终找到解决方案。因此,为了简化讨论(和思考过程),让我们假设args
扩展到3种不同的类型,即。X
,Y
,Z
,即args = {X, Y, Z}
,然后我们可以沿着这些思路思考,一步一步地找到解决方案:
首先也是最重要的是,X
、Y
和Z
的构造函数可以以任何顺序执行,因为我们希望X
首先构造Y
和Z
的C++ and并没有指定计算函数参数的顺序。或者至少我们想要模拟这种行为,这意味着X
必须用输入流开头的数据构造(假设数据是xData
),Y
必须用紧跟在xData之后的数据构造,依此类推。
Z
,这似乎也是不可能的。只要我们从输入流中读取数据,这是不可能的,但是我们从一些可索引的数据结构中读取数据,比如std::vector
,那么这是可能的。
std::vector
,然后所有参数都将从这个向量中读取数据。
代码:
//PARSE FUNCTION
template<typename... args>
std::tuple<args...> parse(std::istream &stream)
{
const int N = sizeof...(args);
return tuple_maker<args...>().make(stream, typename genseq<N>::type() );
}
并且tuple_maker
被定义为:
//FRAMEWORK - HELPER ETC
template<int ...>
struct seq {};
template<int M, int ...N>
struct genseq : genseq<M-1,M-1, N...> {};
template<int ...N>
struct genseq<0,N...>
{
typedef seq<N...> type;
};
template<typename...args>
struct tuple_maker
{
template<int ...N>
std::tuple<args...> make(std::istream & stream, const seq<N...> &)
{
return std::make_tuple(args(read_arg<N>(stream))...);
}
std::vector<std::string> m_params;
std::vector<std::unique_ptr<std::stringstream>> m_streams;
template<int Index>
std::stringstream & read_arg(std::istream & stream)
{
if ( m_params.empty() )
{
std::string line;
while ( std::getline(stream, line) ) //read all at once!
{
m_params.push_back(line);
}
}
auto pstream = new std::stringstream(m_params.at(Index));
m_streams.push_back(std::unique_ptr<std::stringstream>(pstream));
return *pstream;
}
};
测试代码
///TEST CODE
template<int N>
struct A
{
std::string data;
A(std::istream & stream)
{
stream >> data;
}
friend std::ostream& operator << (std::ostream & out, A<N> const & a)
{
return out << "A" << N << "::data = " << a.data ;
}
};
//three distinct classes!
typedef A<1> A1;
typedef A<2> A2;
typedef A<3> A3;
int main()
{
std::stringstream ss("A1\nA2\nA3\n");
auto tuple = parse<A1,A2,A3>(ss);
std::cout << std::get<0>(tuple) << std::endl;
std::cout << std::get<1>(tuple) << std::endl;
std::cout << std::get<2>(tuple) << std::endl;
}
输出:
A1::data = A1
A2::data = A2
A3::data = A3
这是意料之中的。看看你自己的demo at ideone。:-)
请注意,此解决方案通过读取第一次调用read_arg
本身中的所有行,以及所有后续调用使用索引从std::vector
读取,从而避免了从流中读取顺序的问题。
现在,您可以将一些printf放入类的构造函数中,只是为了查看构造的顺序与parse
函数模板的模板参数的顺序相同,这很有趣。此外,这里使用的技术对于不能使用列表初始化的地方也很有用。
发布于 2012-12-27 22:27:05
make_tuple
在这里没有什么特别之处。C++中的任何函数调用都允许以未指定的顺序调用其参数(允许编译器自由优化)。
我真的不建议使用有副作用的构造函数,因为顺序很重要(这将是维护的噩梦),但是如果你绝对需要这样做,你总是可以显式地构造对象来设置你想要的顺序:
A a(std::cin);
std::tuple<A, B> t(std::make_tuple(a, B(std::cin)));
https://stackoverflow.com/questions/14056000
复制相似问题