Understand the bullet (5.4.1) in [dcl.init.ref] clause

Viewed 140

Given the following example,

struct S 
{ 
    operator const double&();
}

const int& ref = S();

First per [dcl.init.ref]/5:

A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

Taking "cv1 T1" as const int and "cv2 T2" as S.

Skipping all discarded bullets until we reach to [dcl.init.ref]/(5.4) which says:

(5.4) Otherwise:

  • (5.4.1) If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion ([dcl.init], [over.match.copy], [over.match.conv]); the program is ill-formed if the corresponding non-reference copy-initialization would be ill-formed. The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference. For this direct-initialization, user-defined conversions are not considered.

  • (5.4.2) [..]

I think all we're agree on that this bullet (5.4.1) is one satisfied because T2 is a class type, and T1 is not reference-related to T2. But my problem at this point is that I can't understand the rest of the wording, and how it's related to each other.

Given the first sentence: "If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion" What's the intention of the bold part? And the entity being initialized is "lvalue reference to cv1 T1" not an object of type "cv1 T1". So why the word "object" is mentioned here? Also, what're those rules of "copy-initialization"?

Given the second sentence: "The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference.". Does this mean that we have to go back again to the beginning of [dcl.init.ref]/5? and why?

The last thing I need to understand is that the reference being initialized is of type const int&, and the called conversion function returns const double&; so how ref can bind to const double which is of an unrelated type?

1 Answers

If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type "cv1 T1" by user-defined conversion

As it says, you use the rules for copy-initialization of an object (not reference) of type cv1 T1. In other words, you pretend that the thing you have to initialize is an object, not reference, and you figure out how to perform such an initialization (treated as a copy-initialization, not a direct-initialization). The rules for copy-initialization of objects are found in [dcl.init.general]/16. In this case, you will reach p16.7:

Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated ([over.match.conv]), and the best one is chosen through overload resolution ([over.match]). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.

[over.match.conv] explains how to determine the candidate conversion functions. Clearly, in this case there is only one candidate. The conversion sequence required to initialize an object of type const int from S() would be calling the conversion function S::operator const double&, followed by an lvalue-to-rvalue conversion, and finally a floating-integral conversion.

After [over.match] has been applied to choose the best conversion function, we go back to [dcl.init.ref]/5.4.1:

The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference.

Now, here's the subtlety: we look at the hypothetical conversion sequence that would have occurred if we were initializing an object of type const int, but we do not actually initialize an object of type const int. Instead, we only call the conversion function, S::operator const double&, and we ignore the rest of the hypothetical conversion sequence. With the result of the conversion function, which is an lvalue of type const double, we direct-initialize our reference from this value.

To determine how to perform this direct-initialization, we have to go back to the beginning of [dcl.init.ref]/5, but in order to avoid potential recursion, it is specified that user-defined conversions will not be considered during this second round. In this case [dcl.init.ref]/5.4.2 will be reached in the second round:

Otherwise, the initializer expression is implicitly converted to a prvalue of type "T1". The temporary materialization conversion is applied, considering the type of the prvalue to be "cv1 T1", and the reference is bound to the result.

This is where the temporary materialization conversion that you were wondering about is specified.

Related