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?
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.
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.
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++
Ambiguous. (Also, if you're stopping at the tl;dr, then the language-lawyer
tag might not be your cup-of-tea. ^_^)
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
In both cases
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.
T
are candidates ([over.match.copy]/1.1)
bar::bar(const foo& foo);
is a candidate_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:
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
).
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
and const foo
to const foo&
([over.match.viable]/4)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
foo::operator bar() const
const foo&
in both cases ([over.match.funcs]/4)foo
and const foo
to const foo&
([over.match.viable]/4)Both are Identity => User Defined Conversion => Identity, i.e., are user-defined conversion sequences.
Can we establish a ranking between the sequences? Only if one of the following applies
Conversion sequences are indistinguishable, i.e., neither is better nor worse
Is either function a 'better' function? Only if one of the following applies
bar
, but the other is not a constructor for a base class of bar
([over.match.best]/1.9)Neither is a 'better' function, so the call is ill-formed. [over.match.best]/2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With