template object's template friend functions and namespaces
Asked Answered
R

1

8

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!
};
Reorientation answered 19/4, 2016 at 15:11 Comment(8)
Why declaring template<typename T, typename... Args> T get(const obj<Args...> &o); outside class ?Necessitate
Because it doesn't work if you don't... try it: godbolt.org/g/peSscUReorientation
Yeah, T is non deducible, so you have to provide <int>, so ADL doesn't works (as there are no template get in global scope) :/ You may declare a dummy template get (with not correct matching) to re-enable ADL: Demo.Necessitate
@Necessitate ADL should still work due to the redeclaration of get() in namespace scope - once unqualified lookup finds some function template, we should be able to find other function templates. I think.Digenesis
@Barry: but the 2 functions would be ambiguous...Necessitate
@Necessitate There's only one function.Digenesis
There's no point in defining the function template inside the class template. You'll easily get ODR violations when you instantiate different obj specializations: godbolt.org/g/RqYn9dYakutsk
@Yakutsk interestingly, only gcc, but not clang, gives ODR violation using my force_adl solution with different obj specializations:Sunnysunproof
D
5

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.

Digenesis answered 19/4, 2016 at 17:17 Comment(5)
Thanks for the comprehensive reply. I appreciate the workaround (of defining the get externally) - and this is what I'm already doing in my code. However the disparity between the non-templatized function being able to be defined inline (and commonly done with e.g. friend operator<<), and yet as soon as the function is itself templatized, having to separate its declaration and definition...is just plain confusing. But...it's C++ :)Reorientation
@MattG The external template requirement can best be explained in this comment. Also, I am not 100% sure that my reasoning is right about the compilers. Hoping somebody else steps in here at some point.Digenesis
CWG2174 and CWG1545 might be relevant.Yakutsk
@Yakutsk Interesting that for 1545, gcc complains about redefinition but clang accepts it. I don't entirely understand 2174 or the proposed resolution.Digenesis
@Sunnysunproof Why? I showed an example which compiles on both gcc/clang without any forward declarations.Digenesis

© 2022 - 2024 — McMap. All rights reserved.