Search code examples
c++language-lawyeroverload-resolutionconversion-operatorcopy-initialization

Which of these conversions should be ambiguous?


I have some code like the following:

class bar;

class foo
{
public:
    operator bar() const;
};

class bar
{
public:
    bar(const foo& foo);
};

void baz() {
    foo f;
    bar b = f;   // [1]

    const foo f2;
    bar b2 = f2; // [2]
}

GCC gives an error at [2] but not [1]. Clang gives an error on both, and apparently MSVC gives an error on neither. Who's right?


Solution

  • tl;dr

    Ambiguous. (Also, if you're stopping at the tl;dr, then the language-lawyer tag might not be your cup-of-tea. ^_^)

    Spoiler

    Both candidates have a single const foo& parameter, which binds equally to a const foo or foo argument. No other rules appear that would prefer one or the other function.


    Breaking it down against the current C++ working draft

    Initializers [dcl.init]

    In both cases

    Copy-initialization of class by user-defined conversion [over.match.copy]

    T is the type being intialised, in both cases this is bar. S is the type of the initializer expression, in the two cases foo and const foo respectively.

    • converting constructors of T are candidates ([over.match.copy]/1.1)
      • bar::bar(const foo& foo); is a candidate
    • the type of the initializer expression is _cv_ S so non-explicit conversion functions are considered: ([over.match.copy]/1.2)
      • foo::operator bar() const is not hidden within foo or within const foo, and yields bar which is the same as T, and hence is a candidate.

    So our candidate list is the same in both cases:

    • bar::bar(const foo& foo)
    • foo::operator bar() const

    In both cases, we have a user-defined conversion consisting of:

    1. Standard conversion of source type to user-defined conversion argument
    2. User-defined conversion (one of the above two functions) to result type
    3. Standard conversion of result type to target type

    If we select the constructor, the "result type" is "a prvalue of the cv-unqualified version of the destination type whose result object is initialized by the constructor" ([dcl.init]/17.6.3), so for both candidate functions, the second Standard Conversion is Identity (bar -> bar).

    Overload resolution [over.match]

    Subsetting the viable candidate functions [over.match.viable]

    Per [dcl.init]/17.6.3, the initializer expression is going to be the argument to the selected call, in the two cases foo and const foo respectively.

    bar::bar(const foo& foo)

    foo::operator bar() const

    Select the best viable function [over.best.ics]

    Both are Identity => User Defined Conversion => Identity, i.e., are user-defined conversion sequences.

    Ranking conversion sequences over.ics.rank

    Can we establish a ranking between the sequences? Only if one of the following applies

    • (X) Not list-initialization sequences ([over.ics.rank]/3)
    • (X) Not a standard conversion sequence ([over.ics.rank]/3.2)
    • (X) The two sequences do not contain "the same user-defined conversion function or constructor or [...] initialize the same class in an aggregate initialization" ([over.ics.rank]/3.3)

    Conversion sequences are indistinguishable, i.e., neither is better nor worse

    Best viable function over.match.best

    Is either function a 'better' function? Only if one of the following applies

    Neither is a 'better' function, so the call is ill-formed. [over.match.best]/2