Why does user-defined conversion applied during the initialization?
The wording was defective and changed with C++14. Now [over.best.ics]/4 reads
However, if the target is
- the first parameter of a constructor or
- […]
and the constructor or user-defined conversion function is a candidate
by
- 13.3.1.3, when the argument is the temporary in the second step of a class copy-initialization,
- 13.3.1.4, 13.3.1.5, or 13.3.1.6 (in all cases), or
- the second phase of 13.3.1.7 when the initializer list has exactly one element, and the target is the first parameter of a
constructor of classX
, and the conversion is toX
or reference to
(possibly cv-qualified)X
,
user-defined conversion sequences are not considered. [ Note: These
rules prevent more than one user-defined conversion from being applied
during overload resolution, thereby avoiding infinite recursion. —
end note ]
The conversion of B()
to int
is not covered by this - the bold phrase only appertains to the binding of a reference to a temporary during copy-initialization.
However, Clang rejects this sample code according to the above:
class A;
struct B
{
operator A();
};
struct A
{
A(A const&){}
};
A a{B()};
Initialization by conversion function for direct reference binding
Why do we even need a conversion function to convert from cv2 T2 to cv1 T even though “cv1 T” is reference-compatible with “cv2 T2”?
Because only after the conversion is done(using the conversion function), the result is of type cv2 T2
. In other words, the result of using/calling the conversion function is cv2 T2
and not whatever expression you have before using the conversion function. This is the whole point of using a conversion function so that we can something that is reference compatible with cv1 T
.
Lets look at some examples:
Example 1
struct C {
operator int&();
};
const int& ref= C();
In the above example, the type cv1 T = const int
is not reference-compatible with the class-type C
. But due to the presence of the conversion function C::operator int&()
, a given C
can be converted to an lvalue of type cv2 T2 = int
. And we know that const int
is reference compatible with int
. This means that the reference ref
can be bound to the result of the conversion function according to your quoted clause.
Example 2
This example 2 does not uses a conversion function but i've added this just to clarify the explanation given at the beginning of my answer.
double val = 4.55;
const int &ref = val;
In the above example, the type cv1 T = const int
is not reference-compatible with the type double
. And at the end, the reference ref
is bound to a materialized temporary(prvalue) of type cv2 T2 = int
. This is possible because const int
is reference compatible with the type of the materialized temporary int
.
The important thing to note here is that a temporary is materialized because originally cv1 T = const int
was not reference compatible with double
. But after the materialization, cv1 T = const int
is reference compatible with the result cv2 T2 = int
.
What's the rank of implicitly conversion for copy-list-initialization
Answers are here,Because the argument is initializer list,so [over.ics.list] rules are performed when overload resolution.
[over.ics.list]/6
- Otherwise, if the parameter is a non-aggregate class X and overload resolution per [over.match.list] chooses a single best constructor C of X to perform the initialization of an object of type X from the argument initializer list:
- If C is not an initializer-list constructor and the initializer list has a single element of type cv U, where U is X or a class derived from X, the implicit conversion sequence has Exact Match rank if U is X, or Conversion rank if U is derived from X.
- Otherwise, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion.
Hence,for these three candiate constructors,the parameter of them are all non-aggerate class type,so the implicit conversion sequence is used to convert the single element of the initializer list to corresponding parameter type of the constructor of parameter of constructor B
and therefore these conversion sequences are all user-defined conversion sequence and the second standard conversion sequence are all identity conversions.So the rank of them are indistinguishable.
When are user-defined conversion sequences not considered for selecting viable constructors in C++?
You can go read [dcl.init]/17 for the actual standardese. The "second step" here is referring to copy-initializing a variable of class type A
from something b
of an unrelated type. In such a case, copy-initialization happens in two steps:
- Step 1: you implicitly convert
b
toA
. If you call a converting constructor for this, it creates a temporaryA
. - Step 2: you then initialize the
A
variable from the result of the conversion. (In sane classes, this is typically elided.)
What that quote is saying is that you don't do user-defined conversions in this second step.
For example, with your A
, A a = 0;
. In the first step, you make an A
temporary from 0
. In the second step, you try to initialize a
with that temporary - without using user-defined conversions. That fails, because neither A
constructor is viable.
Conversion operator in direct-initialization
You are correct that only the constructors of A
are considered when doing A a(b);
. [over.match.ctor]/1 states
When objects of class type are direct-initialized, copy-initialized from an expression of the same or a derived class type ([dcl.init]), or default-initialized, overload resolution selects the constructor. For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors ([class.conv.ctor]) of that class. The argument list is the expression-list or assignment-expression of the initializer.
emphasis mine
This means that A()
, A(const A&)
and A(A&&)
are the candidate list. Then we have [over.match.viable]/4
[...]Third, for F to be a viable function, there shall exist for each argument an implicit conversion sequence that converts that argument to the corresponding parameter of F.[..]
which allows an implicit conversion of b
to an A
so that A(A&&)
can be called.
Return value is copied when using initializer list with a user-defined conversion operator that returns reference
You are actually doing reference initialization here:
Nothing & by_initialization_2{wrapper};
The rules say that since the initializer is not the same type as the reference being bound, user-defined conversion operators are considered, which is fine, since you have the appropriate conversion operator.
However, if the l-value returned by the conversion function is passed through a brace-init list, then a temporary is materialized. Since you can't bind a non-const reference to a temporary, the initialization fails.
Related Topics
Safety of Casting Between Pointers of Two Identical Classes
How to Set Given Channel of a Cv::Mat to a Given Value Efficiently Without Changing Other Channels
Check Xmm Register for All Zeroes
How to Remove Duplicate Values from a List in C++
Throwing the Fattest People Off of an Overloaded Airplane
Check Keypress in C++ on Linux
Strange Exception Throw - Assign: Operation Not Permitted
Generate All Sequences of Bits Within Hamming Distance T
Throw and Ternary Operator in C++
How to Print the Value of Nullptr on Screen
How to Use 'Const_Cast' to Modify a Constant Variable
Can't Use Structure in Global Scope
Vector.Erase(Iterator) Causes Bad Memory Access
Why Is the Data Type Needed in Pointer Declarations