List Initialization

One of the new features in C++0x has been to make a consistent mechanism for initialization via a list. In previous versions of C++, it was inconsistent how you would initialize lists which would lead to a small amount of confusion. For instance:

int foo[] = { 1, 2, 3, 4 };  // OK
std::vector bar = { 1, 2, 3, 4 }; // Error
std::vector baz( 1, 2, 3, 4 );  // OK
int quux[]( 1, 2, 3, 4 );  // Error

Sometimes you could use list of values in braces, sometimes you couldn’t. Sometimes you could use a constructor, sometimes you couldn’t. C++0x has rectified this issue as part of the push towards more uniform initialization. Now you can use braced initializer lists everywhere.

A valid braced initializer list is a list of values of the same type where no narrowing conversions must take place. So, for instance { 1, 2, 3, 4 } is a braced initializer list of unsigned integers, and { 1.0, 2.0, 3.0 } is a braced initializer list of doubles. However, if there are conversions required to make the types similar amongst the list members, they can only be widening conversions. So { 1, 2.0, 3.0 } could be valid, or it could be invalid, depending on the context of its use.

int array1 = { 1, 2.0, 3.0 };  // Not OK
double array2 = { 1, 2.0, 3.0 }; // OK

The declaration of array1 would require the 2.0 and 3.0 to be narrowed, which is not allowed. But the declaration of array2 would require 1 to be widened to 1.0, which is allowed.

Braced initializer lists are not really magic (very little about a language usually is!). They are converted to a std::initializer_list< T >, and then either copy construction or value construction takes over. This is an important piece to understand when designing your own classes. If you wish to allow your class to be initialized with a list of values, you should create a constructor that takes a std::initializer_list. So, for example, with the std::vector from above, it now contains a new constructor:

template <typename T >
vector::vector( const std::initializer_list< T > &list );

This constructor allows the vector to be initialized with the list of values given by the braced initializer list.

Braced initializers aren’t only for lists, however. You can still use them with aggregates like you’ve always been able to. But now that you can use them for lists, they’ve become more powerful. Let’s say you have a map of integers to strings. Previously, to initialize that list, you had to do so manually. But now you can use list initalizers coupled with aggregate initializers to do it inline:

// Previously
std::map< int, std::string > m1;
m1[ 2 ] = "foo";
m1[ 5 ] = "bar";
m1[ -12 ] = "baz";

// Currently
std::map< int, std::string > m2 = { { 2, "foo" }, { 5, "bar" }, { -12, "baz" } };

The inner braces are using aggregate initialization to make std::pair< int, std::string > objects, and the outer braces define a std::initializer_list< std::pair< int, std::string > >.

Initializer lists can be used for more than just assignments. You can use them in return statements, function call arguments, subscripts for custom operator[] overloads, initialization of static class member variables, and more.

One interesting side-effect to list initialization happens with the auto keyword. You cannot use list initializations to declare an array with automatic type inference. For instance:

auto i = { 1, 2, 3 }; // OK, but does not create an int[]
auto i2[] = { 1, 2, 3 }; // Not OK

In the first example, i is a std::initializer_list< int >, and not an array of integers. The second example poses an interesting question: why can’t this work? After all, you can do:

int i3[] = { 1, 2, 3 };  // OK

The specification does not call this case out explicitly, and so I am taking a guess at this. But, I believe the reason this doesn’t work is because braced initializers will call user-defined constructors. So you could be making an array of ints, or an array of something else which happens to have a constructor which takes an int. This brings up an interesting question of what happens when you do:

auto i = { 0 };

You get a std::initializer_list< int >, which is to be expected. So the answer to why you can’t do auto[] with an initializer list is: there’s no way to induct what you actually want. You get a std::initializer_list< auto > which could go any number of ways. So the compiler disallows it.

The only remaining thing to talk about when dealing with braced initializer lists is the fact that you need to #include initializer_list into any file using braced initalizers. Without doing that, the compiler will complain about not knowing what an initializer list is. This is called out explicitly by the spec in Section 8.5.4 Clause 2.

tl;dr: You can now use braced initializer lists to initialize more than just scalar arrays a la the std::initializer_list class.

This entry was posted in C/C++ and tagged , . Bookmark the permalink.

One Response to List Initialization

  1. Priya says:

    Проблема в том, что транслятор трактует строку `friend std::string ::covenrt()’ как `friend std::string::covenrt()’, что очеведно не то что было задумано. В голову приходит что-то типа такого решения (при условии, что существующий дизайн изменять нельзя и в пространстве имен `tools’ также может оказаться объявление функции `std::string covenrt()’):std::string covenrt();// …namespace tools { namespace global_decl { using ::covenrt; } class Numeric { // … friend std::string global_decl::covenrt(); };}

Leave a Reply

Your email address will not be published. Required fields are marked *