Why can't I initialize a reference in an initializer list with uniform initialization?
Asked Answered
M

3

33

That is, why does this:

struct S {};

struct T
{
    T(S& s) : s{s} {}

    S& s;
};

int main()
{
    S s;
    T t{s};
}

give me a compiler error with GCC 4.7:

test.cpp: In constructor 'T::T(S&)':
test.cpp:5:18: error: invalid initialization of non-const reference of type 'S&' from an rvalue of type '<brace-enclosed initializer list>'

?

To fix the error, I have to change the s{s} to s(s). Doesn't this break the, erm, uniformity of uniform initialization?

EDIT: I tried with clang, and clang accepts it, so perhaps it's a GCC bug?

Milan answered 9/5, 2012 at 3:54 Comment(7)
When asking C++11 questions I would include the compiler and version just in case. Not all compilers support all features, and I would not be surprised to find quirksCotter
@DavidRodríguez-dribeas: good point, added in compiler versionMilan
you cannot initialize a named reference from a value reference(initialization list). If you need the c++ standereze on this I could point you to a pdf and page number. Also, to note, the refernce , your reference object, which would properly be &S, goes on only a right hand of an assignment. And No it doesnt not brake unifority of uniform initizaion, initlizier lists are always variablename(initlizier) and comma seperated. Last veriable, no commaOpia
@johnathon: I would actually want that reference, as I understand that you can do it. I have added an answer with what I believe are the appropriate quotes.Cotter
@DavidRodríguez-dribeas page 273.. read on.Opia
@johnathon: The quote in my answer is from n3337, but looking back at n3290 (where page 273 makes sense --BTW, it is usually better to quote the section/paragraph than a page) you can find this 8.5.4/3 Otherwise, if the initializer list has a single element, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed. which is the case at hand.Cotter
@johnathon: Admittedly, that comes right after Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary, so if you take the text in order it is not allowed, but this is clearly an editorial error (i.e. the object or reference in the latter paragraph makes no sense unless the intention is that for lvalues that last paragraph applied).Cotter
I
22

Yes, its a bug. This is something new and was voted in the working paper in February 2012 (link).

Nicol Bolas makes a good point in that gcc is actually the conforming compiler according to the FDIS approved C++11 standard because the changes to the working paper were made after that.

Interweave answered 9/5, 2012 at 4:22 Comment(6)
please sir, tell me where in that working paper, in that section mentioned in that link, that says you can bind a braced initlizier list to a reference?Opia
OR, where it says braced initlizer lists can be used IN inlitlizer lists of the implementation of a constrcutorOpia
— as the initializer in a variable definition (8.5) — as the initializer in a new expression (5.3.4) — in a return statement (6.6.3) — as a function argument (5.2.2) — as a subscript (5.2.1) — as an argument to a constructor invocation (8.5, 5.2.3) — as an initializer for a non-static data member (9.2) — in a mem-initializer (12.6.2) — on the right-hand side of an assignment (5.17)Opia
@johnathon: The link is to DR 1288, which describe the changes made (as quoted by David Rodríguez - dribeas). In your comment you also provided the answer in a mem-initializer (12.6.2) from the standard.Interweave
C++11 is so named because it was approved in 2011. Changes made after then do not apply, even if they are defects. So if it's not in the actual C++11 standard, then the compiler should reject it. It's not a bug to do so; it's a bug to accept it.Satan
@NicolBolas: I'm not sure that what you say reflects the concensus in the compiler community. Compilers routinely implement fixes to defect reports and treat them as if it had been part of the standard. After all, that's what the Standard Committee means by labelling a change a defect report - something that should have been in the standard.Milan
A
9

I believe that to be an error in the compiler. The two paragraphs that deal with reference initialization through list-initialization are (in n3337):

§8.5.4/3

List-initialization of an object or reference of type T is defined as follows:

  • Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.

  • Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary. [ Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. — end note ]

The compiler seems to be applying the last paragraph, when it should be applying the first, as reference-related is defined as

8.5.3/4

Given types “cv1 T1” and “cv2 T2,” “cv1 T1” is reference-related to “cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2.

In the case of the question, the types of the reference and the initializer inside the brace-initialization-list are exactly the same, which means that the initialization should be valid.


In the FDIS draft, the equivalent paragraphs had the order reversed. The implication is that the FDIS draft (n3290) did not allow for brace-list-initialization of *lvalue*s. On the other hand, reading the text it seems obvious that it is a bug in the standard and that the intention was having the order of n3337:

  • Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary.

  • Otherwise, if the initializer list has a single element, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.

The order in that document means that because all reference types are handled by the first clause, mentioning reference in the following paragraph would make no sense.

Augmented answered 9/5, 2012 at 4:13 Comment(0)
M
6

(Note: I'm writing this answer with the benefit of 2 years of hindsight since the original question; and to put some of the information from comments into an actual answer so that it is searchable).


Of course, initializing a reference of type S& with a reference also of type S& is supposed to bind directly.

The problem is a defect in the C++11 standard and was addressed by DR1288. The corrected text appears in C++14.

The Committee has clarified that the corrected text is what was intended for C++11, and so a "conforming compiler" should implement the corrected version.

g++ 4.8 followed the published text of the C++11 standard; however once this issue came to light, g++ 4.9 implemented the corrected version, even with -std=c++11 switch.

Note that the problem is not confined to constructor initializer lists either, e.g.: S s; S &t{s}; doesn't work in g++ 4.8, nor does S s; S &t = s; S &u { t };

Macaulay answered 11/11, 2014 at 5:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.