The wording that we currently have in the standard about implicit conversion sequences is not great, which is probably why you see this implementation divergence.
Consider [over.best.ics.general] paragraph 1 and the first half of paragraph 6:
An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called. The sequence of conversions is an implicit conversion as defined in [conv], which means it is governed by the rules for initialization of an object or reference by a single expression ([dcl.init], [dcl.init.ref]).
[...]
When the parameter type is not a reference, the implicit conversion sequence models a copy-initialization of the parameter from the argument expression.
The implicit conversion sequence is the one required to convert the argument expression to a prvalue of the type of the parameter.
This suggests that the implicit conversion sequence from v
(an lvalue of type volatile S
) to the parameter type S
is determined by the rules of copy-initialization of an S
object from a volatile S
lvalue. This is governed by [dcl.init.general]/16.6.2 since the cv-unqualified version of the type of the initializer is the same as the class type of the object being initialized. Because the overload resolution fails (S
has no volatile-qualified copy constructor) sub-sub-bullet 3 applies, and the initialization is ill-formed.
However, when [dcl.init] says that the initialization is ill-formed, and it is a hypothetical initialization for the purpose of forming an implicit conversion sequence, it sometimes means that there is no implicit conversion sequence, and it sometimes means the implicit conversion sequence exists but actually using it in a call would be ill-formed, and you sort of just have to know which one it means. (See CWG2525.)
This matters because if there is no implicit conversion sequence, then the other overload must be selected (the one with a parameter type of volatile S&
), but if there is an implicit conversion sequence that would be ill-formed if used for a call, then the overload resolution is ambiguous.
The example in the second half of [over.best.ics.general] paragraph 6 hints weakly that the latter interpretation is correct:
A parameter of type A
can be initialized from an argument of type const A
. The implicit conversion sequence for that case is the identity sequence; it contains no “conversion” from const A
to A
.
A
could theoretically be a class type whose copy constructor has the form A(A&)
(rather than A(const A&)
) yet the example is claiming that the implicit conversion sequence is always the identity conversion.
Paragraph 7 is also a weak hint:
When the parameter has a class type and the argument expression has the same type, the implicit conversion sequence is an identity conversion. When the parameter has a class type and the argument expression has a derived class type, the implicit conversion sequence is a derived-to-base conversion from the derived class to the base class. A derived-to-base conversion has Conversion rank ([over.ics.scs]).
The first sentence doesn't apply because the parameter type S
is not the same as the type of the argument, volatile S
. But the second sentence hints that even if e.g. the copy-initialization of the parameter type from the argument type would not be a simple call to the parameter type's copy constructor (but could actually involve other competing constructors, and may be ambiguous) it doesn't affect the formation of the implicit conversion sequence. So we are led to think that the formation of an implicit conversion sequence to Base
from cv Derived
always succeeds. If that's the case, it ought to be even more the case for a conversion to S
from cv S
(and perhaps the first sentence should be amended to include the cv-qualified case).
So I think Clang and GCC are doing what the standard means, but it's honestly not that clear.
const
instead and deleting copy constructor ofS
leads to the same outcome. – Believefoo(v)
is an equally good match for bothfoo()
overloads, hence the ambiguity. The test of whether the argument can be passed (e.g.foo(S)
requires an accessible copy constructor) occurs AFTER a single "best" candidate overload is picked. MSVC appears to be disregarding candidates prematurely. One reason the standard does it that way is to avoid circumstances where adding an overload silently changes which function is called by existing code (such changes could otherwise be surprising, and cause bugs that are hard to find). – Arboriculturevoid foo(S);
case), at the callsite offoo(v);
a copy is made of thev
. That copy is an rvalue, which will be bound to the parameter of thevoid foo(S);
, which happens — in this case — to be unnamed in the declaration). – Mcauleyvoid foo(S par);
and the callS s; foo(s);
,par
(an lvalue) is copy-constructed froms
(also an lvalue). Where is the rvalue? Do you think,s
is first copied to a prvalue, thenpar
is move-constructed from it? I think this doesn't happen. Would be too slow, and unnecessary anyway. – Voodooismvoid foo(S)
. For most implementations, when you call a function the arguments are pushed onto the stack (or possibly into registers depending on the calling convention), and then the function is invoked. Those arguments are the local parameters of the function. (This is not how the C++ standard describes it, as the standard describes the C++ abstract machine, which does not specify a stack or heap implementation details.) The values at the callsite that are pushed on the stack are unnamed; they're rvalues. – Mcauleypar
is an lvalue, which is not "bound to an rvalue", but copy-constructed froms
. And, both are lvalues. Is this reasoning wrong? – Voodooism