Consider a case where we've reached into bullet [dcl.init.ref]/(5.4.1) during reference binding:
(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.
How, during direct initialization of a reference, user-defined conversions are not considered?
If possible, support the answer with an example in which this rule is applied.
EDIT
I think an example of this would be:
struct S {
operator const int &&();
};
int main(void) {
int && r1 = S().operator const int&&(); // ill-formed
int && r2 = S(); // well-formed
}
Still I need an explanation about why the first initialization is ill-formed while the second is not.
CodePudding user response:
If possible, support the answer with an example in which this rule is applied.
Below is a contrived example showing the same.
struct Custom
{
//conversion operator so that Custom is convertible to int
operator const int&()const{
return i;
}
int i =0;
};
struct AnotherType
{
//conversion operator so that AnotherType is convertible to Custom
operator const Custom&()
{
return c;
}
Custom c;
};
int main()
{
//const int &ref = AnotherType(); //INVALID according to the quoted statement in your question
const int &ref2 = AnotherType().operator const Custom&(); //VALID
}
In the above contrived example, the first statement:
const int &ref = AnotherType(); //this won't work as the conversion from `Custom` to `int` is a user-defined conversion which is not allowed
this won't work because even though AnotherType
can be converted to a Custom
, the next step which involves conversion from Custom
to int
is a user-defined conversion and is not considered according to the quoted statement in your question.
CodePudding user response:
As far as I know, the bolded sentence is redundant. It may have been added out of an abundance of caution.
The predecessor of the quoted paragraph, in C 11, actually required a temporary of type "cv1 T1
" to be copy-initialized from the initializer expression, whereupon the reference to type "cv1 T1
" (that we are trying to initialize) would be bound to that reference. Simple, right? There is no question of how such binding is to occur. The reference simply becomes an alias for that temporary object and we are done.
When CWG1604 was resolved just prior to the publication of C 14, the wording was changed so that, instead of this paragraph requiring the copy-initialization of a temporary of type "cv1 T1
", we instead do the following:
- Determine the user-defined conversion function f that would be used for a hypothetical copy-initialization of a cv1
T1
object from the initializer expression; - Call the function f on the initializer expression. Denote the result of this by x;
- Finally, direct-initialize our reference (call it r) from r.
Because x might not be of type cv1 T1
, a second run through the reference initialization algorithm is required in order to determine how to direct-initialize r from x. The old wording didn't require a second run. The drafters may have been concerned that there would be an edge case where the new wording would require consideration of user-defined conversions in the second run. But I think we can prove that there are no such cases.
We can consider all possible ways in which the copy-initialization of a hypothetical cv1 T1
object could be done by consulting [dcl.init.general]/17, which governs copy-initializations:
- p17.6.1 would not be applicable because if
T1
andT2
are identical, then we would not be coming from [dcl.init.ref]/5.4.1 in the first place. - p17.6.2 would not be applicable because if
T1
is a base class ofT2
, then we would not be coming from [dcl.init.ref]/5.4.1 in the first place. - If p17.6.3 applies, then f might be selected from the converting constructors of
T2
(see [over.match.copy]/1.1). In this case the second run will be trivial: r is bound directly to x. - If p17.6.3 applies and a conversion function of
T2
(possibly inherited) is selected then, as specified by [over.match.copy]/1.2, f must return a type cv3T3
such thatT3
is derived fromT1
. This implies thatT1
is reference-related toT3
, and on the second run, due toT1
being reference-related toT3
, we never reach any of the clauses telling us to consider user-defined conversions. So even without the bolded sentence, user-defined conversions would be irrelevant. - Otherwise, p17.6.4 applies;
T1
is a non-class type, and f is a conversion function ofT2
(possibly inherited). As specified in [over.match.conv]/1.1, f must yield a type (i.e., x must be of a type) that is convertible toT1
by a standard conversion sequence. A standard conversion sequence can never convert a class type to a non-class type, so in this case x is of a non-class type. SinceT1
is of non-class type and x is also of non-class type, during the second run, user-defined conversions will be irrelevant even without the bolded sentence. - The remaining cases in p17.6 deal with cases where
T1
andT2
are both of non-class type, so we would not reach them if we are coming from [dcl.init.ref]/5.4.1.
Note that all references above are to the C 20 standard.
I suspect that the resolution to this issue might have been drafted in a hurry since it was not fixed until after the committee draft had been sent out and it appears that the Canadian NB asked the committee to fix it. The drafters might have decided to just insert the clause about not using user-defined conversions rather than doing an in-depth study to determine whether it is needed. In addition, it seems "cleaner" to avoid even conceptual recursion (i.e. instead of re-running the reference initialization algorithm with different inputs on which the logic of the algorithm guarantees that a further recursive call won't occur, we run a more limited algorithm).