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
I believe the only way to manually unroll the definition. Something like the following might work. I welcome attempts to make it nicer though.
#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;
}
The trivial solution is not to use std::make_tuple(...)
in the first place but to construct a std::tuple<...>
directly: The order in which constructors for the members are called is well defined:
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)...);
}
The function template dummy<T>()
is only used to have something to expand on. The order is imposed by construction order of the elements in the 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
Following the discussion below and Xeo's comment it seems that a better alternative is to use
template <typename... T>
std::tuple<T...> parse(std::istream& in) {
return std::tuple<T...>{ T(in)... };
}
The use of brace initialization works because the order of evaluation of the arguments in a brace initializer list is the order in which they appear. The semantics of T{...}
are described in 12.6.1 [class.explicit.init] paragraph 2 stating that it follows the rules of list initialization semantics (note: this has nothing to do with std::initializer_list which only works with homogenous types). The ordering constraint is in 8.5.4 [dcl.init.list] paragraph 4.
As the comment says, you could just use initializer-list:
return std::tuple<args...>{args(stream)...};
which will work for std::tuple
and suchlikes (which supports initializer-list).
But I got another solution which is more generic, and can be useful where initializer-list cannot be used. So lets solve this without using initializer-list:
template<typename... args>
std::tuple<args...> parse(std::istream &stream) {
return std::make_tuple(args(stream)...);
}
Before I explain my solution, I would like to discuss the problem first. In fact, thinking about the problem step by step would also help us to come up with a solution eventually. So, to simply the discussion (and thinking-process), lets assume that args
expands to 3 distinct types viz. X
, Y
, Z
, i.e args = {X, Y, Z}
and then we can think along these lines, reaching towards the solution step-by-step:
First and foremost, the constructors of X
, Y
, and Z
can be executed in any order, because the order in which function arguments are evaluated is unspecified by the C++ Standard.
But we want X
to construct first, then Y
, and Z
. Or at least we want to simulate that behavior, which means X
must be constructed with data that is in the beginning of the input stream (say that data is xData
) and Y
must be constructed with data that comes immediately after xData, and so on.
As we know, X is not guaranteed to be constructed first, so we need to pretend. Basically, we will read the data from the stream as if it is in the beginning of the stream, even if Z
is constructed first, that seems impossible. It is impossible as long as we read from the input stream, but we read data from some indexable data structure such as std::vector
, then it is possible.
So my solution does this: it will populate a std::vector
first, and then all arguments will read data from this vector.
My solution assumes that each line in the stream contains all the data needed to construct an object of any type.
Code:
//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() );
}
And tuple_maker
is defined as:
//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;
}
Output:
A1::data = A1
A2::data = A2
A3::data = A3
which is expected. See demo at ideone yourself. :-)
Note that this solution avoids the order-of-reading-from-the-stream problem by reading all the lines in the first call to read_arg
itself, and all the later calls just read from the std::vector
, using the index.
Now you can put some printf in the constructor of the classes, just to see that the order of construction is not same as the order of template arguments to the parse
function template, which is interesting. Also, the technique used here can be useful for places where list-initialization cannot be used.
There's nothing special about make_tuple
here. Any function call in C++ allows its arguments to be called in an unspecified order (allowing the compiler freedom to optimize).
I really don't suggest having constructors that have side-effects such that the order is important (this will be a maintenance nightmare), but if you absolutely need this, you can always construct the objects explicitly to set the order you want:
A a(std::cin);
std::tuple<A, B> t(std::make_tuple(a, B(std::cin)));
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 <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;
Let's take it for a spin
#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;
}
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 <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;
With test here.