[dcl.init.general]/14
The initialization that occurs [...] as well as in argument passing [...] is called copy-initialization.
The semantics of the copy-initializations in the two function calls in the question are governed by [dcl.init.general]/16.6.2:
Otherwise, if the destination type is a (possibly cv-qualified) class type:
[...]
Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]).
Then:
- If overload resolution is successful, the selected constructor is called to initialize the object, with the initializer expression or expression-list as its argument(s).
- [...]
[...]
The initialization of the parameter a
from the argument a
, or the parameter b
from the argument b
, is by constructor. In these cases the copy constructors are used (I hope this is obvious enough that I don't need to explain it).
Since A
and B
don't have user-declared copy constructors, [class.copy.ctor]/6 applies:
If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defaulted ([dcl.fct.def]).
The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor ([depr.impldec]).
The semantics of an implicitly declared copy constructor are given by [class.copy.ctor]/14.
The implicitly-defined copy/move constructor for a non-union class X
performs a memberwise copy/move of its bases and members. [...] The order of initialization is the same as the order of initialization of bases and members in a user-defined constructor (see [class.base.init]). Let x
be either the parameter of the constructor or, for the move constructor, an xvalue referring to the parameter. Each base or non-static data member is copied/moved in the manner appropriate to its type:
- if the member is an array, each element is direct-initialized with the corresponding subobject of
x
;
- if a member
m
has rvalue reference type T&&
, it is direct-initialized with static_cast<T&&>(x.m)
;
- otherwise, the base or member is direct-initialized with the corresponding base or member of
x
.
Virtual base class subobjects shall be initialized only once by the implicitly-defined copy/move constructor (see [class.base.init]).
Since A
has neither bases nor non-static data members, its copy constructor does nothing. B
on the other hand has a single non-static data member of type int
, so according to the above, its implicit copy constructor direct-initializes mem
from the mem
member of the object being copied, which is a const lvalue of type int
(because the parameter to the copy constructor has type const B&
).
The semantics of a direct-initialization of an int
from a const lvalue of type int
are given by [dcl.init.general]/16.9:
- [...]
- Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. A standard conversion sequence ([conv]) is used to convert the initializer expression to a prvalue of the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed. When initializing a bit-field with a value that it cannot represent, the resulting value of the bit-field is implementation-defined. [...]
The destination type is int
, so we must form a standard conversion sequence from an lvalue of const int
to a prvalue of int
. The standard conversion that can accomplish this is the lvalue-to-rvalue conversion. See [conv.lval]/1 (footnotes omitted):
A glvalue of a non-function, non-array type T
can be converted to a prvalue. If T
is an incomplete type, a program that necessitates this conversion is ill-formed. If T
is a non-class type, the type of the prvalue is the cv-unqualified version of T
. Otherwise, the type of the prvalue is T
.
Lvalue-to-rvalue conversions are applied when the rules of the language either explicitly require them to be performed, or when the rules of the language call for a standard conversion sequence to be performed from some source type to some destination type and the lvalue-to-rvalue conversion ends up being a necessary step in that standard conversion sequence. The copy-initialization of an empty class type such as A
from the same type is not one of these situations since, based on the above, it is specified to have the behaviour of calling a constructor (not performing a standard conversion) and there is no other rule that demands the lvalue-to-rvalue conversion in such a case. However, copying a glvalue of a scalar type always entails an lvalue-to-rvalue conversion.