-
Notifications
You must be signed in to change notification settings - Fork 7
Description
Full name of submitter: Anoop Rana
Reference (section label): [dcl.init.ref]
Link to reflector thread (if any): https://stackoverflow.com/questions/78867403/reference-initialization-using-list-initialization-accepted-by-gcc-and-msvc-but
Issue description:
There are two main issues described here. First is that dcl.init.ref#5.4.1.sentence-2 doesn't specify if the reference is direct initialized using ()
or using {}
. This matters because X&& ref{z};
form is accepted by three compilers while X&& r = z;
is rejected by all. The second issue is that we should make X&& r =z;
and X&&{z};
be more consistent and ill-formed.
So to make the issue clearer, consider the following example: Demo
struct X
{
public:
X(){}
};
struct Y : X
{
};
struct Z
{
public:
operator const Y () const
{
return {};
}
};
int main()
{
Z z;
// X&& r = z; // #1: All four rejects this
X&& ref{z}; //#2: Clang: Nope, gcc: Ok, msvc: Ok, Edg: Ok
}
First we see how/why #1
is almost ill-formed as per the current wording. I say "almost" because [dcl.init.ref#5.4.1] doesn't specify if ()
or {}
form is used for the direct initialization. And this might make a difference as already seen from X&& ref{z};
which is accepted by 3 compilers but not clang.
So to understand #1
we go to dcl.init.ref:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
- [...]
- [...]
- [...]
- Otherwise, T1 shall not be reference-related to T2.
- If T1 or T2 is a class type, 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.
This means that the result of the conversion function which will be of type const Y
will be used to direct initialize r
.
So the initialization process will be repeated again. This time we first go to direct initialization:
If the destination type is a reference type, see [dcl.init.ref].
So now come to dcl.init.ref and see that none of the bullet points is applicable for initialization of the reference r
from const Y
.
In particular, 5.1 is not applicable because the reference is not lvalue reference.
Similarly 5.2 is not applicable.
5.3 is also not applicable.
5.4 is not applicable because const Y
is reference related to X
as X
is a base.
So none of the bullet points are applicable for the direct initialization of r
by const Y
and so X&& r = z;
is ill-formed.
The important thing here is that here we didn't go to dcl.init#general-16.1 but instead to dcl.init#general-16.2. But the dcl.init.ref#5.4.1.sentence-2 currently doesn't specify that {}
form of direct initialization is not used.
We can solve this first issue by changing "direct initialization" to "direct-non-list-initialization" or directly saying ()
form is used instead of {}
.
Additionally, there might also be another issue(that of consistency) between not allowing X&& r = z;
but allowing X&& ref{z};
. That should also be fixed(if any) by additional wording changes(apart from what is suggested here).
Suggested Resolution
Change dcl.init.ref#5.4.1.sentence-2 to as highlighted below in bold:
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 using direct-non-list-initialization. For this direct-initialization, user-defined conversions are not considered.