Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 784
Tavian Barnes Avatar asked Aug 11 '17 13:08

Tavian Barnes


People also ask

What does ambiguous in C++ mean?

Access to a base class member is ambiguous if you use a name or qualified name that does not refer to a unique function or object. The declaration of a member with an ambiguous name in a derived class is not an error.

What is ambiguity error and how do you resolve it?

Ambiguity errors occur when erasure causes two seemingly distinct generic declarations to resolve to the same erased type, causing a conflict. Here is an example that involves method overloading: Notice that MyGenClass declares two generic types: T and V.

How do you solve ambiguity in C++?

If the derived class object needs to access one of the similarly named member functions of the base classes then it results in ambiguity because the compiler gets confused about which base's class member function should be called. Example: C++


1 Answers

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

  • we are performing copy-initialization ([dcl.init]/15)
  • the destination type is a class type ([dcl.init]/17.6)
    • (X) initializer expression is not a prvalue ([dcl.init]/17.6.1)
    • (X) source type is not the same or derived from the destination type ([dcl.init]/17.6.2)
    • User-defined conversion sequences are enumerated from [over.match.copy] and the best chosen by overload resolution.

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)

  • One argument in the list of arguments, exactly one parameter. ([over.match.viable]/2.1)
  • No associated constraints ([over.match.viable]/3)
  • Implicit conversion sequence exists from both foo and const foo to const foo& ([over.match.viable]/4)
  • Initial standard conversion is Identity conversion in both cases: [over.best.ics]/5 => [over.ics.ref]/1 for direct reference binding:
    • const foo is reference-compatible with both foo and const foo as const is more cv-qualified than both const and nothing. [dcl.init.ref]/4
    • const foo& binds directly to an lvalue foo and an lvalue const foo. [dcl.init.ref]/5
  • Viable

foo::operator bar() const

  • One argument in the list of arguments, exactly one implicit object parameter. ([over.match.viable]/2.1)
    • Implicit object parameter is const foo& in both cases ([over.match.funcs]/4)
  • No associated constraints ([over.match.viable]/3)
  • Implicit conversion sequence exists from both foo and const foo to const foo& ([over.match.viable]/4)
  • Initial standard conversion is Identity conversion in both cases, see above.
  • Viable

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

  • (X) Neither is a better conversion sequence ([over.match.best]/1.3)
  • (X) Second standard conversion is the same ([over.match.best]/1.4)
  • (X) Not direct reference binding a reference to a function type ([over.match.best]/1.5)
  • (X) Neither is a function template specialisation ([over.match.best]/1.6 and [over.match.best]/1.7)
  • (X) Neither is constrained ([over.match.best]/1.8)
  • (X) One is a constructor for bar, but the other is not a constructor for a base class of bar ([over.match.best]/1.9)
  • (X) No deduced class types ([[over.match.best]/1.10](http://eel.is/c++draft/over.match.best#1.10 and [over.match.best]/1.11
  • (X) Neither is a constructor template ([over.match.best]/1.12)

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

like image 166
TBBle Avatar answered Oct 04 '22 09:10

TBBle