There are two unresolved issues regarding friend function templates defined in class templates: 1545 and 2174 . The first question is to what extent it is valid, and the second is about odr violations that may arise on the basis of actual instances of these function templates. Iโm not sure which compiler is right (I previously believed that both were wrong), but the standard may not sufficiently or sufficiently indicate what the correct behavior is in this situation.
The code should ideally compile (pending the resolution of the problem):
template<typename ...Args> class obj { bool p = false; template<typename T, typename... Args2> friend T get(const obj<Args2...> &o) { return op; } }; template<typename T, typename... Args> T get(const obj<Args...> &o);
The friend declaration first declares get , so this creates a new member of the innermost enclosing namespace: ::get . An external declaration simply overrides the same function, so in fact there is only ::get . From [temp.over.link]:
Two expressions containing template parameters are considered equivalent if two function definitions containing expressions would satisfy the rule of one definition (3.2), except that the tokens used to indicate template parameters may differ if the token used to denote a template parameter in one expression, replaced by another token that names the same template parameter in another expression. To determine whether two dependent names (14.6.2) are equivalent, only the name itself is considered, and not the result of the name search in the context of the template.
Using different template parameter names ( Args... vs Args2... ) is excellent - this second declaration of the ::get function template is valid and allows you to search for it.
This leads us to:
bool test(const obj<int, float, double> &a) { #ifdef UNQUAL return get<int>(a); // unqualified #else return ::get<int>(a); // qualified #endif }
Both of these should work - since we redefined the function in the namespace, we donโt even need to worry about ADL. Both calls should find ::get<int>(obj<int, float, double> ) , which is friend , so the code should compile and link. gcc allows an unqualified call, but not a qualified call, clang does not allow any.
We could get around both CWG issues completely by simply not defining a function template inside the class:
template<typename ...Args> class obj { bool p = false; template<typename T, typename... Args2> friend T get(const obj<Args2...> &o); }; template<typename T, typename... Args> T get(const obj<Args...> &o) { return op; }
In this statement, both compilers allow both qualified and unskilled get calls.
If we rewrite the code so that obj a class, not a class template, and everything else is equal:
class obj { bool p = false; template <class T> friend T get(const obj& o) { return op; } }; template <class T> T get(const obj& ); bool test(const obj& a) { #ifdef UNQUAL return get<int>(a); #else return ::get<int>(a); #endif }
Both compilers allow both calls. But there is no difference that I know in the rules between obj being an ordinary class and a class template.