Navigation



B.4 — Initializer lists and uniform initialization

Initializer lists

C++03 has partial support for initializer lists, allowing you to use initializer lists for simple aggregate data types (structs and C-style arrays):

struct Employee
{
    int nID;
    int nAge;
    float fWage;
};

Employee sJoe = {1, 42, 60000.0f};
int anArray[5] = { 3, 2, 7, 5, 8 };

However, this doesn’t work for classes, as classes must be initialized via constructors using the function call syntax. This leads to the following inconsistency:

int anArray[5] = { 3, 2, 7, 5, 8 }; // ok
std::vector<int> vArray[5] = {3, 2, 7, 5, 8}; // not okay in C++03

C++11 extends initializer lists so they can be used for all classes. This is done via a new template class called std::initializer_list, which is part of the <initializ_list> header file. If you use an initializer list on a class, C++11 will look for a constructor with a parameter of type std::initializer_list.

std::vector<int> vArray[5] = {3, 2, 7, 5, 8}; // calls constructor std::vector<int>(std::initializer_list<int>);

All of the relevant standard library classes in C++11 have been updated to accept initializer lists, so you can start using them immediately (assuming your compiler supports them — as of the time of writing, Visual Studio 2010 does not).

You can also add initializer_list constructors to your own classes, and use an iterator to step through the members of the initializer list:

#include <vector>
#include <initializer_list>

using namespace std;

template <typename T>
class MyArray
{
private:
    vector<T> m_Array;

public:
    MyArray() { }

    MyArray(const initializer_list<T>& il)
    {
        // Manually populate the elements of the array from initializer_list x
        for (auto x: il) // use range-based for statement to iterate over the elements of the initializer list
            m_Array.push_back(x); // push them into the array manually
    }
};

int main()
{
    MyArray<int> foo = { 3, 4, 6, 9 };
    return 0;
}

Because std::vector has an initializer_list constructor in C++11, we could also have let the vector constructor handle initialization itself:

    MyArray(const std::initializer_list<T>& x): m_Array(x) // let vector constructor handle population of mArray
    {
    }

Note that because initializer_list has iterator functions begin() and end(), we can use the new range-based for statement to iterate through them.

A few oddities to note: while initializer_list supports the iterator functions begin() and end(), it doesn’t support const interator functions cbegin() and cend(). Initializer_list also doesn’t provide direct random access to data members via operator[].

You can also use initializer lists a function parameters, and access the elements in the same way:

int sum(const initializer_list<int> &il)
{
    int nSum = 0;
    for (auto x: il) // use range-based for statement to iterate over the elements of the initializer list
        nSum += x;
    return nsum;
}

cout << sum( { 3, 4, 6, 9 } );

Uniform initialization

As noted above, C++03 is inconsistent in how it lets you initialize different types of data. Initializer lists go a long way to helping making initialization of data more consistent. However, C++11 has one more trick up its sleeve called uniform initialization. Unlike initializer lists, which take the form:

type variable = { data, elements };

The uniform initialization syntax takes the following form:

type variable { data, elements }; // note: no assignment operator

This style of initialization will work for both plain aggregate data types (structs and C-style arrays) and classes. For classes, the following rules are observed:

  • If there is an initialization_list constructor of the appropriate type, that constructor is used
  • Otherwise the class elements are initialized using the appropriate constructor

For example:

class MyStruct
{
private:
    int m_nX;
    float m_nY;

public:
    MyStruct(int x, float y): m_nX(x), m_nY(y) {};
};

MyStruct foo {2, 3.5f};

Since MyStruct does not have an initializer_list constructor, it will next check to see if there’s a constructor that takes parameters of type (int, float). MyStruct does, so that constructor is called.

Although it may initially seem like the uniform initialization syntax is always preferable to the standard constructor syntax, there are cases where the two can provide different results:

std::vector<int> v1(8); // creates an empty vector of size 8, using the int constructor
std::vector<int> v1{8}; // creates a one-element vector with data value 8, using the initializer_list constructor

This happens because the initializer_list constructor takes precedence over other constructors when doing uniform initialization.

You can also use the uniform initialization syntax when calling or return values in functions:

void useMyStruct(MyStruct x)
{
}

useMyStruct({2, 3.5f}); // use uniform initialization to create a MyStruct implicitly

MyStruct makeMyStruct(void)
{
    return {2, 3.5f}; // use uniform initialization to create a MyStruct implicitly
}

Initializer lists vs initialization lists

The choice of the name “initializer list” is unfortunate, as it’s very easy to get confused with the “initialization list”, which is a similar concept. Here’s the difference:

An initialization list is used to do implicit assignments to class variables as part of a constructor:

    MyStruct(int x, float y): m_nX(x), m_nY(y) {}; // m_nX and m_nY are part of the initialization list

An initializer list is a list of initializers inside brackets ( { } ) that can be used to initialize simple aggregate data types and classes that implement std::initializer_list:

std::vector<int> vArray[5] = {3, 2, 7, 5, 8}; // vArray initialized using an initializer_list
B.5 — Delegating constructors
Index
B.3 — Range-based for statements and static_assert

4 comments to B.4 — Initializer lists and uniform initialization

You must be logged in to post a comment.