Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to initialize all tuple elements by the same arguments?

Tags:

c++

c++11

tuples

Is it possible to initialize all elements of std::tuple by the same argument, using the non-default constructors of the underlying types?

template <typename... TElements>
struct Container {
    // I'd wish to be able to do something like this:
    Container(Foo foo, Bar bar)
        : tuple(foo, bar)
    {}
    std::tuple<TElements...> tuple;
};

The point is that I don't know the tuple size (it's templated by a variadic parameter), so I can't duplicate the arguments as many times as I need. The only thing I know is that all types in TElements have a constructor taking Foo and Bar as arguments and don't have a default constructor.

like image 616
lizarisk Avatar asked Mar 21 '14 13:03

lizarisk


3 Answers

The clearest way is just to construct each element in the tuple constructor argument list:

template <typename... TElements>
struct Container {
    Container(Foo foo, Bar bar)
        : tuple(TElements{foo, bar}...)
    {}
    std::tuple<TElements...> tuple;
};

This will result in move (or copy) constructing each element of the tuple from its corresponding constructor parameter; if this is unacceptable you could use piecewise construction:

template <typename... TElements>
struct Container {
    Container(Foo foo, Bar bar)
        : tuple(std::piecewise_construct, (sizeof(TElements), std::tie(foo, bar))...)
    {}
    std::tuple<TElements...> tuple;
};

Unfortunately in this case we have to do some kind of gymnastics (here sizeof and a comma operator) to get the variadic list TElements mentioned and ignored.

like image 77
ecatmur Avatar answered Oct 20 '22 10:10

ecatmur


with double parameter pack expansion you can (try to) construct each element of a given tuple class with all given parameters to a function:

template <class T> struct tuple_construct_t;

template <class... Ts> struct tuple_construct_t<std::tuple<Ts...>> {
  template <class... Args>
  static std::tuple<Ts...> make_tuple(Args&&... args) {
    //this is the central part - the double pack expansion
    return std::make_tuple(Ts{args...}...);
  }
};

// a little free helper function...
template <class Tup, class... Args>
Tup construct_tuple(Args&&... args) {
    return tuple_construct_t<Tup>::make_tuple(std::forward<Args>(args)...);
}

And then somewhere in the code:

typedef std::tuple<NoDefault1, NoDefault2> myTuple;
auto t = construct_tuple<myTuple>(Foo{}, Bar{});

full working example: Link

Edit:

Since @Rakvan deleted his answer, I'll preserve the second (correct) part of it:

template <class ... Ts, class ... Args>
std::tuple<Ts...> cartesian_make_tuple(Args && ... args)
{
    return std::make_tuple(Ts{args...}...);
}

here is a working exaple

like image 33
Arne Mertz Avatar answered Oct 20 '22 12:10

Arne Mertz


We want to do variadic expansion (to get just the right amount of parameters), but we have to put a ‘hint’ to tie the expansion to whichever pack it is we want to match:

template<typename Dummy, typename Value>
Value depends(Value&& value)
{ return std::forward<Value>(value); }

template<typename... Elements>
void example()
{
    // naive attempt to construct all the elements from 0:
    // std::tuple<Elements...> t { 0... };

    // now expansion is tied to the Elements pack
    std::tuple<Elements...> t { depends<Elements>(0)... };

    // with two arguments:
    std::tuple<Elements...> t { { depends<Elements>(0), depends<Elements>(1) }... };
}
like image 26
Luc Danton Avatar answered Oct 20 '22 12:10

Luc Danton