In the following program struct B
defines two conversion operators: to A
and to const A&
. Then A
-object is created from B
-object:
struct A {};
struct B {
A a;
B() = default;
operator const A&() { return a; }
operator A() { return a; }
};
int main() {
(void) A(B{});
}
The program is
- accepted by MSVC in all language versions.
- rejected by GCC in all language versions.
- rejected by Clang in C++14 mode and accepted in C++17 mode. Demo: https://gcc.godbolt.org/z/rKv589EG3
GCC error message is
error: call of overloaded 'A(B)' is ambiguous
note: candidate: 'constexpr A::A(const A&)'
note: candidate: 'constexpr A::A(A&&)'
Which compiler is right here?
The implementation divergence is probably related to CWG 2327.
If look strictly at the wording of C++20, then GCC is right and the overload resolution is ambiguous. I'll go into the wording in detail first, and then at the end of the answer I'll discuss CWG 2327 again.
There are two candidates for the initialization:
The first step is to determine the implicit conversion sequence required to call each candidate: the ICS from "rvalue of
B
" toconst A&
, and the ICS from "rvalue ofB
" toA&&
. The value category of theB
is not actually relevant, though, because neither conversion function inB
has a ref-qualifier.To convert from
B
toconst A&
or toA&&
, we go to [dcl.init.ref]. For the conversion toconst A&
, p5.1.2 applies:This applies because
B
can be converted to an lvalue of typeconst A
(this is theT3
), andconst A
(asT1
) is reference-compatible withconst A
(asT3
).To convert from
B
toA&&
, the applicable rule is p5.3.2, which is very similar except that this time we are only looking for conversion functions that yield an rvalue of some typeT3
.12.4.2.7, a.k.a. [over.match.ref] explains how to find the candidate conversion functions:
When initializing
const A&
, obviouslyoperator const A&()
is one of the candidates.operator A()
is not a candidate since it doesn't yield an lvalue. (The fact that aconst A&
can be initialized from anA
rvalue is irrelevant; as you can see from the wording above, there is no special casing forconst
references in [over.match.ref].) When initializingA&&
,operator A()
is a candidate, andoperator const A&()
is not, because it doesn't yield an rvalue.Thus, we have the following implicit conversion sequences:
A::A(const A&)
, the ICS is a temporary materialization conversion onB{}
followed by the user-defined conversionB::operator const A&
and finally an identity conversion.A::A(A&&)
, the ICS is a temporary materialization conversion onB{}
followed by the user-defined conversionB::operator A
and finally an identity conversion.The basic rule for ranking user-defined conversion sequences, [over.ics.rank]/3.3, is that if two ICSes use the same user-defined conversion function, the one whose second standard conversion sequence is better is considered to be the overall better ICS. This rule doesn't apply here because the two conversion functions are different. The tie-breaker rules in p4 of this section do not prefer one over the other. So finally we have to go to the global tie-breaker rules in [over.match.best.general]. p2.2 seems like it might be relevant:
However, a proper understanding of the wording reveals that these rules don't pick out either constructor as being better than the other, either. Although our overload resolution involved a "subtask" overload resolution to select user-defined conversion functions, those subtasks have already been completed and we are no longer in the "context" of an initialization by user-defined conversion; we are in the context of selecting the best constructor.
So there is no rule that can be used to select either constructor over the other; the overload resolution fails.
There does not seem to be any wording from earlier standard editions that would allow this to compile.
But if you look at the discussion of CWG 2327 on the linked page, you'll see that Richard Smith suggests that a change be made to the initialization rules. Under the current rules, since
A
is a class type, the overload resolution always involves enumerating constructors ofA
and picking the best candidate, which we discussed above, which might involve as "subtasks" the consideration of conversion functions fromB
to types required byA
's constructors. Smith has informally proposed that the conversion functions ofB
be considered at the top level alongside the constructors ofA
. However, there is currently no proposed wording explaining how to rank such conversion functions against constructors.If there are three possible candidates for the initialization, namely
A::A(const A&)
(whereB{}
must be implicitly converted toconst A&
)A::A(A&&)
(whereB{}
must be implicitly converted toA&&
)B::operator A
directlythen it would be reasonable for the third option to be considered better than the other two, and I suspect that Smith has already come up with some rule and implemented it in Clang, but I'm not sure what it is. I'm sure he'll add wording to the issue once he's worked out all the cases for the wording. If this is indeed the case, then it makes sense that Clang accepts the code (by calling
operator A
instead of a constructor) only in C++17 mode and later, where guarantee copy elision applies. As for MSVC, perhaps they have a proposed resolution that they've decided to apply all the way back to C++14 (and probably C++11 as well).