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

Brace initialize tuple in function argument


The main problem in question is that I want to write a function that takes two variable-length sets of arguments.

The abstraction I decided to go for is to emulate the following call syntax:

f({a,b,c},x,y);

If a, b, and c all have the same type, this can be made to work with

template <typename X, typename... A>
void f(std::initializer_list<X> xs, A&&... as) { . . . }

But a similar function definition with std::tuple instead of the std::initializer_list does not allow for my desired syntax.

template <typename... X, typename... A>
void f(const std::tuple<X...>& xs, A&&... as) { . . . }

Is there some trick I can use to allow the types in the first group to be heterogeneous?

Addendum: In general, I do not apriori know the size of X....

Addendum 2: Does anyone know the technical reason why a braced initializer works for std::tuple t{a,b,c}, but not in the template argument context? Is it because in my example xs is not expanded by X...?


Solution

  • No, it is currently impossible with the syntax you want. The initializer list can only be deduced to an array or a std::initializer_list, both of homogeneous types.

    This is a special rule. An initializer list has no type and simply cannot be deduced to anything else. If the function parameter is not an array or a std::initializer_list, then the parameter for which the initializer list was given becomes a non-deduced context and template argument deduction will ignore it.

    In particular class template argument deduction cannot be done in a function parameter to determine the type from a braced initializer list as it can be done in a variable definition since C++17. Such a feature was not added to the language.

    In principle you could allow a finite number of different types by using e.g. an array of std::variant as parameter, but that would be resolved to the type only at runtime and is unlikely to be what you want.

    You need to either add a type name or function call to the initializer list braces as in

    f(std::forward_as_tuple(a, b, c), x, y)
    

    or you can use a tag type instead to indicate the end of the first pack in a flat argument list:

    f(a, b, c, tag, x, y)
    

    The tag can then be detected with some template work.