How to Use C++11 Uniform Initialization Syntax

How to use C++11 uniform initialization syntax?

The compile error for

// T t{u};
std::string a;
std::string b{a};

Is a combination of four things

  • The draft until not long ago said that if T has an initializer list constructor (std::string has one, taking char elements), that the initializer list is passed itself as an argument. So the argument to the constructor(s) is not a, but {a}.

  • The draft says that an initializer list initializing a reference is done not by direct binding, but by first constructing a temporary out of the element in the initializer list, and then binding the target reference to that temporary.

  • The draft says that when initializing a reference by an initializer list, when the initialization of the reference is not direct binding, that the conversion sequence is a user defined conversion sequence.

  • The draft says that when passing the initializer list itself when considering constructors of class X as candidates in an overload resolution scenario in a context like the above, then when considering a first constructor parameter of type "reference to cv X" (cv = const / volatile) - in other words highly likely a copy or move constructor, then no user defined conversions are allowed. Otherwise, if such a conversion would be allowed, you could always run in ambiguities, because with list initialization you are not limited to only one nested user defined conversion.

The combination of all the above is that no constructor can be used to take the {a}. The one using initializer_list<char> does not match, and the others using string&& and const string& are forbidden, because they would necessitate user defined conversions for binding their parameter to the {a}.

Note that more recent draft changed the first rule: They say that if no initializer list constructor can take the initializer list, that then the argument list consists of all the elements of the initializer list. With that updated rule, your example code would work fine.

Usage of Uniform Initialization Syntax

In case of simple types, like int in your case, there is no difference. However, initialization of std::vector from STL will be completely different

std::vector<int> v1(3,1); // v1 consists of: 1, 1, 1
std::vector<int> v2{3,1}; // v2 consists of: 3, 1

Have a look at this answer if you want to see why generally brace {} initialization is better, however quoting from Scott Meyer's book Effective Modern C++, which I highly recommend:

[...] So why isn’t this Item
entitled something like “Prefer braced initialization syntax”?
The drawback to braced initialization is the sometimes-surprising behavior that
accompanies it. [...]

Preferred way of initialization in c++11

For something simple, such as the int in your example, I'd agree that

int i=0; 

is probably the most commonly understood (among programmers), but there are advantages to using the brace-initialization that, to me, make it preferable. For instance

int i = 3.99;    // i gets 3; no warning, no error
int i{3.99}; // i gets 3; warning: "narrowing conversion"

It helps to write more bug-free code and is therefore a better way to do it in my view.

Mixing it with auto is more perilous. I typically use auto only for:

  • the temporary variable in a range-for loop (e.g. for (const auto &n : mycollection))
  • to simplify declaration of a named lambda
  • for iterator instances when I use them explicitly (rather than range-for)
  • templated code where doing so avoids creating a lengthy typedef

C++11 uniform initialization: ambiguity between initializer list and multiple-parameter constructors?

it seems like the compiler interprets Foo b {1, 2} as a list
initialization, and calls constructor 2. Is the () syntax the only way
to force the compiler to consider other kinds of constructors when an
initializer-list constructor is present?

Quotes from standard draft explains this well:

9.4.5.2 [dcl.init.list] (emphasis mine):

A constructor is an initializer-list constructor if its first
parameter is of type std​::​initializer_­list or reference to cv
std​::​initializer_­list for some type E, and either there are no
other parameters or else all other parameters have default arguments
([dcl.fct.default]).

[Note 2: Initializer-list constructors are
favored
over other constructors in list-initialization
([over.match.list]). Passing an initializer list as the argument to
the constructor template template C(T) of a class C does not
create an initializer-list constructor, because an initializer list
argument causes the corresponding parameter to be a non-deduced
context ([temp.deduct.call]). — end note]

and 12.4.2.8 [over.match.list]:

When objects of non-aggregate class type T are list-initialized such
that [dcl.init.list] specifies that overload resolution is performed
according to the rules in this subclause or when forming a
list-initialization sequence according to [over.ics.list], overload
resolution selects the constructor in two phases:


  • If the initializer list is not empty or T has no default constructor,
    overload resolution is first performed where the candidate functions
    are the initializer-list constructors
    ([dcl.init.list]) of the class T
    and the argument list consists of the initializer list as a single
    argument.


  • Otherwise, or if no viable initializer-list constructor is found,
    overload resolution is performed again, where the candidate functions
    are all the constructors of the class T
    and the argument list consists
    of the elements of the initializer list.

C++11 Uniform initialization for constructor and operator=

operator= is used for assignment, not initialisation. To provoke that, you'll have to assign to an already existing object.

Table t = {blah, blah};   // initialisation
t = {wibble, wobble}; // assignment

Uniform initialization syntax or type conversion?

First note that what you are doing there is not initialization, is a type conversion followed by an assignment. I strongly recommend C++ casting operators (static_cast in this case) over C casts and these constructor-based castings.

That said, the main difference between uniform initialization and the other is that uniform initialization doesn't allow (See the note) narrowing conversions such these you are doing, float to int. This is helpful when writting constants or initializing variables, since initializing an int with 3.141592654 has no sense at all because the fractional part will be stripped out.

NOTE: I remember the initial proposal for uniform-initialization explicitly stating that it disallows narrowing conversions, so if I had understood it correctly, code like yours should not compile.

I have tested it and seems like compilers emmit warnings about the narrowing conversions instead of aborting compilation. Indeed, that warnings are useful too, and you could allways use a -Werror flag.

Uniform Initialization to call a constructor other than initializer list

This code:

MyClass a2{ (1, 2) };

could not call the 2 argument constructor. What is happening is that in the expression (1,2), the 1 is evaluated before the comma operator, it is discarded, and the result of the expression is 2. This obviously calls the initializer_list constructor.

Enable warnings, e.g. with -Wall and the compiler will tell you about this.


Note that the same thing happens in the vector<int> a{ (1, 2) }; call. The 1 is discarded, and the constructor of vector that takes a single argument is invoked.

Uniform initialization on member initializer list error

This seems like a MSVC bug. The difference is that the struct version is an aggregate, and the class version is not (on account of the default private access specifier).

The class version is value initialized by {}. The struct version is aggregate initialized. A conforming compiler should just list initialize condition_ with {}, because you didn't provide an initializer for it.

But MSVC seems to be stumbling on the fact that members of an aggregate are copy initialized from the corresponding initializer in the initializer list. It seems to check for the copy c'tor, even if it isn't supposed to actually use it.

This is further supported by the fact it knows what to do when an object of the same type is initialized outside of a member initializer list.



Related Topics



Leave a reply



Submit