0

Consider the following code snippet:

class A;

class B { 
      public: 
         B(){} 

         B(A&) // conversion constructor that takes cv-unqualified A
         { 
              cout << "called B's conversion constructor" << endl; 
         } 
};

class A { 
      public: 
         operator B() const // conversion operator that takes cv-qualified A
         { 
              cout << "called A's conversion operator" << endl; 
              return B(); 
         } 
};

int main()
{
    B b = A(); // who gets called here?
    return 0;
}

According to this question, the conversion sequence with the least cv-qualified form wins (13.3.3.2/3 in specification):

Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if [...] S1 and S2 are reference bindings (8.5.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.

Yet in the above snippet, the conversion operator always gets picked, regardless of A being cv-qualified or not in both of the functions. The only exception is that when both the constructor and the operator are cv-qualified, the compiler complains about ambiguity in choosing the conversion sequence, while both cv-unqualified case does not (why?).

So the question is:

  1. Why the conversion operator always gets picked in this case?
  2. Why did both cv-qualified causes an ambiguity, while both cv-unqualified does not?
Johnson Steward
  • 534
  • 3
  • 16

1 Answers1

1

For the purpose of overload resolution, there is an implicit object parameter for A::operator B(), whose type is cv A&. This parameter is special that it can be bound to an rvalue even if it is an lvalue reference to non-const type according to [over.match.funcs]/5:

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 non-static member functions declared without a ref-qualifier, an additional rule applies:

  • 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. [ Note: The fact that such an argument is an rvalue does not affect the ranking of implicit conversion sequences. — end note ]

and [over.ics.ref]/3

Except for an implicit object parameter, for which see [over.match.funcs], a standard conversion sequence cannot be formed if it requires binding an lvalue reference other than a reference to a non-volatile const type to an rvalue or binding an rvalue reference to an lvalue other than a function lvalue. [ Note: This means, for example, that a candidate function cannot be a viable function if it has a non-const lvalue reference parameter (other than the implicit object parameter) and the corresponding argument would require a temporary to be created to initialize the lvalue reference (see [dcl.init.ref]). — end note ]

So if the conversion constructor takes a non-const parameter, it is not viable while the conversion operator is always viable, which makes overload resolution always choose the conversion operator.


If the conversion constructor takes a const parameter and the conversion operator is non-const, both implicit conversions are identity conversions according to [over.ics.ref]/1:

When a parameter of reference type binds directly to an argument expression, the implicit conversion sequence is the identity conversion ...

Then according to [over.ics.rank]/3:

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

  • ...

  • S1 and S2 are reference bindings, 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.

So the non-const version (conversion operator) is chosen.


Finally, if both are const, there is no difference between them and no special rule applies, so overload resolution fails because of ambiguity.

Community
  • 1
  • 1
xskxzr
  • 12,442
  • 12
  • 37
  • 77