Template Functions Template Template and Namespace

In the following C ++ code example, GCC 6 and Clang 3.8 disagree on what the correct behavior is:

This contrived example "works" - as in the test() function returns op in GCC. In clang, it calls the function (undefined) get<int, int, float, double> :

 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); bool test(const obj<int, float, double> &a) { return get<int>(a); } 

Putting the same code in the namespace forces GCC to do the same as clang.

 namespace ns { 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); } bool test(const ns::obj<int, float, double> &a) { return ns::get<int>(a); } 

https://godbolt.org/g/sWrXQO and https://godbolt.org/g/9tIXwe

Which compiler is "correct" and in general there is a way to determine the function of a member template of a member without declaring it, and then defining it separately. That is, things like:

 struct Foo { friend bool bar() { return true; } // declares *and* defines a free function bar template<typename T> T bar2() { return true; } // doesn't work! }; 
+8
c ++ friend templates argument-dependent-lookup
source share
1 answer

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.

+5
source share

All Articles