how to avoid undefined execution order for the constructors when using std::make_tuple

前端 未结 5 1502
攒了一身酷
攒了一身酷 2020-12-01 12:33

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

5条回答
  •  暗喜
    暗喜 (楼主)
    2020-12-01 13:11

    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.

提交回复
热议问题