Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which is the best viable operator == function between these two declarations?

#include <iostream>
template<typename T>
struct A{};
struct Y{
    template<typename T>
    bool operator==(A<T>){
        std::cout<<"#1\n";
        return true;
    }
};
template<typename T>
bool operator==(T,Y){
    std::cout<<"#2\n";
    return true;
}
int main(){ 
    A<int> a;
    Y y;
    a==y;
}

For the above snippet, GCC prints #2 while Clang prints #1. The result is here. I think Clang is right, since #2 is a non-rewritten non-member candidate for the expression a==y. Instead, the synthesized candidate for #1 is also a viable function. That is, the overload set would consist of two candidates, which look like:

#1'
bool operator==(A<int>,Y); [with T = int] // synthesized candidate for #1

#2'
bool operator==(A<int>,Y);[with T = A<int>]

According to temp.func.order#3

If exactly one of the function templates was considered by overload resolution via a rewritten candidate ([over.match.oper]) with a reversed order of parameters, then the order of the function parameters in its transformed template is reversed.

The P/A pairs are the following:

transformed type for #1: (A<uniqueT1>, Y) as A
original type for #2: (T, Y)  as P

transformed type for #2: (UniqueT2, Y) as A
original type for #1: (Y, A<UniqueT1>) as P

For the above candidates, the template function's partial ordering is sufficient to determine which is the best viable. which is bullet over.match.best#2.5

for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,

2.5 F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in [temp.func.order], or, if not that,
[2.6 - 2.7]
2.8 F2 is a rewritten candidate ([over.match.oper]) and F1 is not

That means that it's not necessary to step into bullet 2.8. According to the above P/A pairs, #1 is at least as specialized as #2 but #2 is not at least as specialized as #1; Hence, #1' is the best viable candidate in partial ordering. So, Clang should be correct here.

However, consider the following variant snippet

#include <iostream>
template<class T>
struct A{};

class Y{};

template<class T>
bool operator==(Y,A<T>){
    std::cout<<"#1\n";
    return true;
}

template<class T>
bool operator ==(T,Y){
    std::cout<<"#2\n";
    return true;
}

int main(){
   A<int> a;
   Y y{};
   a == y;
}

At this time, Both Clang and GCC agree that #2 is the best viable candidate. The result is here. However, it appears to me that this example is similar to the first. Merely, it changes the member candidate to be a non-member candidate. Again, the overload set would consist of two candidates, which look like:

#1''
bool operator==(A<int>,Y); [with T = int]  // synthesized candidate for #1

#2''
bool operator==(A<int>,Y); [with T = A<int>]

In this example, partial ordering is also sufficient to determine which candidate is the best. Hence, #1'' still should be the best. Why do Clang and GCC both think #2 is the best in this example?

like image 569
xmh0511 Avatar asked Mar 16 '21 05:03

xmh0511


Video Answer


1 Answers

Short answer: You used different Clang versions in your examples, Clang 11 has a correct implementation, while Clang 10 does not.

In my answer I go into detail why Clang 11 and MSVC are right and GCC is wrong in both cases.


From [over.match.oper]#3 the candidates include four sets:

For [...] a binary operator @ [...] four sets of candidate functions, designated member candidates, non-member candidates, built-in candidates, and rewritten candidates, are constructed as follows:

In your case the rewritten candidates are determined by [over.match.oper]#3.4.4:

For the equality operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each non-rewritten candidate for the expression y == x.

In your case for an expression x == y the candidates of interest are:

  • member candidates: candidates for x.operator==(y)
  • non-member candidates: candidates for operator==(x, y)
  • rewritten candidates: non-rewritten candidates for y == x, which are y.operator==(x) and operator==(y, x).

Let's look at your two examples and determine the best candidate.


First example

For the expression a == y the candidates are:

non-rewritten candidates:
    #2 via operator==(a, y)
rewritten candidates:
    #1 via y.operator==(a)

To determine the better candidate we need to apply [over.match.best.general]#2, the relevant rules to determine if F1 is a better match than F2 are:

(2.5) F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in [temp.func.order], or, if not that,

[...]

(2.8) F2 is a rewritten candidate ([over.match.oper]) and F1 is not

If we take #1 to be F1 and #2 to be F2 we get that #1 is a better match, since (2.5) applies and it's considered before (2.8). Clang 11+ and the latest MSVC correctly select #1 as the better candidate here (demo).


Second example

For the expression a == y the candidates are:

non-rewritten candidates:
    #2 via operator==(a, y)
rewritten candidates:
    #1 via operator==(y, a)

Applying [over.match.best.general]#2 and taking #1 to be F1 and #2 to be F2, similarly to the first example we get that #1 is a better candidate since (2.5) applies and it's considered before (2.8). Same as in the first example Clang 11+ and the latest MSVC correctly select #1 as the better candidate (demo).

Why do Clang and GCC both think #2 is the best in this example?

The don't. In your demo link you used Clang 10, while in the first example you used Clang 11. Clang 10 has the same result as GCC in both cases.


In both cases #1 is the better candidate, which Clang 11+ and the latest MSVC correctly select. GCC fails in both cases, selecting #2. My guess would be that GCC's implementation of [over.match.best.general]#2 is incorrect in that it wrongly consideres (2.8) before (2.5) and selects the non-rewritten candidate in both cases.

like image 104
IlCapitano Avatar answered Oct 17 '22 02:10

IlCapitano