20

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 constructor of class B is undefined for:

std::tuple<A, B> t(std::make_tuple(A(std::cin), B(std::cin)));

I came to that conclusion after reading a comment to the question

Translating a std::tuple into a template parameter pack

that says that this

template<typename... args>
std::tuple<args...> parse(std::istream &stream) {
  return std::make_tuple(args(stream)...);
}

implementation has an undefined execution order of the constructors.

Update, providing some context:

To give some more background to what I am trying to do, here is a sketch:

I want to read in some serialized objects from stdin with the help of CodeSynthesis XSD binary parsing/serializing. Here is an example of how such parsing and serialization is done: example/cxx/tree/binary/xdr/driver.cxx

xml_schema::istream<XDR> ixdr (xdr); 
std::auto_ptr<catalog> copy (new catalog (ixdr));

I want to be able to specify a list of the classes that the serialized objects have (e.g. catalog, catalog, someOtherSerializableClass for 3 serialized objects) and store that information as a typedef

template <typename... Args>
struct variadic_typedef {};

typedef variadic_typedef<catalog, catalog, someOtherSerializableClass> myTypes;

as suggested in Is it possible to “store” a template parameter pack without expanding it?

and find a way to get a std::tuple to work with after the parsing has finished. A sketch:

auto serializedObjects(binaryParse<myTypes>(std::cin));

where serializedObjects would have the type

std::tuple<catalog, catalog, someOtherSerializableClass>
Community
  • 1
  • 1
Erik Sjölund
  • 10,690
  • 7
  • 46
  • 74
  • 3
    `tuple` has nothing to do with it. Instead of `make_pair` you can call e.g. `some_function (A(std::cin), B(std::cin))`. `A` and `B` will be called before `some_function` that is all you can rely upon. –  Dec 27 '12 at 14:19
  • 1
    +1 good question. I had faced the same problem when I was trying to improve @Xeo's solution [here](http://stackoverflow.com/questions/8476975/call-function-with-parameters-extracted-from-string) which basically has the same problem. Xeo's solution works for the input in his answer, it doesn't work if the reading from stream depends on the order. I fixed that though. I'm just thinking of how would my solution fit in here. Once I get some idea, I would post my answer. – Nawaz Dec 27 '12 at 14:26
  • @aleguna: Thanks for clarifying. I guess you are referring to the same thing as user Nawaz wrote in his answer: "order in which function arguments are evaluated is unspecified by the C++ Standard". – Erik Sjölund Dec 30 '12 at 08:46

5 Answers5

10

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.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • How does it solve the problem? It just pushes the reading-order-issue to the tuple and its arguments. – Nawaz Dec 27 '12 at 15:44
  • This only works if the `T` constructors are not explicit, correct? – jogojapan Dec 27 '12 at 15:46
  • @jogojapan: I don't think it will not work even then, since `T` is basically variadic args, and which means all the constructors can still be executed in any order. – Nawaz Dec 27 '12 at 15:53
  • @Nawaz: The stream is passed to the constructor of each member of the `std::tuple` using the constructor deducing argument types. Thus, the order of construction is imposed by the construction order of the `std::tuple<...>` elements. – Dietmar Kühl Dec 27 '12 at 15:53
  • @jogojapan: No, the constructors of the `T...` types can be `explicit`. However, the library shipping with gcc doesn't get this right. – Dietmar Kühl Dec 27 '12 at 15:54
  • @DietmarKühl I haven't tried it with GCC. I just thought that initializing a member of type `Ti` using an argument of type `std::istream &` would require explicit conversion if the constructor for `Ti` is explicit. Perhaps I'm wrong. (It's a great answer anway, +1) – jogojapan Dec 27 '12 at 15:56
  • @DietmarKühl: I think one of us didn't get the problem correctly (and I think it is you :P, unless you prove me wrong ). Just say `T...` is `{A,B,C}`. You are eventually passing the stream to the constructors of `A`, `B` and `C`, and you're assuming that `A` will be constructed first, so it will read the data from the stream, then `B` will be constructed, and so on? Is this order well-defined? – Nawaz Dec 27 '12 at 15:57
  • @Nawaz: I'm pretty sure that I'm correct. The constructor is the same as in `struct T { A a; B b; C c; T(std::istream& in1, std::istream& in2, std::istream& in3): a(in1), b(in2), c(in3) {}` (slip in a `std::forward()` and universal references to get the actual semantics). The references `in1`, `in2`, and `in3` are all identical and it doesn't matter in which order they get evaluated. The members are constructed in the order `a`, `b`, and then `c`. – Dietmar Kühl Dec 27 '12 at 16:00
  • @jogojapan: I understand that very much. That is not an issue. Issue is with the construction of `A`, `B`, and `C`. – Nawaz Dec 27 '12 at 16:00
  • 1
    Worth mentioning that this only works because the constructor of `std::tuple` that is declared as `tuple(const T&...)` isn't chosen because that would require a conversion, while the perfect forwarding ctor would not require one. +1, very nice :) – Johannes Schaub - litb Dec 27 '12 at 16:02
  • 2
    @jogojapan: I just noticed that the constructor actually does require that the elements are implicitly constructible form the parameter type: It says to in 20.4.2.1 [tuple.cnstr] paragraph 9 (although I'm never sure whether `Remark:`s are normative. – Dietmar Kühl Dec 27 '12 at 16:07
  • 2
    @Nawaz I too find no remark on whether the element subobjects appear in the order of `Types..`. Hmm. That the elements need to be implicitly convertible makes sense to me, because otherwise the perfect forwarding ctor would allow more initialization cases than the `tuple(const Types& ...)` constructor, which because it is a function call too only allows implicit conversions. I'm not sure anymore whether this always works as advertised. – Johannes Schaub - litb Dec 27 '12 at 16:11
  • 3
    Why rely on some uncertain overload resolution to get the right constructor? Just use an initializer-list and you get left-to-right evaluation. – Xeo Dec 27 '12 at 16:11
  • @Nawaz: Indeed, I don't find a restriction that the elements are constructed in order (nor a restriction that for `std::pair` that the member `first` is constructed prior to `second`). It seems, Xeo's solution is the way to go. – Dietmar Kühl Dec 27 '12 at 16:16
  • @DietmarKühl: Xeo's solution works in this case only. I was thinking of a more generic solution. – Nawaz Dec 27 '12 at 16:21
  • @Xeo, how does one use an initializer list here, when the tuple is non homogeneos? Do you mean uniform initialization (in which case, that does not change the situation?), or something I am missing? EDIT: nevermind I see this was clarified w/ a comment in the answer. – mmocny Dec 27 '12 at 16:40
  • 4
    @mmocny: I should have said "list-initialization", which is the correct term for uniform initialization. So, yes, I meant: "Use `std::tuple{T(args)...}`. And it changes the situation. As Johannes correctly said in a comment on Nawaz' answer, list-init is specified to *always* evaluate left-to-right. – Xeo Dec 27 '12 at 16:42
  • @Xeo I did not know that list-initialization is specified to do that. Sweet! – mmocny Dec 27 '12 at 16:43
  • 2
    @mmocny we shall distinguish between an initializer-list, an initialier list, and an `std::initializer_list`. See http://meta.stackexchange.com/questions/75798/unfortunate-auto-detection-of-synonyms-for-initializer-list (too easy to confuse them!). – Johannes Schaub - litb Dec 27 '12 at 16:44
  • @JohannesSchaub-litb Thanks for the link to the meta, which clarifies the language construct (initializer list) from the library type ([std::]initializer_list). However, I am still curious to know which naming conventions to use to differentiate between when brace initialization ends up a call to a constructor with initializer_list vs just regular constructor argument list. In the case of the former, calling those brace initialization arguments an initializer list seems natural. In the case of the latter, I find that confusing (though perhaps it is still the technically correct term). – mmocny Dec 27 '12 at 17:34
  • Why is that `return std::tuple(dummy(in)...);` in parse template not `return std::tuple(T(in)...);` , I don't get why we need `dummy` there? . Thanks for explanation) – M3taSpl0it Dec 28 '12 at 09:31
  • @M3taSpl0it: Both will have the nearly same effect, but the `dummy` one will fail if `T` isn't *implicitly* constructible from `std::istream&`. – Xeo Dec 28 '12 at 10:21
  • @M3taSpl0it: The fact that it only works with implicit constructors is more a problem than the goal! The goal was to have the construction of the members impose the desired order, i.e., the order wouldn't be determined by the evaluation order in the function call. As it turns out, there is no guarantee on the order of elements in the `std::tuple<...>` either. I think it was a nice idea but still has too many problems while the use of the brace-init-list shouldn't have either of these problems (but it seems to be incorrectly implemented for the current gcc, at least on MacOS). – Dietmar Kühl Dec 28 '12 at 12:56
  • @DietmarKühl Your answer states "The order in which constructors for the members are called is well defined" however based on your last comment you are rescinding that? Could you update your answer? (This issue came up again [here](http://stackoverflow.com/questions/32192809/tuple-isnt-being-constructed-in-order#32192809)) – M.M Aug 24 '15 at 23:34
3

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

///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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    That seems overly complicated. Why not simply say `return std::tuple{args(stream)...};` – Johannes Schaub - litb Dec 27 '12 at 16:18
  • @JohannesSchaub-litb: I didn't know `std::tuple` provides that. But the technique in my solution is more generic, and would solve problems where initializer-list is absent. – Nawaz Dec 27 '12 at 16:24
  • 2
    @JohannesSchaub-litb: Isn't `std::tuple{ T(in)... }` equivalent to `std::tuple( T(in)... )`, i.e., it still has the original problem, just that the function being called isn't `std::make_pair()` but the constructor of `std::tuple`? – Dietmar Kühl Dec 27 '12 at 16:30
  • @JohannesSchaub-litb: Honestly, I didn't get it : `std::tuple{args(stream)...};`. How does it work? It still has reading-order-problem? – Nawaz Dec 27 '12 at 16:35
  • 2
    @DietmarKühl the important one is that it uses the brace enclosed initializer list syntax. It is explicitly noted that such a list is strictly evaluated left to right, even if it ends up being the argument list of a constructor (see 8.5.4p4) – Johannes Schaub - litb Dec 27 '12 at 16:39
  • @JohannesSchaub-litb: Isn't that true for only types that support `std::initializer_list`? `std::tuple` doens't support it. – Nawaz Dec 27 '12 at 16:40
  • 2
    @Nawaz It's required for any list-initialization (§8.5.4/4). It says there, explicitly: _This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call._ – jogojapan Dec 27 '12 at 16:42
  • @jogojapan: Oh that is great! – Nawaz Dec 27 '12 at 16:43
  • @Nawaz Yes, indeed. I hadn't realized it myself until Xeo mentioned it. – jogojapan Dec 27 '12 at 16:44
  • 2
    @Nawaz: Followning Johannes's comment, I chased through the standard: According to 12.6.1 [class.explicit.init] paragraph 2 the semantics of `T{...}` follow 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. – Dietmar Kühl Dec 27 '12 at 16:45
  • @DietmarKühl: That is awesome. Learned something new today. – Nawaz Dec 27 '12 at 16:46
  • 2
    "(which supports initializer-list)" -- every type supports list-initialization, even if it ends up just calling a constructor, which is what happens with `std::tuple`. – Xeo Dec 27 '12 at 16:46
  • @Xeo: Yes, I just realized that. Thanks. – Nawaz Dec 27 '12 at 16:48
  • 2
    So we can enforce an order of argument evaluation for constructors using `{...}` instead of `(...)`. Clearly, we need to propose calling functions using *brace-argument-lists* which have a defined order of evaluation of their arguments ;-) – Dietmar Kühl Dec 27 '12 at 16:53
  • So this is all amazing and fascinating, but the naming conventions are starting to confuse me. Is it really acceptable to name this type of construction (using brace initialization syntax that calls a constructor forwarding to arguments) as construction using an "initializer-list"? I personally reserved that word for construction that specifically uses initializer_list, and would have just called this uniform initialization, or I see now the term list-initialization (which is a bit to close to initializer_list for my taste). – mmocny Dec 27 '12 at 16:55
  • @DietmarKühl: Some vague idea is coming to my mind as to how to simulate `function{....}` using `std::tuple`. – Nawaz Dec 27 '12 at 16:59
  • 1
    @DietmarKühl hah, yes agreed, now we need {} function call syntax. Or perhaps just use a "function-apply" helper which accepts a function and tuple argument pack. (a-la javascript's Function.prototype.apply) – mmocny Dec 27 '12 at 17:01
  • @Nawaz: Well, I *know* how to call a function object with its argument being passed as a `std::tuple<...>`. Unfortunately, it requires that the function object is being passed as argument, making it a bit hard to use with overloaded functions. – Dietmar Kühl Dec 27 '12 at 17:02
  • @mmocny: I was thinking exactly along that line : `function-apply` with function and arguments packed in a tuple. – Nawaz Dec 27 '12 at 17:03
  • I am wondering where did `call_make_pair` came out of nowhere in your code? (typo making call to below `call` fn?) . Also can you provide sample/live working code example?, since your example is hard to understand with sample input etc. Thanks – M3taSpl0it Dec 28 '12 at 09:39
  • @M3taSpl0it: Ohh, it was not complete. Actually I had copied some code from my solution to similar problem I had faced before, but forgot to edit it. Anyway, I edited it now. Thanks. – Nawaz Dec 28 '12 at 10:13
  • @M3taSpl0it: I edited it again. Now it is a working code. See the demo here : http://ideone.com/zb1dxp – Nawaz Dec 28 '12 at 10:57
  • @Nawaz: Maybe your solution is reading more than necessary from the input stream? Could the while loop stop when enough lines have been read? In other words when the number of lines is equal to sizeof...(args). Maybe I'm being nitpicky – Erik Sjölund Dec 30 '12 at 09:17
  • @ErikSjölund: That is a good improvement, indeed. I hope the reader will read your comment too. – Nawaz Dec 30 '12 at 09:18
2

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)));
Mark B
  • 95,107
  • 10
  • 109
  • 188
  • 2
    The OP cannot do that. He/she is working with variadic arguments. And he/she seems to already know that it doesn't have well-defined order of evaluation; he/she just couldn't express it the way the spec (or you) did. – Nawaz Dec 27 '12 at 14:41
0

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.

Community
  • 1
  • 1
rici
  • 234,347
  • 28
  • 237
  • 341
-1

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;
}
Peter Ogden
  • 710
  • 5
  • 10