3
votes
#include <iostream>
struct Data{};
struct Test{
    Test() = default;
    Test(Data){}
};
int main(){
  Data d;
  Test const& rf = d;
}

Consider the above code, The standard says:

Otherwise:

  • 5.2.2.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.2.2.2 Otherwise, the initializer expression is implicitly converted to a prvalue of type “cv1 T1”. The temporary materialization conversion is applied and the reference is bound to the result.

So, which bullet does the above case obey? The initializer expression is converted to type Test through converting constructor Test::Test(Data) rather than conversion function. However note the emphasized part in 5.2.2.1, It says that the result of the call to the conversion function is then used to direct-initialize the reference. In my example, the called function was converting constructor, hence, the resulting was resulted from converting constructor.

Issue 1:

which bullet covers my example? 5.2.2.1 or 5.2.2.2?


5.2.1.2 has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see [over.match.ref]),

Consider the bullet 5.2.1.2, It has already covered the case which type T2 is a class type, and it can be converted to cv3 T3 through conversion function.

Issue 2:

So, Is it redundant that 5.2.2.1 covers T2 is a class type, and it can be converted to destination type through conversion function, Such case has already covered in 5.2.1.2?

1

1 Answers

2
votes

Issue 1

[dcl.init.ref]/5.2.2.1 applies here. It is the paragraph covering user-defined conversions. One of the acceptable conversion mechanisms is 16.3.1.4 [over.match.copy] which can use converting constructors on T1. The value is converted using this constructor and the resulting temporary is bound to the reference.

[dcl.init.ref]/5.2.2.2 applies to cases of implicit conversion not including user-defined conversion, such as widening numeric conversions.

Issue 2

From [dcl.init.ref]:

(5.2.1) If the initializer expression

...

(5.2.1.2) has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 16.3.1.6),

Jumping to 16.3.1.6 [over.match.ref], there is a lot of prose here, but this is the only relevant part:

(1) ... Assuming that “reference to cv1 T” is the type of the reference being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:

(1.1) The conversion functions of S and its base classes are considered. ...

The rest of the section details which conversion functions of S are eligible to be used, but that's unimportant to the situation in the example code. [over.match.ref] considers only conversion operators on the type of the value being used to initialize the reference, which would not be the case here -- Data has no implicit conversion operators. This section makes no reference to converting constructors of T.

Therefore [over.match.ref] and by extension [dcl.init.ref]/5.2.1.2 do not apply to this case.