struct A
{
A(int) { }
};
A&& a = 7;
Going through this once more, I just realized that the cross reference to the whole of [dcl.init] in the existing text does include [dcl.init]/17.6.3, which defines what the result of the conversion function is in case of a constructor. A bit obscure, but consistent, so my proposed change doesn't seem so useful after all.
A&& a{ A(7)};
[dcl.init]/(17.6.3) doesn't apply when the destination type is a reference type. See (17.2).
In reference to your example, the initialization is first handled by (5.2.2.1). Then, the algorithm starts from the beginning to direct-initialize the reference as if using:
A&& a{ A(7)};which is then handled by (5.2.1.1).
That's the gist of it, yes, but without the braces - that would introduce the additional step in [dcl.init.list]/3.7.
Anyway, we're discussing the first step, which you agree is handled by 5.2.2.1.
It does apply when 5.2.2.1 says
[...] user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion (8.6, 13.3.1.4, 13.3.1.5) [...]
struct A
{
A(int) { }
private:
A(const A&);
};
A&& a = 7;
I don't understand which additional step are you talking about here. The reference is just direct-initialized with the expression A(7), with, or without braces.
On Tuesday, August 16, 2016 at 12:56:41 PM UTC-3, bogdan wrote:
It does apply when 5.2.2.1 says
[...] user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion (8.6, 13.3.1.4, 13.3.1.5) [...]I don't understand the reason why you insist [dcl.init]/(17.6.3) would apply here. Note that this code is well-formed:
The reason is one line above, in the quote.
We're analyzing A&& a = 7;:
- We go to [dcl.init.ref]/5.2.2.1; it tells us to consider the following invented non-reference copy-initialization: A a = 7. We're considering it, we're not going through with the initialization of an object of type A. We pretend we're doing it, in order to determine the user-defined conversion to apply to 7, but while pretending we still have to follow the rules.
- Where are the rules for that non-reference initialization? [dcl.init]/17.6.3.
- Which sends us to [over.match.copy], which chooses the constructor.
- Back to 17.6.3, which specifies how that constructor call is seen as a prvalue expresssion of type A.
- Back to 5.2.2.1, which tells us that the synthesized prvalue ("the result of the call to the conversion function") is used to direct-initialize the reference.
struct A
{
A(int) { }
private:
A(const A&);
};
A&& a = 7;
Regarding the well-formedness of your last example, yes, as far as I can tell, that initialization is ill-formed in C++14 and compilers are wrong to accept it. I guess they don't implement DR1604 in its entirety.
Note that in C++17 the code is well-formed, as the non-reference copy-initialization no longer involves the copy constructor (guaranteed copy elision).
struct X {
X(int) {}
X(X const &) = delete;
};
void f() {
X const &x = 0;
}
struct A
{
explicit A(int) { }
};
A&& a(7);
struct A
{
explicit operator int() { return 7; }
};
long&& i(A{});
Using copy-initialization in there does matter, and compilers definitely don't ignore that. Example:
struct A
{
explicit A(int) { }
};
A&& a(7);
All four compilers that I tested reject this, which is consistent with the intuitive meaning of explicit in such a declaration: if you want an object of type A to be constructed from an int, you have to explicitly request it.
The same goes for conversion functions that are used in the context of 5.2.2.1:
struct A
{
explicit operator int() { return 7; }
};
long&& i(A{});
Unanimously rejected as well.
Usefulness of the change notwithstanding, there's a question about cv-qualification: in the constructor case, the current specification will eventually bind the reference to a temporary of type T1, not cv1 T1. This is inconsistent with the case when T1 is a non-class type, where 5.2.2.2 (when it works correctly) creates a temporary of type cv1 T1. I think for indirect binding it makes sense that the type of the reference dictates the type of the temporary, cv-qualification included, in all cases. What is the intended behaviour in this case?
struct A
{
operator volatile int&();
};
const int& a1 = {A{}.operator volatile int&()}; // ill-formed
const int& a2 = {A{}}; // well-formed