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 converts 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. Note that list.size() returns a size_t (which is unsigned) so we need to cast to a signed int here.

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 to the standards committee 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.

One caveat: Initializer lists will always favor a matching initializer_list constructor over other potentially matching constructors. Thus, this variable definition:

would match to IntArray(const std::initializer_list<int> &), not IntArray(int). If you want to match to IntArray(int) once a initializer_list constructor has been defined, you’ll need to use copy initialization or direct initialization.

Class assignment using std::initializer_list

You can also use std::initializer_list to assign new values to a class by overloading the assignment operator to take a std::initializer_list parameter. This works 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

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 { 1, 3, 5, 7, 9, 11 } is a std::initializer_list, the compiler will use the list constructor to convert the initializer list into a temporary IntArray. Then it will call the implicit assignment operator, which will shallow copy the temporary IntArray into our array object.

At this point, both the temporary IntArray’s m_data and array->m_data point to the same address (due to the shallow copy). You can already see where this is going.

At the end of the assignment statement, the temporary IntArray is destroyed. That calls the destructor, which deletes the temporary IntArray’s m_data. This leaves our array variable with a hanging m_data pointer. When you try to use array->m_data for any purpose (including when array goes out of scope and the destructor goes to delete m_data), you’ll get undefined results (and 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

98 comments to 10.7 — std::initializer_list

  • Deepak

    Hi,
    Suppose i have a class with below variables

    How to Initialize both while creating the object

    I tried writing the constructor as below,but getting error.

    could you please tell me how this can be handled

    • @list1 is an @std::initializer_list. You're missing the template argument, and '100' isn't an initializer list.

      You don't need an initializer list at all to make your call work.

  • Pawan Kataria

    Hi,

    While studying delegating constructors(https://www.learncpp.com/cpp-tutorial/8-6-overlapping-and-delegating-constructors/), I read,

    "A few additional notes about delegating constructors. First, a constructor that delegates to another constructor is not allowed to do any member initialization itself. So your constructors can delegate or initialize, but not both."

    But here we are delegating from one constructor to another to allocate memory and then initializing it members in the constructor which just did a delegation.

    Which part I understood wrong ??

    • Hi!

      The constructor's body can do whatever it wants, but there can not be any initializations in the member initializer list.

  • Anthony

    Hi Alex,

    Just to be clear: If the copy constructor and the assignment operator are not deleted, then they should (both) be overloaded and the initializer constructor made explicit? I've done this below:

    • Alex

      No. If you have a constructor that takes a std::initializer_list, you should have a corresponding list-assignment or a copy assignment function. Otherwise you'll get a shallow copy if you do a list assignment.

      The prior recommendation to make your std::initializer_list constructor explicit doesn't prevent this by itself, so that recommendation has been revoked, but it's a good idea to mark all single-parameter constructors as explicit anyway, so no harm there.

      • Anthony

        > No. If you have a constructor that takes a std::initializer_list, you should have a corresponding list-assignment or a copy assignment function. Otherwise you'll get a shallow copy if you do a list assignment.

        But if you're too lazy to have the corresponding list-assignment or copy assignment functions, you should delete them so that they can't be used to shallow copy by accident?

  • Louis Cloete

    @Alex, your solution again wouldn't compile with the stricter compiler error levels you recommended in 0.11. Here is what you should change:

    In the IntArray(const std::initializer_list &list) constructor, you need to call the delegating constructor with IntArray(static_cast<int>(list.size()), else you will get a -Werror warning treated as an error about a narrowing conversion which might change the sign. Thus:

    Likewise in the operator=(const std::initializer_list &list) method. I solved the problem like this:

    Further, I get a -Werror warning saying the class implements pointer members, but doesn't override IntArray(const IntArray&) or operator=(const IntArray&). I did this:

    and the compiler was happy!

    • Alex

      Thanks, and updated. I'm sure there are other instances of examples that were compiled before the new settings recommendations were put in place. If you find other examples that generate warnings, please point them out so I can update them. Much appreciated!

  • hassan magaji

    hi Alex,
    about the following code(i might be wrong):

    since copying fundamental data types is faster than referencing them.

  • Jon

    Hello! On the way to figuring out the quiz I had a question I couldn't quite answer. I originally had my operator overload function written like the following, which does print the expected output but also results in a runtime error because it accidentally results in the class Destructor trying to delete unallocated memory later on. I understand how to fix it and get the program to run correctly.

    My question is, with the following INCORRECT code, since I delete[] m_data and free the dynamically allocated memory but forget to reallocate the m_data array via the "new" keyword, where and how is my for-each loop setting up the (1, 3, 5, 7, 9, 11) array that still prints correctly?

    In a new memory address on the stack, instead of the heap as originally intended? Or is it able to still store and retrieve values in/from the same heap addresses even though they remain unallocated (which doesn't make sense to me)? I just want to make sure I understand what's really going on under the hood. Thank you!

Leave a Comment

Put all code inside code tags: [code]your code here[/code]