Search

10.7 — std::initializer_list

Consider a fixed array of integers in C++:

If we want to initialize this array with values, we can do so directly via the initializer list syntax:

This prints:

5 4 3 2 1

This also works for dynamically allocated arrays:

In the previous lesson, we introduced the concept of container classes, and showed an example of an IntArray class that holds an array of integers:

What happens if we try to use an initializer list with this container class?

This code won’t compile, because the IntArray class doesn’t have a constructor that knows what to do with an initializer list. As a result, we’re left initializing our array elements individually:

That’s not so great.

Prior to C++11, list initialization could only be used with static or dynamic arrays. However, as of C++11, we now have a solution to this problem.

Class initialization using std::initializer_list

When a C++11 compiler sees an initializer list, it automatically convert it into an object of type std::initializer_list. Therefore, if we create a constructor that takes a std::initializer_list parameter, we can create objects using the initializer list as an input.

std::initializer_list lives in the <initializer_list> header.

There are a few things to know about std::initializer_list. Much like std::array or std::vector, you have to tell std::initializer_list what type of data the list holds using angled brackets. Therefore, you’ll never see a plain std::initializer_list. Instead, you’ll see something like std::initializer_list<int> or std::initializer_list<std::string>.

Second, std::initializer_list has a (misnamed) size() function which returns the number of elements in the list. This is useful when we need to know the length of the list passed in.

Let’s take a look at updating our IntArray class with a constructor that takes a std::initializer_list.

This produces the expected result:

5 4 3 2 1

It works! Now, let’s explore this in more detail.

Here’s our IntArray constructor that takes a std::initializer_list<int>.

On line 1: As noted above, we have to use angled brackets to denote what type of element we expect inside the list. In this case, because this is an IntArray, we’d expect the list to be filled with int. Note that we also pass the list by const reference, so we don’t make an unnecessary copy of the std::initializer_list when it’s passed to our constructor.

On line 2: We delegate allocating memory for the IntArray to the other constructor via a delegating constructor (to reduce redundant code). This other constructor needs to know the length of the array, so we pass it list.size(), which contains the number of elements in the list.

The body of the constructor is reserved for copying the elements from the list into our IntArray class. For some inexplicable reason, std::initializer_list does not provide access to the elements of the list via subscripting (operator[]). The omission has been noted many times and never addressed.

However, there are easy ways to work around the lack of subscripts. The easiest way is to use a for-each loop here. The for-each loops steps through each element of the initialization list, and we can manually copy the elements into our internal array.

Class assignment using std::initializer_list

You can also use std::initializer_list to assign new values to a class by overriding the assignment operator to take a std::initializer_list parameter. This work analogously to the above. We’ll show an example of how to do this in the quiz solution below.

Note that if you implement a constructor that takes a std::initializer_list, you should ensure you do at least one of the following:

  1. Provide an overloaded list assignment operator
  2. Provide a proper deep-copying copy assignment operator
  3. Make the constructor explicit, so it can’t be used for implicit conversions

Here’s why: consider the above class (which doesn’t have an overloaded list assignment or a copy assignment), along with following statement:

First, the compiler will note that an assignment function taking a std::initializer_list doesn’t exist. Next it will look for other assignment functions it could use, and discover the implicitly provided copy assignment operator. However, this function can only be used if it can convert the initializer list into an IntArray. Because our constructor that takes a std::initializer_list isn’t marked as explicit, the compiler will use the list constructor to convert the initializer list into an IntArray. Then it will call the implicit assignment operator. And this implicit assignment operator will shallow copy the temporary array into our IntArray object. You can already see where this is going.

At the end of the assignment, the initializer list (because it is an anonymous object) goes out of scope, and our IntArray is left with a hanging pointer. When the IntArray goes out of scope, it will try to delete the hanging pointer, leading to undefined results (probably a crash).

Rule: If you provide list construction, it’s a good idea to provide list assignment as well.

Summary

Implementing a constructor that takes a std::initializer_list parameter (by reference to prevent copying) allows us to use list initialization with our custom classes. We can also use std::initializer_list to implement other functions that need to use an initializer list, such as an assignment operator.

Quiz time

1) Using the IntArray class above, implement an overloaded assignment operator that takes an initializer list.

The following code should run:

This should print:

5 4 3 2 1 
1 3 5 7 9 11

Show Solution

10.x -- Chapter 10 comprehensive quiz
Index
10.6 -- Container classes

15 comments to 10.7 — std::initializer_list

  • Erik

    When I try to use this, I get an error saying there is no member named initializer_list in std. I am doing "#include <initializer_list>", so I don’t think that’s the problem, but I don’t know what else it could be.

  • Jacco

    Hi Alex,

    I used the following code for overloading the assignment operator which worked fine as well:

    Thx for all your efforts, great learning, sometimes I struggle loading the website…

  • Rohit

    Why did u write m_data = 0 instead of m_data = nullptr here:
    void erase()
        {
            delete[] m_data;
            // We need to make sure we set m_data to 0 here, otherwise it will
            // be left pointing at deallocated memory!
            m_data = 0;
            m_length = 0;
        }

    and whats the difference in delete m_data    and      delete[] m_data? I want to know the difference in
    delete m_data;

    // reallocate array
    m_length = list.size();
    m_data = new int[m_length];
    }

    and

    delete[] m_data;
    // We need to make sure we set m_data to 0 here, otherwise it will
    // be left pointing at deallocated memory!
    m_data = 0;
    m_length = 0;

    • Alex

      0 and nullptr are synonymous in this context. Using nullptr is probably slightly better, but I wrote these examples before nullptr existed and din’t update them.

      delete m_data assumes m_data is not an array. delete[] m_data assumes m_data is an array. You need to call the right version of delete depending on whether m_data is an array or not.

  • Hugh Mungus

    Hey Alex,

    This is my initial solution for the quiz. Can you tell me why it doesn’t work? I run into runtime errors and it appears to be accessing memory it doesn’t own.

  • Hugh Mungus

    std::initializer_list lives in the header.

    Was that line a typo?

  • Terty

    Regarding the following code:

    Assume we use the following to create an IntArray object:

    Is the initializer_list not an anonymous object, if it is, wouldn’t assigning the array elements of the IntArray object to references to the values in the initializer_list cause problems once the constructor is finished and the initializer_list gets destroyed?

    • Terty

      I think I understand now, since m_data holds int values and not int& values the values of the elements get copied from the initializer_list even if they are references.

      • Alex

        Yes -- but when you say “even if they are references”, the only reference here is m_list, which is a const reference to the temporary initializer list argument. The elements of the initializer list are actual values, and the elements of m_data are actual values.

    • Alex

      Good question. The initializer list _is_ an anonymous object, so &list is a const reference to an anonymous object (which is okay). But note that the elements of m_data are not references -- they’re actual integer values. So when we say m_data[count] = element, this is actually _copying_ an integer value from the initializer list to an m_data element (not initializing a reference) That way, when the initializer list goes out of scope at the end of the initialization expression, we still have our copied values in m_data.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter