Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

template object's template friend functions and namespaces

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

This contrived example "works" -- as in the test() function returns o.p in GCC. In clang, it calls the (undefined) function 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 o.p; }
};

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 a namespace causes GCC to do the same thing clang does.

namespace ns {

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);

}

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 is there in general a way to define a friend member template function inline without having to declare it and then define 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!
};
like image 396
Matt Godbolt Avatar asked Apr 19 '16 15:04

Matt Godbolt


1 Answers

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.

like image 191
Barry Avatar answered Sep 30 '22 13:09

Barry