JoshG79 JoshG79 - 9 months ago 51
C++ Question

How to overload variadic templates when they're not the last argument

Basically the problem can be summed up with this example:

template <typename ...Us>
void foo(Us...) { std::cout << "A\n"; }

template <typename ...Us>
void foo(Us..., int) { std::cout << "B\n"; }

int main(){

This calls the first
). How can I get it to call the second

If this used a non-variadic template, or if the "int" was the first argument, then the overload rules would call the right function. That is, a specific type (
) is a better match than a template so it would call the second
. But apparently that's not the case with variadic templates? Is there any way to overload a variadic template when it's not the last argument?

Answer Source

When a parameter pack doesn't appear last in the parameter declaration, it is a non-deduced context. A non-deduced context means that the template arguments have to be given explicitly. This is why foo #1 is a better overload. You can force the second overload call by providing explicit arguments (foo<int,int>(1,2,3)) or as you said, move the int to the front.

To make things clear, you can overload a function with variadic templates, but when they do not appear as the last argument, they cannot be deduced, which automatically disqualifies them as candidates when explicit arguments are not provided. When they are provided, the template parameters are replaced with their provided types and the resulting non-template function is a candidate in overload resolution.

To answer your question, you can put all arguments into a tuple and pick out the last and test that one. Then pass on an overload based on a simple is_same check:

void foo_impl(true_type,Us...); // last argument is int
void foo_impl(false_type,Us...); // last argument non-int

template <typename ...Us>
void foo( Us&& ) {
  auto pack=forward_as_tuple(forward<Us>(us)...);
  using last=decltype(get<sizeof...(Us)-1>(move(pack)));
  foo_impl(is_same<decay_t<last>,int>{}, forward<Us>(us)...);