I have a program with variadic templates and a helper function:
#include <iostream>
#include <string>
using std::cout;
template<typename... Ts>
void fooImpl(char const *cp, Ts... args);
template<typename... Ts>
inline void foo(const std::string &s, Ts... args)
{
fooImpl(s.c_str(), args...);
}
void fooImpl(char const *cp)
{
// do something
}
template<typename T, typename... Ts>
void fooImpl(char const *cp, T val, Ts... args)
{
char special{'@'};
while (*cp)
{
if (*cp == special)
{
// handle val ...
// recurse over remaining args
fooImpl(cp, args...);
return;
}
++cp;
}
}
int main()
{
std::string s = "Hello!";
foo("Text", s, "C++", 3.14159, 42);
}
This gives a linker error:
/tmp/ccZpPMC2.o:vt-test.cc:
function void foo<std::string, char const*, double, int>(std::string const&, std::string, char const*, double, int)
: error:
undefined reference to 'void fooImpl<std::string, char const*, double, int>(char const*, std::string, char const*, double, int)
'
It compiles and links fine if I put the definition of foo
down just before main()
.
So I assume it's just a bug, but I get this with GCC 4.9 and clang 3.5, so maybe I'm missing something?
template<typename T, typename... Ts>
void fooImpl(char const *cp, T val, Ts... args) { /* ... */ }
You are declaring an overload (!) of the original template.
Since in the call
fooImpl(s.c_str(), args...);
there is a pack-expansion in the argument list, the unqualified-id denotes a dependent name1. Dependent name resolution applies. [temp.dep.candidate]:
For a function call that depends on a template parameter, the
candidate functions are found using the usual lookup rules (3.4.1,
3.4.2, 3.4.3) except that:
For the part of the lookup using unqualified name lookup (3.4.1) [..] only function declarations from the
template definition context are found.
For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition
context or the template instantiation context are found.
It's safe to say that unqualified name lookup won't find the second overload as it solely considers declarations from the definition context of the template.
ADL does apply here, but the global namespace will not be associated with any of the types in the parameter pack. We have std::string, char const*, double, int
. [basic.lookup.argdep]/2 specifies:
If T
is a fundamental type, its associated sets of namespaces and
classes are both empty.
If T
is a class type (including unions), its associated classes are:
the class itself; the class of which it is a member, if any; and its
direct and indirect base classes. Its associated namespaces are the
namespaces of which its associated classes are members. Furthermore,
if T
is a class template specialization, its associated namespaces and
classes also include: the namespaces and classes associated with the
types of the template arguments provided for template type parameters
(excluding template template parameters); the namespaces of which any
template template arguments are members; and the classes of which any
member templates used as template template arguments are members.
So neither the fundamental types nor std::string
include the global namespace as an associated namespace.
Long story short...
... the global namespace isn't searched in during ADL and the second overload isn't found. Thus the first overload of the function template is the only one found and subsequently chosen by overload resolution. The first overload isn't defined though, hence your linker error is issued.
1) [temp.dep]/1:
In an expression of the form:
postfix-expression (
expression-listopt )
where the postfix-expression is an id-expression, the
id-expression denotes a dependent name if
- any of the expressions in the expression-list is a pack expansion (14.5.3),
- [..]