How can I use std::make_tuple if the execution order of the constructors is important?
For example I guess the execution order of the constructor of class A and the
This answer comes from a comment I made to the template pack question
Since make_tuple deduces the tuple type from the constructed components and function arguments have undefined evaluation ordder, the construction has to happen inside the machinery, which is what I proposed in the comment. In that case, there's no need to use make_tuple; you could construct the tuple directly from the tuple type. But that doesn't order construction either; what I do here is construct each component of the tuple, and then build a tuple of references to the components. The tuple of references can be easily converted to a tuple of the desired type, provided the components are easy to move or copy.
Here's the solution (from the lws link in the comment) slightly modified, and explained a bit. This version only handles tuples whose types are all different, but it's easier to understand; there's another version below which does it correctly. As with the original, the tuple components are all given the same constructor argument, but changing that simply requires adding a ... to the lines indicated with // Note: ...
#include
#include
template struct ConstructTuple {
// For convenience, the resulting tuple type
using type = std::tuple;
// And the tuple of references type
using ref_type = std::tuple;
// Wrap each component in a struct which will be used to construct the component
// and hold its value.
template struct Wrapper {
U value;
template
Wrapper(Arg&& arg)
: value(std::forward(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... {
template Impl(Arg&& arg) // Note ...Arg, ...arg
: Wrapper(std::forward(arg))... {}
};
template ConstructTuple(Arg&& arg) // Note ...Arg, ...arg
: impl(std::forward(arg)), // Note ...
value((static_cast&>(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 struct ConstructFromTupleHelper;
template struct ConstructFromTupleHelper> {
using type = ConstructTuple;
};
template
using ConstructFromTuple = typename ConstructFromTupleHelper::type;
Let's take it for a spin
#include
// 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;
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(33.14159);
// Now show the result
std::cout << greet << std::endl;
return 0;
}
See it in action on liveworkspace. Verify that it constructs in the same order in both clang and gcc (libc++'s tuple implementation holds tuple components in the reverse order to stdlibc++, so it's a reasonable test, I guess.)
To make this work with tuples which might have more than one of the same component, it's necessary to modify Wrapper to be a unique struct for each component. The easiest way to do this is to add a second template parameter, which is a sequential index (both libc++ and libstdc++ do this in their tuple implementations; it's a standard technique). It would be handy to have the "indices" implementation kicking around to do this, but for exposition purposes, I've just done a quick-and-dirty recursion:
#include
#include
template struct Item {
using type = T;
static const int value = I;
};
template struct ConstructTupleI;
template struct ConstructTupleI- ...> {
using type = std::tuple
;
using ref_type = std::tuple;
// I is just to distinguish different wrappers from each other
template struct Wrapper {
U value;
template
Wrapper(Arg&& arg)
: value(std::forward(arg)) {
}
};
struct Impl : Wrapper... {
template Impl(Arg&& arg)
: Wrapper(std::forward(arg))... {}
};
template ConstructTupleI(Arg&& arg)
: impl(std::forward(arg)),
value((static_cast&>(impl)).value...) {
}
operator type() const { return value; }
ref_type operator()() const { return value; }
Impl impl;
ref_type value;
};
template struct List{};
template struct WrapNum;
template struct WrapNum> {
using type = ConstructTupleI;
};
template
struct WrapNum, T, Rest...>
: WrapNum>, Rest...> {
};
// Use WrapNum to make ConstructTupleI from ConstructTuple
template using ConstructTuple = typename WrapNum, T...>::type;
// Finally, a convenience alias in case we want to give `ConstructTuple`
// a tuple type instead of a list of types:
template struct ConstructFromTupleHelper;
template struct ConstructFromTupleHelper> {
using type = ConstructTuple;
};
template
using ConstructFromTuple = typename ConstructFromTupleHelper::type;
With test here.