Search code examples
c++templatesc++17variadic-templatesvariadic-functions

Accept all combinatorial possibilities for a set of object types as parameter to function in C++


Given three different empty structs, A, B and C, I want to have a function foo accept any combination of any number of these three parameters, e.g.:

struct A {};
struct B {};
struct C {};

// Foo has a required parameter that must be the first one given. Anything else is optional.

foo(1);
foo(1, A{});
foo(1, B{});
foo(1, B{}, A{});
foo(1, A{}, C{}, B{});

I thought variadic templates and function overloading would help in this situation, so here's what I've tried:

struct A {};
struct B {};
struct C {};

template <typename... ExtraParams>
void foo(int x, ExtraParams&&...)
{
  std::cout << "x = " << x;
}

template <typename... ExtraParams>
void foo(int x, A&&, ExtraParams&&... extra)
{
  foo(x, extra...);
  std::cout << " with A";
}

template <typename... ExtraParams>
void foo(int x, B&&, ExtraParams&&... extra)
{
  foo(x, extra...);
  std::cout << " with B";
}

// same for C

However, when calling f(2, A{}, B{}), only x = 2 with A gets printed. I think I undestand why this does not work, but I am not quite sure how I should actually be dealing with this situation.

EDIT The code I was testing against used rvalue reference for the type considered known, e.g.:

template <typename... ExtraParams>
void foo(int x, A&&, ExtraParams&&... extra)
{
  foo(x, extra...);
  std::cout << " with A";
}

and this will generate the exact behavior I mentioned (I don't know why though).


Solution

  • The problem is when you pass the parameters extra to other overloads of foo as foo(x, extra...);, they're lvalues and can't be bound to the rvalue-references like A&& and B&&.

    (emphasis mine)

    The following expressions are lvalue expressions:

    • the name of a variable, a function, a template parameter object (since C++20), or a data member, regardless of type, such as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;

    You should use std::forward to forward the parameters (as rvalues if the original arguments passed are rvalues, i.e. to take advantage of forwarding reference). e.g.

    foo(x, std::forward<ExtraParams>(extra)...);
    

    LIVE