Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is clang wrongfully reporting ambiguity when mixing member and non-member binary operators?

Consider the following code, which mixes a member and a non-member operator|

template <typename T>
struct S
{
    template <typename U>
    void operator|(U)
    {}
};

template <typename T>
void operator|(S<T>,int) {} 

int main()
{
    S<int>() | 42;
}

In Clang this code fails to compile stating that the call to operator| is ambiguous, while gcc and msvc can compile it. I would expect the non-member overload to be selected since it is more specialized. What is the behavior mandated by the standard? Is this a bug in Clang?

(Note that moving the operator template outside the struct resolves the ambiguity.)

like image 364
Luc Touraille Avatar asked Oct 28 '21 21:10

Luc Touraille


1 Answers

I do believe clang is correct marking the call as ambiguous.

Converting the member to a free-standing function

First, the following snippet is NOT equal to the code you have posted w.r.t S<int>() | 42; call.

template <typename T>
struct S
{

};
template <typename T,typename U>
void operator|(S<T>,U)
{}

template <typename T>
void operator|(S<T>,int) {} 

In this case the now-non-member implementation must deduce both T and U, making the second template more specialized and thus chosen. All compilers agree on this as you have observed.

Overload resolution

Given the code you have posted.

For overload resolution to take place, a name lookup is initiated first. That consists of finding all symbols named operator| in the current context, among all members of S<int>, and the namespaces given by ADL rules which is not relevant here. Crucially, T is resolved at this stage, before overload resolution even happens, it must be. The found symbols are thus

  • template <typename T> void operator|(S<T>,int)
  • template <typename U> void S<int>::operator|(U)

For the purpose of picking a better candidate, all member functions are treated as non-members with a special *this parameter. S<int>& in our case.

[over.match.funcs.4]

For implicit object member functions, the type of the implicit object parameter is

  • (4.1) “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
  • (4.2) “rvalue reference to cv X” for functions declared with the && ref-qualifier

where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration.

This leads to:

  • template <typename T> void operator|(S<T>,int)
  • template <typename U> void operator|(S<int>&,U)

Looking at this, one might assume that because the second function cannot bind the rvalue used in the call, the first function must be chosen. Nope, there is special rule that covers this:

[over.match.funcs.5][Emphasis mine]

During overload resolution, the implied object argument is indistinguishable from other arguments. The implicit object parameter, however, retains its identity since no user-defined conversions can be applied to achieve a type match with it. For implicit object member functions declared without a ref-qualifier, even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.

Due to some other rules, U is deduced to be int, not const int& or int&. S<T> can also be easily deduced as S<int> and S<int> is copyable.

So both candidates are still valid. Furthermore neither is more specialized than the other. I won't go through the process step by step because I am not able to who can blame me if even all the compilers did not get the rules right here. But it is ambiguous exactly for the same reason as foo(42) is for

void foo(int){}
void foo(const int&){}

I.e. there is no preference between copy and reference as long as the reference can bind the value. Which is true in our case even for S<int>& due to the rule above. The resolution is just done for two arguments instead of one.

like image 82
Quimby Avatar answered Sep 28 '22 07:09

Quimby