10

I was playing with some useless code to understand initialization of member references, and bumped into this:

struct A {};

struct B
{
    B() : a()
    {
    }

    const A& a;
};

The code above gives the following error when compiled with gcc 4.9.2:

In constructor 'B::B()':
error: value-initialization of reference type 'const A&'
  B() : a()

Which I understand.

But if I use uniform initialization in B's constructor's initializer list, like so:

struct A {};

struct B
{
    B() : a{}
    {
    }

    const A& a;
};

It compiles fine.

So the question is, why does the use of uniform initialization here change the compilation result?

I also tried this with Microsoft Visual C++ 2013. It does not compile either version of the code, with the same error message:

Error 3 error C2440: 'initializing' : cannot convert from 'int' to 'const A & 

You can have a quick play with it here:

http://ideone.com/7f2t8I

leemes
  • 44,967
  • 21
  • 135
  • 183
Hugo
  • 155
  • 1
  • 6
  • 1
    Uniform initialization is a relatively new feature, and you have to expect some compiler bugs with it. In this case, without looking it up, I'd guess that the Microsoft compiler is correct. – James Kanze Feb 25 '15 at 11:36
  • 2
    My guess: `{}` is interpreted as a call to the default constructor of `A` (like if you did `A a {};`), so a temporary `A{}` is created. It is then bound to the const-ref and its lifetime is extended (like if you did `const A & a {}`, which also works in GCC). But I don't know what the standard says about that; if it is allowed... What really confuses me is that MSVC sees an `int` which I cannot see... Did you really compile this code? – leemes Feb 25 '15 at 11:50
  • @JamesKanze I understand that, but I didn't want to just go out and shout compiler bug. Maybe there is something about uniform initialization that I don't know and that would make it behave differently in this situation. – Hugo Feb 25 '15 at 11:52
  • @leemes MSVC often uses default ints in its error messages. I have no idea whether this is one of those cases though. – T.C. Feb 25 '15 at 11:54
  • @leemes GCC indeed calls the default constructor and binds the result to the `const` reference. At least, it does so when I add a data member to `A` that I set in `A`'s default constructor. – 5gon12eder Feb 25 '15 at 11:56
  • http://stackoverflow.com/questions/6546470/uniform-initialization-of-references seems it is allowed by the standard, but the result is kinda useless and probably dangerous because the object is gone but the reference will still be there. – thang Feb 25 '15 at 11:57
  • @thang A temporary bound to a `const` reference will live as long as the reference it is bound to. – 5gon12eder Feb 25 '15 at 11:58
  • @leemes Yes, I did really compile this code. You guess is the same as mine as to why it works with uniform initialization. Having said that, I would expect it not to work, because 'a' is not an A, is a const A&. – Hugo Feb 25 '15 at 11:59
  • 2
    @5gon12eder, that's what I thought until I saw this http://stackoverflow.com/questions/2784262/does-a-const-reference-prolong-the-life-of-a-temporary. see the quote from the standard that says: **A temporary bound to a reference member in a constructor’s ctor-initializer (§12.6.2 [class.base.init]) persists until the constructor exits**. Not exactly sure how to interpret that. – thang Feb 25 '15 at 12:01
  • @thang It was a defect in the standard that has been fixed ([I was told](http://stackoverflow.com/q/25561387/1392132)). – 5gon12eder Feb 25 '15 at 12:02
  • @5gon12eder, that link has nothing about when the standard was fixed. It's just a gcc implementation issue. Do you know when the standard was changed with respect to this issue? Also,in the bug report, someone says "G++ is actually correct according to wording in the C++11 FDIS (see 8.5.4 paragraphs 5 and 6)". I think that is correct. I think the warning is right. You are allowed to do it, but it is dangerous, unless the standard has changed. I am very interested in when that happened... I ran into this problem before. – thang Feb 25 '15 at 12:05
  • @thang See Jonathan Wakely's comment to the accepted answer. However, as I think about it, I'm not exactly sure that this is the same rule that applies here. – 5gon12eder Feb 25 '15 at 12:07
  • @Hugo If two compilers do different things, and it's not unspecified, implementation dependent, or undefined behavior, one has a bug. Unless, which is possibly the case here, they are implementing different versions of the standard; one of the answers suggests that there is a change in C++14 which affects the legality of the second version. – James Kanze Feb 25 '15 at 12:07
  • @5gon12eder, DR 1288 seems to be only marginally related. It doesn't address the constructor initialization (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1288). – thang Feb 25 '15 at 12:13
  • 1
    @5gon12eder The standard used to say that list-initialization for references always created a temporary and never did direct binding. That is the cause of the GCC problem in the linked question (it makes no sense to create a temporary when the reference can be bound directly) and was fixed by CWG1288. The temporary lifetime issue is distinct. – T.C. Feb 25 '15 at 12:40

1 Answers1

7

GCC is correct in its interpretation of {}. [dcl.init.list]/p3.8-9 (quoting N4296; earlier drafts has the same relative ordering of these two bullets):

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

  • [7 inapplicable bullets omitted]

  • Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized or direct-list-initialized, depending on the kind of initialization for the reference, 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 ]

  • Otherwise, if the initializer list has no elements, the object is value-initialized.

List-initializing the reference hits bullet 3.8, causing the construction of a temporary. The value-initialization case, in 3.9, doesn't apply.

Value-initialization of a reference is ill-formed ([dcl.init]/p9):

A program that calls for default-initialization or value-initialization of an entity of reference type is ill-formed.


However, as of N4296, per [class.base.init]/p8:

A temporary expression bound to a reference member in a mem-initializer is ill-formed.

This was added as a result of CWG issue 1696, which is a DR (defect report) against C++14.

Pre-CWG1696, the standard provided that (N4140 [class.temporary]/p5.1):

A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.

which means that the reference will become dangling immediately after construction. This presumably motivated CWG1696's decision to disallow such bindings altogether.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • I assume N4296 is C++14. So they've finally corrected something which has been a problem (IMHO) since C++98. – James Kanze Feb 25 '15 at 12:04
  • @JamesKanze N4140 is C++14. CWG 1696 is a DR against C++14, however, so should still be implemented by compilers in C++14 mode. – T.C. Feb 25 '15 at 12:06
  • Does any compiler have a fully compliant C++14 mode? (For that matter, does any have a fully compliant C++11 mode?) – James Kanze Feb 25 '15 at 12:12
  • @JamesKanze and T.C. Thank you both for you comments and answers. I appreciate the difference between the two kinds of initialization in question now. From this answer I am also lead to believe that gcc 4.9.2 is not compliant with the standard regarding N4296 [class.base.init]/p8. – Hugo Feb 25 '15 at 14:14
  • @Hugo That was just added last November :) – T.C. Feb 25 '15 at 14:18
  • @Hugo Regarding non-compliance: it may just be an issue of the compilers targetting different versions of the standard. (And since T.C.s answer now covers everything I mentionned in my, and is far more complete and accurate, I'll delete mine.) – James Kanze Feb 25 '15 at 14:49