There are two unresolved issues regarding friend
function templates defined in class templates: 1545 and 2174. The former questions the extent to which it's valid at all and the latter is about odr violations that may arise based on the actual instantiations of those function templates. I am unsure which compiler is right (having previously believed that both were wrong), but it may simply be under- or poorly specified in the standard what the correct behavior is in this situation.
The code should ideally compile (pending issue resolution):
template<typename ...Args>
class obj {
bool p = false;
template<typename T, typename... Args2>
friend T get(const obj<Args2...> &o) { return o.p; }
};
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
. The external declaration just redeclares the same function, so there really is just the one ::get
. From [temp.over.link]:
Two expressions involving template parameters are considered equivalent if two function definitions containing
the expressions would satisfy the one-definition rule (3.2), except that the tokens used to name the
template parameters may differ as long as a token used to name a template parameter in one expression is
replaced by another token that names the same template parameter in the other expression. For determining
whether two dependent names (14.6.2) are equivalent, only the name itself is considered, not the result of name lookup in the context of the template.
Using different template parameter names (Args...
vs Args2...
) is fine - this second declaration of the function template ::get
is valid and allows for lookup to find it.
This brings 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 redeclared the function to be in namespace scope, we don't even have to worry about ADL. Both calls should find ::get<int>(obj<int, float, double> )
, which is a friend
, and so the code should compile and link. gcc allows the unqualified call but not the qualified call, clang allows neither.
We could sidestep both CWG issues entirely by simply not defining the 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 o.p; }
With this formulation, both compiles allow both qualified and unqualified invocation of get
.
If we rewrite the code such that obj
is a class and not a class template, but all else being equal:
class obj {
bool p = false;
template <class T>
friend T get(const obj& o) { return o.p; }
};
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 invocations. But there is no difference that I'm aware of in the rules between obj
being a normal class and a class template.
template<typename T, typename... Args> T get(const obj<Args...> &o);
outside class ? – NecessitateT
is non deducible, so you have to provide<int>
, so ADL doesn't works (as there are no templateget
in global scope) :/ You may declare a dummy template get (with not correct matching) to re-enable ADL: Demo. – Necessitateget()
in namespace scope - once unqualified lookup finds some function template, we should be able to find other function templates. I think. – Digenesisobj
specializations: godbolt.org/g/RqYn9d – Yakutskforce_adl
solution with differentobj
specializations: – Sunnysunproof