Must a template friend operator overload precede a member function overload of the same operator?

54 views Asked by At

Does anybody know why this compiles:

template<typename T>
class Foo;

template<typename T>
bool operator==(const T& l, const Foo<T>& r); 

template<typename T>
class Foo 
{
public:
    Foo();
    ~Foo();

    friend bool operator== <> (const T& l, const Foo<T>& r); 

    bool operator==(const Foo<T>& r);
};

But this does not (only difference is order of member vs friend operator==):

template<typename T>
class Foo;

template<typename T>
bool operator==(const T& l, const Foo<T>& r); 

template<typename T>
class Foo 
{
public:
    Foo();
    ~Foo();

    bool operator==(const Foo<T>& r);

    friend bool operator== <> (const T& l, const Foo<T>& r); 
};

I was banging my head against the second case, with an "error: declaration of ‘operator==’ as non-function" at the friend line. I finally decided there must be something wrong before the friend declaration, so I tried moving it up in the code to see if I could get a different error. Lo and behold, when I moved it above the member declaration, everything worked!

3

There are 3 answers

2
Artyer On BEST ANSWER

It's because after declaring a member operator== in the scope of the class, the name operator== will refer to that instead of the global template.

I'm not sure that's the correct behaviour (might be a compiler bug, may also fall foul of https://stackoverflow.com/a/15538759/5754656), but what's happening is that operator== is parsed as a non-template, so the next < is a less than sign rather than part of a template-id.

Qualifying it so that the non-member operator== is found instead fixes this:

template<typename T>
class Foo 
{
public:
    Foo();
    ~Foo();

    bool operator==(const Foo<T>& r);

    //friend bool operator== <> (const T& l, const Foo<T>& r);
    friend bool ::operator== <> (const T& l, const Foo<T>& r);
};
1
Some programmer dude On

The declaration

friend bool operator== <> (const T& l, const Foo<T>& r);

is not for the non-member function you declared above the class.

It looks almost like a specialization for the non-member operator function, but isn't.

The friend declaration should look like

template<typename U>
friend bool operator==(const U& l, const Foo<U>& r); 

In other words, basically exactly the same as the previous declaration, with added friend and different template name (to not clash with the class template name).

With this you don't need the forward declarations of Foo or the non-member operator== function.

Also, with this proper declaration the order should not matter.

0
Brian Bi On

Let's deal with the second example first—the one that compilers reject.

For the attempted friend declaration

friend bool operator== <> (const T& l, const Foo<T>& r); 

the meaning is governed by [dcl.meaning.general]/2. Bullet 2.2 applies:

If the id-expression E in the declarator-id of the declarator is a qualified-id or a template-id:

  • If the friend declaration is not a template declaration, then in the lookup for the terminal name of E:
    • if the unqualified-id in E is a template-id, all function declarations are discarded;
    • otherwise, [...]
    • each remaining function template is replaced with the specialization chosen by deduction from the friend declaration ([temp.deduct.decl]) or discarded if deduction fails.
  • The declarator shall correspond to one or more declarations found by the lookup; they shall all have the same target scope, and the target scope of the declarator is that scope.

Since E is a template-id, it is looked up. Since we are in a class scope, the lookup finds the previously declared member operator== and stops (it doesn't go to the enclosing namespace scope). And [dcl.meaning.general]/2.2.1.1 says that all function declarations are discarded (because E can only be referring to a specialization of a function template, not an ordinary function). Consequently, the result of the lookup is an empty set, and subbullet 2.3 is violated; the declarator doesn't correspond to anything found by the lookup, because the lookup didn't find anything.

When you have the two declarations the other way around, namely, the member operator== second and the friend declaration first, you violate [class.member.lookup]/6 because if the friend declaration were to be reinterpreted in a context where the member operator== declaration is already visible, then the program would be ill-formed as previously explained. In this case the program is IFNDR.

(Artyer has already explained how to fix the issue—in both cases—namely, by using a qualified name in the friend declaration.)