Kobi
Kobi

Reputation: 878

Variadic class template and variadic constructor

On C++ weekly ep. 48 by Jason Turner there is the following code:

template<typename ... B>
struct Merged : B...
{
  template <typename ... T>
  Merged(T && ... t) : B(std::forward<T>(t))...
  {  }

  using B::operator()...;
};

It fails with the following on gcc 7.1:

error: mismatched argument pack lengths while expanding 'B'

What is the proper way to Expand B?

(BTW, in the above link, the code seems to compile with some 7.0 snapshot).

Edit1:

As noted by @Jarod42, it will compile with Structs acting as the functors. The actual video use lambda and it seems to break there.

auto l1 = [] { return 4 ; };
auto l2 = [](const int i) { return i * 10; };
// This would work, S1, S2 are just functors structs
Merged<S1, S2> merged1(42, "hello");
// This fails
Merged merged2 = Merged(l1, l2);

Edit2:

Seems like User-defined deduction guides does not work here.

template <typename ... T>
Merged(T...) -> Merged<std::decay_t<T>...>;

The above should have enable the following:

Merged merged(l1, l2);

But it does not. It seems like you have to pass the types to Merged<>

Merged<t1, t2> merged(l1, l2);

which probably not really what the tutorial wanted to demonstrate.

Upvotes: 2

Views: 241

Answers (3)

stiar
stiar

Reputation: 368

If the sizes of the parameter packs should be equal, we can enforce this with std::enable_if:

template<typename ... B>
struct Merged : B...
{
  template <typename ... T,
            typename = typename std::enable_if_t<sizeof...(T) == sizeof...(B)>>
  Merged(T && ... t) : B(std::forward<T>(t))...
  {  }

  using B::operator()...;
};

This together with the user-defined deduction guide seems to work.

Upvotes: 2

Kobi
Kobi

Reputation: 878

Seems like there is no need to have any constructor defined here, and instead, just using C++17 aggregate:

template<typename ... B>
struct Merged : B...
{
//  This is not needed, it would actually render this class
//  to be a non-aggregate one.
//  template <typename ... T>
//  Merged(T && ... t) : B(std::forward<T>(t))...
//  {  }

    using B::operator()...;
};

// C++17 class deduction guidance (user defined)
template <typename ... T>
Merged(T...) -> Merged<T...>;

int main()
{
    auto l1 = [] { return 4 ; };
    auto l2 = [](const int i) { return i * 10; };
    // Note here, using {} for initializing an aggregate
    Merged merged{l1, l2};
}

Note the C++17 class deduction user defined guidance, plus using {} for initializing an aggregate.

Constructor is not needed. If we had it, this class would not be an aggregate anymore and the syntax used for initializing, would stop working.

Upvotes: 2

Jarod42
Jarod42

Reputation: 217065

With

template <typename ... T>
Merged(T&& ... t) : B(std::forward<T>(t))...
{}

sizeof...(T) should be equal to sizeof...(B): You have to provide one argument by base.

And then it works.

If you don't provide same number of argument, you hace error similar to:

error: mismatched argument pack lengths while expanding 'B'

Upvotes: 1

Related Questions