Consider the following example:
struct S {
template<typename T = void>
friend void foo(S) {
}
};
int main() {
S s;
foo(s); // (1)
foo<void>(s); // (2)
}
My GCC 9.2.0 fails to compile (2)
with the following error:
a.cpp: In function 'int main()':
a.cpp:10:5: error: 'foo' was not declared in this scope
10 | foo<void>(s);
| ^~~
a.cpp:10:9: error: expected primary-expression before 'void'
10 | foo<void>(s);
| ^~~~
However, (1)
works fine. Why is this? How do I call foo
with explicit template arguments?
friend
function definitions in a class body do not make the friend
function visible in the enclosing namespace scope to usual unqualified name lookup (although they are placed into this namespace scope).
In order to make it visible, you need to add a declaration for the template in the namespace scope (it doesn't matter whether this happens before or after the definition):
struct S {
template<typename T = void>
friend void foo(S) {
}
};
template<typename T>
void foo(S);
int main() {
S s;
foo(s); // (1)
foo<void>(s); // (2)
}
Now the question is why foo(s)
works. This is because of argument-dependent lookup. In a function call without nested name specifier the classes and enclosing namespaces of the types of a call's argument (and others) are also searched for matching function overloads. For argument-dependent lookup friend
s declared only in the class body are visible. In this way a matching function for the call foo(s)
is found.
foo<void>(s)
should work the same way, because the name is unqualified and s
is of type S
, so ADL should again find the friend foo
inside S
.
However, there is another issue to consider. When the compiler reads foo
it must decide whether foo
can be a template or not, because it changes the parsing of <
following foo
.
To decide this, unqualified name lookup is done on foo
. Before C++20, foo
would be considered a template name only if this lookup finds a template of some kind by that name. But unqualified name lookup doesn't find anything in your case, because the only foo
is not visible to ordinary unqualified name lookup. Therefore foo
will not be considered a template and foo<void>
will not be parsed as a template-id.
In C++20, the rule was changed and if unqualified name lookup finds either an ordinary function by that name or nothing at all, then foo<void>
will be considered a template-id as well. In that case the following ADL for the call will find foo
and the call will succeed.
So the code will work as is in C++20 and pre-C++20 you actually only need to declare any template by the name foo
to get foo<void>(s)
to find the friended foo
by ADL. E.g.:
struct S {
template <typename T = void>
friend void foo(S) {}
};
template<int>
void foo();
int main() {
S s;
foo(s); // (1)
foo<void>(s); // (2)
}