Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to forward multiple constructor arguments through a variadic template to an array initializer list?

Tags:

c++

c++17

I am trying to pass multiple initializer lists through a variadic template to construct an std::array in place.

However I fail to find a version that compiles (C++17).

Consider the following code:

struct TwoInts {
  TwoInts(int x, int y) 
    : a{x}
    , b{y}
  {}
  int a,b;
};

template<typename T, int S>
struct Holder {
  template<typename... Args>
  Holder(Args&&... args) : m({{std::forward<Args>(args)...}}) {}

  //Deleted for clarity of the example:
  Holder(const Holder& o) = delete;
  Holder(Holder&& o) = delete;
  std::array<T,S> m;
};

We can perform the following initializations:

TwoInts a = {1,2};
std::array<TwoInts,1> b{{{1,2}}};
std::array<TwoInts,2> c{{{1,2},{3,4}}};

Holder<int,1> d{1};
Holder<int,2> e{1,2};

But how to modify the example to make this work?

Holder<TwoInts,1> f{{{1,2}}};
Holder<TwoInts,2> f{{{1,2},{3,4}}};

Gcc and clang fail find a matching constructor.

like image 891
RedundantEntry Avatar asked Oct 11 '19 09:10

RedundantEntry


1 Answers

You can be explicit about the argument types instead of implicitly passing std::initializer_lists:

Holder<TwoInts,1> f{TwoInts{1, 2}};
Holder<TwoInts,2> e{TwoInts{1,2}, TwoInts{3,4}};

This is due to std::initializer_lists that produce a so-called "non-deduced" context, so you cannot use std::initializer_lists to pass them to a template constructor (as the language doesn't allow you to explicitly nail down the template types of a constructor template).

Note that in std::array<TwoInts,1> b{{{1,2}}}; you don't use std::initializer_list, but instead perform aggregate initialization, and in Holder<int,1> d{1};, you just pass an int, which works fine with deduction.


If TwoInt objects aren't move/copy constructible, you could turn them into an aggregate;

struct TwoInts {
   TwoInts(const TwoInts&) = delete;
   TwoInts(TwoInts&&) = delete;

   int a,b;
};

and then use aggregate initialization throughout. The holder constructor should look like this;

template<typename... Args>
   Holder(Args&&... args) : m{{std::forward<Args>(args)...}} {}
   //                        ^ no parentheses  ... neither ^

and you can initialize with

Holder<TwoInts,1> f{1, 2};
Holder<TwoInts,2> e{1,2, 3,4};
like image 177
lubgr Avatar answered Oct 07 '22 14:10

lubgr