I have the following code:
#include <iostream>
template <typename... Args>
void f(int a, int b, Args... args) {
std::cout << b << '\n';
f(a, args...);
}
void f(int, int b) {
std::cout << b << '\n';
}
int main() {
f(1, 2);
//f(1, 2, 3);
}
While f(1, 2)
compiles, f(1, 2, 3)
does not. From the error message produced by the compiler, I see that f<>
is being instantiated somehow. Within the instantiation, the call f(a)
is made and thus the error. What makes the compiler not to use f(int, int)
but try to instantiate f<>(int, int)
during the process of parsing the call f(1, 2, 3)
?
Within the variadic function template f()
, the f
in the recursive call is a dependent name due to [temp.dep], emphasis mine:
In an expression of the form:
postfix-expression ( expression-listopt)
where the postfix-expression is an unqualified-id, the unqualified-id denotes a dependent name if
(1.1) — any of the expressions in the expression-list is a pack expansion (14.5.3),
And, according to [temp.dep.res], emphasis mine:
In resolving dependent names, names from the following sources are considered:
(1.1) — Declarations that are visible at the point of definition of the template.
(1.2) — Declarations from namespaces associated with the types of the function arguments both from the instantiation context (14.6.4.1) and from the definition context.
There is only one declaration of f
that is visible at the point of definition of template <typename... Args> void f(int, int, Args...)
and that is itself. The second point doesn't apply here because all of your arguments are int
s and there's no associated namespaces for fundamental types. Since that function template cannot be called with a single argument, you get a compile error.
The solution is to restructure your code so that your base case is visible at the point of definition, that is:
// this can be just the declaration
void f(int, int ) { /* ... */ }
template <typename... Args>
void f(int a, int b, Args... args)
{
std::cout << b << '\n';
f(a, args...); // now this will call f(int, int)
// if sizeof...(Args) == 1
}
An example in which (1.2) applies would be the following:
#include <iostream>
template <typename A, typename... Args>
void f(A a, int b, Args... args) {
std::cout << b << '\n';
f(a, args...);
}
template <typename A>
void f(A a, int b) {
std::cout << b << '\n';
}
struct bar {};
int main() {
//f(1,2,3); // still doesn't compile, same reasoning
f(bar{}, 2, 3); // OK. bar is in the global namespace, so declarations
// from the global namespace in both instantiation
// and definition context are considered, which includes
// the second `f`.
}