Why Can't I Initialize a Reference in an Initializer List with Uniform Initialization

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

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.

Initialization of const reference member in initializer list

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.

Why doesn't this work? (brace-initialization of references)

The first thing to notice is that the initializer {1, "t"} is using brace elision to initialize the sub-aggregate A.c which means that the literal "t" is taken to directly initialize the internal array that std::array holds. In this case, this array would look like char data[12].

This reduces to say that we are initializing a reference to const A with a brace-list containing an element that initializes a member array.

This will be somewhat equivalent to:

struct S {
char const data[2];
};
S const& s = {"t"}; // fail for gcc

GCC has already a bug report on that.

You've already provided a workaround in the comment section. Just initialize the reference as:

const A& ar = A{1, "t"}

Can't initialize an object in a member initialization list

The int is coming from the fact that CTypedPtrMap has a constructor that takes an int argument that is defaulted to 10.

The real problem that you're running into is that the m_calcMap reference initalization you have there is trying to default construct a temporary CTypedPtrMap object to bind the reference to. However, only const references can be bound to temporary objects. No doubt the error message is not very informative.

But even if the m_calcMap member were a const refernce, you'd still have a problem binding it to a temporary. in this case, the MSVC 2008 compiler gives a pretty clear warning:

mfctest.cpp(72) : warning C4413: '' : reference member is initialized to a temporary 
that doesn't persist after the constructor exits

Uniform Initialization inside constructor/function declaration parameter list

It's a grammatical constraint. This specific grammar element is described in [dcl.fct] ¶3:


parameter-declaration-clause:
parameter-declaration-listopt ...opt
parameter-declaration-list , ...

parameter-declaration-list:
parameter-declaration
parameter-declaration-list , parameter-declaration

parameter-declaration:
attribute-specifier-seqopt decl-specifier-seq declarator
attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause
attribute-specifier-seqopt decl-specifier-seq abstract-declaratoropt
attribute-specifier-seqopt decl-specifier-seq abstract-declaratoropt = initializer-clause

The thing to note is that the grammar permits an initializer-clause to be present only if it's preceded by a =. I suspect it's to ensure there's no ambiguity between function declarations and object declarations. For instance, in block scope:

Widget foo(CompA{}, CompB{});

Has to be an object declaration, like the uniform initialization proposal wanted to make sure. Allowing plain {} as a default argument would make the above ambiguous, and we'll just have another vexing parse to add to the collection. So a = is required.

Now, as for why not allow it in a constructor where ambiguity is unlikely: the standard committee is not really in the habit of allowing a very constrained use-case if the more general one cannot be supported.

Uniform initializer used in default argument to const reference

It is valid in C++11, but it was a very late addition to the working paper that Bjarne put through. So it's not surprising that GCC doesn't support brace default arguments yet.

c++ initializer list issue - error: attempting to reference a deleted function

In the first variant, where you don't have a User default constructor, the compiler will not create a default constructor for you. That means there is no way to default-construct (like you do in the ofApp class) an object of the User class.

There are two ways of solving the problem: The first one you already know and that is to create a default constructor. As a variant of this you could use the compilers default constructor by using

class User {
...
User() = default;
...
};

The other solution is to use default arguments for the other constructor, so it can be invoked without arguments like a default constructor:

class User {
...
User(ICoordinateMapper coordinateMapper = nullptr)
: _coordMapper(coordinateMapper){}
...
};

I would recommend the second way, as it will initialize the _coordMapper member.



Related Topics



Leave a reply



Submit