C++ 如何避免在使用std::make_tuple时构造函数的未定义执行顺序
如果构造函数的执行顺序很重要,如何使用std::make_tuple 例如,我猜A类构造函数和B类构造函数的执行顺序是未定义的:C++ 如何避免在使用std::make_tuple时构造函数的未定义执行顺序,c++,c++11,tuples,variadic-templates,C++,C++11,Tuples,Variadic Templates,如果构造函数的执行顺序很重要,如何使用std::make_tuple 例如,我猜A类构造函数和B类构造函数的执行顺序是未定义的: std::tuple 就是说这个 模板 std::tuple二进制解析/序列化。以下是如何完成此类解析和序列化的示例: xml_模式::istream ixdr(xdr); std::自动ptr副本(新目录(ixdr)); 我希望能够指定序列化对象拥有的类的列表(例如,catalog、catalog、someOtherSerializableClass,用于3个序列
std::tuple
就是说这个
模板
std::tuple二进制解析/序列化。以下是如何完成此类解析和序列化的示例:
xml_模式::istream ixdr(xdr);
std::自动ptr副本(新目录(ixdr));
我希望能够指定序列化对象拥有的类的列表(例如,catalog、catalog、someOtherSerializableClass,用于3个序列化对象),并将该信息存储为typedef
模板
结构变量_typedef{};
typedef可变_typedef myTypes;
如中所述
并找到一种在解析完成后使用std::tuple的方法。草图:
自动序列化对象(二进制解析(std::cin));
其中SerializedObject的类型为
std::tuple
这里的make\u tuple
没有什么特别之处。C++中的任何函数调用允许其参数以未指定的顺序调用(允许编译器自由优化)。
我真的不建议使用具有副作用的构造函数,使顺序变得重要(这将是维护的噩梦),但如果您确实需要这样做,您可以始终显式地构造对象以设置所需的顺序:
A a(std::cin);
std::tuple<A, B> t(std::make_tuple(a, B(std::cin)));
aa(标准::cin);
std::tuple t(std::make_tuple(a,B(std::cin));
简单的解决方案不是首先使用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)...);
}
模板
std::istream和dummy(std::istream和in){
返回;
}
模板
std::tuple解析(std::istream&in){
返回std::tuple(dummy(in)…);
}
函数模板dummy()。顺序是由std::tuple
中元素的构造顺序强加的:
模板
模板
标准::元组::元组(U.&&arg)
:members_(std::forward(arg)…。{//注意:伪代码-实际代码为
}//有点复杂
在下面的讨论和Xeo的评论之后,似乎更好的选择是使用
template <typename... T>
std::tuple<T...> parse(std::istream& in) {
return std::tuple<T...>{ T(in)... };
}
模板
std::tuple解析(std::istream&in){
返回std::tuple{T(in)…..};
}
大括号初始化的使用之所以有效,是因为大括号初始值设定项列表中参数的求值顺序是它们出现的顺序。12.6.1[class.explicit.init]第2段描述了T{…}
的语义,说明它遵循列表初始化语义的规则(注意:这与仅适用于同质类型的std::initializer_list无关)。排序约束在8.5.4[dcl.init.list]第4段中。如注释所述,您可以使用初始值设定项列表:
return std::tuple<args...>{args(stream)...};
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 >代码>的构造可以按任意顺序执行,因为函数参数的顺序被C++标准未指定。
- 但是我们希望先构造
X
,然后是Y
,和Z
。或者至少我们想要模拟这种行为,这意味着X
必须用输入流开头的数据构造(比如说数据是xData
),而Y
必须用紧跟在xData之后的数据构造,依此类推
- 正如我们所知,X不能保证首先构造,所以我们需要假装。基本上,我们将从流读取数据,就像它在流的开头一样,即使先构造
Z
,这似乎是不可能的。只要我们从输入流读取数据,这是不可能的,但是我们从一些可索引的数据结构(如std::vector
)读取数据,那么这是可能的
- 所以我的解决方案是这样的:它将首先填充一个
std::vector
,然后所有参数都将从这个向量读取数据
- 我的解决方案假设流中的每一行都包含构造任何类型的对象所需的所有数据
代码:
测试代码
这是意料之中的。看看你自己。:-)
请注意,此解决方案通过读取对read_arg
本身的第一次调用中的所有行,以及使用索引从std::vector
读取的所有后续调用,避免了从流中读取的顺序
现在,您可以在类的构造函数中放置一些printf,只是为了看到构造顺序与解析函数模板的模板参数顺序不同,这很有趣。此外,这里使用的技术对于无法使用列表初始化的地方也很有用。我认为手动展开定义的唯一方法。像下面这样的方法可能会奏效。不过,我欢迎尝试让它变得更好
#include <iostream>
#include <tuple>
struct A { A(std::istream& is) {}};
struct B { B(std::istream& is) {}};
template <typename... Ts>
class Parser
{ };
template <typename T>
class Parser<T>
{
public:
static std::tuple<T> parse(std::istream& is) {return std::make_tuple(T(is)); }
};
template <typename T, typename... Ts>
class Parser<T, Ts...>
{
public:
static std::tuple<T,Ts...> parse(std::istream& is)
{
A t(is);
return std::tuple_cat(std::tuple<T>(std::move(t)),
Parser<Ts...>::parse(is));
}
};
int main()
{
Parser<A,B>::parse(std::cin);
return 1;
}
#包括
#包括
结构A{A(std::istream&is){};
结构B{B(std::istream&is){};
模板
类解析器
{ };
模板
类解析器
{
公众:
//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() );
}
//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
#include <iostream>
#include <tuple>
struct A { A(std::istream& is) {}};
struct B { B(std::istream& is) {}};
template <typename... Ts>
class Parser
{ };
template <typename T>
class Parser<T>
{
public:
static std::tuple<T> parse(std::istream& is) {return std::make_tuple(T(is)); }
};
template <typename T, typename... Ts>
class Parser<T, Ts...>
{
public:
static std::tuple<T,Ts...> parse(std::istream& is)
{
A t(is);
return std::tuple_cat(std::tuple<T>(std::move(t)),
Parser<Ts...>::parse(is));
}
};
int main()
{
Parser<A,B>::parse(std::cin);
return 1;
}
#include <tuple>
#include <type_traits>
template<typename...T> struct ConstructTuple {
// For convenience, the resulting tuple type
using type = std::tuple<T...>;
// And the tuple of references type
using ref_type = std::tuple<T&...>;
// Wrap each component in a struct which will be used to construct the component
// and hold its value.
template<typename U> struct Wrapper {
U value;
template<typename Arg>
Wrapper(Arg&& arg)
: value(std::forward<Arg>(arg)) {
}
};
// The implementation class derives from all of the Wrappers.
// C++ guarantees that base classes are constructed in order, and
// Wrappers are listed in the specified order because parameter packs don't
// reorder.
struct Impl : Wrapper<T>... {
template<typename Arg> Impl(Arg&& arg) // Note ...Arg, ...arg
: Wrapper<T>(std::forward<Arg>(arg))... {}
};
template<typename Arg> ConstructTuple(Arg&& arg) // Note ...Arg, ...arg
: impl(std::forward<Arg>(arg)), // Note ...
value((static_cast<Wrapper<T>&>(impl)).value...) {
}
operator type() const { return value; }
ref_type operator()() const { return value; }
Impl impl;
ref_type value;
};
// Finally, a convenience alias in case we want to give `ConstructTuple`
// a tuple type instead of a list of types:
template<typename Tuple> struct ConstructFromTupleHelper;
template<typename...T> struct ConstructFromTupleHelper<std::tuple<T...>> {
using type = ConstructTuple<T...>;
};
template<typename Tuple>
using ConstructFromTuple = typename ConstructFromTupleHelper<Tuple>::type;
#include <iostream>
// Three classes with constructors
struct Hello { char n; Hello(decltype(n) n) : n(n) { std::cout << "Hello, "; }; };
struct World { double n; World(decltype(n) n) : n(n) { std::cout << "world"; }; };
struct Bang { int n; Bang(decltype(n) n) : n(n) { std::cout << "!\n"; }; };
std::ostream& operator<<(std::ostream& out, const Hello& g) { return out << g.n; }
std::ostream& operator<<(std::ostream& out, const World& g) { return out << g.n; }
std::ostream& operator<<(std::ostream& out, const Bang& g) { return out << g.n; }
using std::get;
using Greeting = std::tuple<Hello, World, Bang>;
std::ostream& operator<<(std::ostream& out, const Greeting &n) {
return out << get<0>(n) << ' ' << get<1>(n) << ' ' << get<2>(n);
}
int main() {
// Constructors run in order
Greeting greet = ConstructFromTuple<Greeting>(33.14159);
// Now show the result
std::cout << greet << std::endl;
return 0;
}
#include <tuple>
#include <type_traits>
template<typename T, int I> struct Item {
using type = T;
static const int value = I;
};
template<typename...TI> struct ConstructTupleI;
template<typename...T, int...I> struct ConstructTupleI<Item<T, I>...> {
using type = std::tuple<T...>;
using ref_type = std::tuple<T&...>;
// I is just to distinguish different wrappers from each other
template<typename U, int J> struct Wrapper {
U value;
template<typename Arg>
Wrapper(Arg&& arg)
: value(std::forward<Arg>(arg)) {
}
};
struct Impl : Wrapper<T, I>... {
template<typename Arg> Impl(Arg&& arg)
: Wrapper<T, I>(std::forward<Arg>(arg))... {}
};
template<typename Arg> ConstructTupleI(Arg&& arg)
: impl(std::forward<Arg>(arg)),
value((static_cast<Wrapper<T, I>&>(impl)).value...) {
}
operator type() const { return value; }
ref_type operator()() const { return value; }
Impl impl;
ref_type value;
};
template<typename...T> struct List{};
template<typename L, typename...T> struct WrapNum;
template<typename...TI> struct WrapNum<List<TI...>> {
using type = ConstructTupleI<TI...>;
};
template<typename...TI, typename T, typename...Rest>
struct WrapNum<List<TI...>, T, Rest...>
: WrapNum<List<TI..., Item<T, sizeof...(TI)>>, Rest...> {
};
// Use WrapNum to make ConstructTupleI from ConstructTuple
template<typename...T> using ConstructTuple = typename WrapNum<List<>, T...>::type;
// Finally, a convenience alias in case we want to give `ConstructTuple`
// a tuple type instead of a list of types:
template<typename Tuple> struct ConstructFromTupleHelper;
template<typename...T> struct ConstructFromTupleHelper<std::tuple<T...>> {
using type = ConstructTuple<T...>;
};
template<typename Tuple>
using ConstructFromTuple = typename ConstructFromTupleHelper<Tuple>::type;