Conversion constructor vs. conversion operator: precedence

15,009

Solution 1

You do copy initialization, and the candidate functions that are considered to do the conversions in the conversion sequence are conversion functions and converting constructors. These are in your case

B(const A&)
operator B() 

Now, that are the way you declare them. Overload resolution abstracts away from that, and transforms each candidate into a list of parameters that correspond to the arguments of the call. The parameters are

B(const A&)
B(A&)

The second one is because the conversion function is a member function. The A& is the so-called implicit object parameter that's generated when a candidate is a member function. Now, the argument has type A. When binding the implicit object parameter, a non-const reference can bind to an rvalue. So, another rule says that when you have two viable functions whose parameters are references, then the candidate having the fewest const qualification will win. That's why your conversion function wins. Try making operator B a const member function. You will notice an ambiguity.

From an object-oriented philosophical standpoint, is this the way the code should behave? Who knows more about how an A object should become a B object, A or B? According to C++, the answer is A -- is there anything in object-oriented practice that suggests this should be the case? To me personally, it would make sense either way, so I'm interested to know how the choice was made.

For the record, if you make the conversion function a const member function, then GCC will chose the constructor (so GCC seems to think that B has more business with it?). Switch to pedantic mode (-pedantic) to make it cause a diagnostic.


Standardese, 8.5/14

Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3).

And 13.3.1.4

Overload resolution is used to select the user-defined conversion to be invoked. Assuming that "cv1 T" is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:

  • The converting constructors (12.3.1) of T are candidate functions.
  • When the type of the initializer expression is a class type "cv S", the conversion functions of S and its base classes are considered. Those that are not hidden within S and yield a type whose cv-unqualified version is the same type as T or is a derived class thereof are candidate functions. Conversion functions that return "reference to X" return lvalues of type X and are therefore considered to yield X for this process of selecting candidate functions.

In both cases, the argument list has one argument, which is the initializer expression. [Note: this argument will be compared against the first parameter of the constructors and against the implicit object parameter of the conversion functions. ]

And 13.3.3.2/3

  • 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.

Solution 2

It seems MSVS2008 has its own opinion about constructor selection: it calls copy constructor in B regardless of constness of A's operator. So be careful here even while standard specifies the correct behavior.

I thought MSVS just search for suitable constructor before conversions operator, but then found that it starts calling A's operator B() if you remove const word from B's constructor. Probably it has some special behavior for temporaries, because the following code still calls B's constructor:

A a;

B b = a;
Share:
15,009
GRB
Author by

GRB

Updated on June 17, 2022

Comments

  • GRB
    GRB almost 2 years

    Reading some questions here on SO about conversion operators and constructors got me thinking about the interaction between them, namely when there is an 'ambiguous' call. Consider the following code:

    class A;
    
    class B { 
          public: 
             B(){} 
    
             B(const A&) //conversion constructor
             { 
                  cout << "called B's conversion constructor" << endl; 
             } 
    };
    
    class A { 
          public: 
             operator B() //conversion operator
             { 
                  cout << "called A's conversion operator" << endl; 
                  return B(); 
             } 
    };
    
    int main()
    {
        B b = A(); //what should be called here? apparently, A::operator B()
        return 0;
    }
    

    The above code displays "called A's conversion operator", meaning that the conversion operator is called as opposed to the constructor. If you remove/comment out the operator B() code from A, the compiler will happily switch over to using the constructor instead (with no other changes to the code).

    My questions are:

    1. Since the compiler doesn't consider B b = A(); to be an ambiguous call, there must be some type of precedence at work here. Where exactly is this precedence established? (a reference/quote from the C++ standard would be appreciated)
    2. From an object-oriented philosophical standpoint, is this the way the code should behave? Who knows more about how an A object should become a B object, A or B? According to C++, the answer is A -- is there anything in object-oriented practice that suggests this should be the case? To me personally, it would make sense either way, so I'm interested to know how the choice was made.

    Thanks in advance

  • GRB
    GRB over 14 years
    Ah, so my problem was not about precedence between the constructor and operator, but the const-ness of each. You were right, changing operator B() to operator B() const resulted in an ambiguity error.
  • Johannes Schaub - litb
    Johannes Schaub - litb over 14 years
    What a coincidence that i put the same "analysis" on another answer of mine as an example earlier: stackoverflow.com/questions/1051379/… xD
  • Dan Nissenbaum
    Dan Nissenbaum about 9 years
    Quick question about the very first sentence of the answer - you do copy initialization.... I don't see a copy occurring anywhere - i.e., there's no A being constructed from another A (nor a B from another B) - so am I correct that you didn't literally mean copy initialization, but rather something along the lines of assignment initialization? Thanks!
  • Dan Nissenbaum
    Dan Nissenbaum about 9 years
    Another question regarding the transformation of the conversion constructor B(const A&) as a result of the overload resolution abstraction that you mention near the start of the answer: It is my understanding that all constructors also utilize a this pointer implicitly, just as the operator B() function does (as you've highlighted). Therefore, wouldn't it be correct to write, for the transformed conversion constructor near the top, void(B&,const A&), rather than B(const A&)? Note that one of your own answers seems to indicate as much - see stackoverflow.com/a/2483725/368896.
  • Escape0707
    Escape0707 over 4 years
    +1 for mentioned -pedantic. I can't reproduce the ambiguity using example code from cppreference.com without that. Blame g++ for that.