Understanding overload resolution ranking involvin

2019-04-27 22:49发布

I am trying to understand overload resolution.

First let's consider this first case:

struct int1{
   int val;
   operator int&()&{
      return val;
      }
   operator const int &() const&{
      return val;
      }
    };

void f(int &){}       //f#1
void f(const int&){}  //f#2

void test1(){
  int1 x;
  f(x);
   //Conversion sequence for f#1: 
   //   - int_wrapper& --> int1::operator int&
   //   => Ranking: user defined conversion rank
   //Converison sequence for f#2:
   //   - int1& --> int1::operator int & --> const int&
   //   - int1& --> const int1 &         --> int1::operator const int& 
   //   => Ranking: ambiguous because 2 conversion sequence [over.best.ics]/10 
   //            => user defined conversion rank 
   //
   //=> No best viable overload, 2 user defined conversion rank 
}

Unlike my wrong analysis, compilers agrees: the call to f is not ambiguous. Why?


Now consider this second case, which is very similar, I just replace the int& by int &&:

struct int2{
   int val;
   operator int&&()&&{
      return std::move(val);
      }
   operator const int &() const&{
      return val;
      }
    };

void g(int &&){}     // g#1 
void g(const int&){} // g#2

void test2(){
  int2 x;
  g(std::move(x));
  //Conversions sequence for g#1 
  //   - int2&& --> int2::operator int&&
  //   => Ranking: user defined conversion rank
  //Conversion sequence for g#2 
  //   - int2&& --> const int2&           --> int2::operator const int&
  //   - int2&& --> int2::operator int&&  --> const int&
  //   => Ranking: ambiguous because 2 conversion sequence [over.best.ics]/10 
  //            => user defined conversion rank 
  //
  //=> No best viable overload, 2 user defined conversion rank 
  }

My analysis (which is certainly wrong in this case too) concludes similarly that the call to g is ambiguous. Unfortunatly, in this second case, compilers do not agree:

  • Clang (3.4.1 to 5.0), MSVC 19 2017 RTW, Zapcc 190308 (Clang derivate), ellcc (0.1.33, 0.1.34)(Clang derivate) => call to g is ambiguous;
  • GCC (4.8.1 to 7.2), icc (16 to 18) => call to g is not ambiguous.

What is the right analysis and which compiler is right?


Could you precise why the rule below does not apply, or when is it applied?

[over.best.ics]/10:

If several different sequences of conversions exist that each convert the argument to the parameter type, the implicit conversion sequence associated with the parameter is defined to be the unique conversion sequence designated the ambiguous conversion sequence . For the purpose of ranking implicit conversion sequences as described in 16.3.3.2, the ambiguous conversion sequence is treated as a user-defined conversion sequence that is indistinguishable from any other user-defined conversion sequence

2条回答
欢心
2楼-- · 2019-04-27 23:30

N4713 16.3.3.2:

3.3 User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or they initialize the same class in an aggregate initialization and in either case the second standard conversionsequence of U1 is better than the second standard conversion sequence of U2. [Example

struct A {
    operator short();
} a;
int f(int);
int f(float);
int i = f(a); // calls f(int), because short→int is // better than short →float

end example]

So, standard conversion is the key:

3.2 Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if

3.2.3 S1 and S2 are reference bindings (11.6.3) and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference [ Example:

int i;
int f1();
int&& f2();
int g(const int&);
int g(const int&&);
int j = g(i); // calls g(const int&)
int k = g(f1()); // calls g(const int&&)
int l = g(f2()); // calls g(const int&&)
struct A {
    A& operator<<(int);
    void p() &;
    void p() &&;
};
A& operator<<(A&&, char);
A() << 1; // calls A::operator<<(int)
A() << ’c’; // calls operator<<(A&&, char)
A a;
a << 1; // calls A::operator<<(int)
a << ’c’; // calls A::operator<<(int)
A().p(); // calls A::p()&&
a.p(); // calls A::p()&

end example]

3.2.6: S1 and S2 are reference bindings (11.6.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers. [Example:

int f(const int &);
int f(int &);
int g(const int &);
int g(int);
int i;
int j = f(i); // calls f(int &)
int k = g(i); // ambiguous
struct X {
    void f() const;
    void f();
};
void g(const X& a, X b) {
    a.f(); // calls X::f() const
    b.f(); // calls X::f()
}

— end example]

So gcc meets the standards's requirement.

查看更多
冷血范
3楼-- · 2019-04-27 23:52

Both of these examples are more complicated representations of simpler underlying concepts:

void f(int& );        // #1
void f(int const& );  // #2
void g(int&& );       // #3
void g(int const& );  // #4

int i;
f(i); // calls #1
g(0); // calls #3

For the first call, we favor the the less cv-qualified type, so int& is better than int const&. For the second call, we favor binding to rvalue reference over binding to lvalue reference, so int&& is better than int const&.

In the specific example, these preferences manifest themselves over the choice of conversion function used by way of the implicit object parameter. For the first example, because we're binding the implicit object parameter to int1& for converting to int&, but int1 const& for converting to int const&. Likewise, in the second example, we're binding the implicit object parameter to int2&& for converting to int&&, but int2 const& for converting to int const&.

I'd call this a clang bug.

查看更多
登录 后发表回答