16.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:

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.

Class initialization using std::initializer_list

When a 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, unless you initialize the std::initializer_list right away. Therefore, you’ll almost 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 don’t pass the list by const reference. Much like std::string_view, std::initializer_list is very lightweight and copies tend to be cheaper than an indirection.

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. We use direct initialization, rather than brace initialization, because brace initialization prefers list constructors. Although the constructor would get resolved correctly, it’s safer to use direct initialization to initialize classes with list constructors if we don’t want to use the list constructor.

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(std::initializer_list<int>), not IntArray(int). If you want to match to IntArray(int) once a list constructor has been defined, you’ll need to use copy initialization or direct initialization. The same happens to std::vector and other container classes that have both a list constructor and a constructor with a similar type of parameter

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).


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


Implementing a constructor that takes a std::initializer_list parameter 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

Question #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

16.x -- Chapter 16 comprehensive quiz
16.6 -- Container classes

179 comments to 16.7 — std::initializer_list

  • magaji::hussaini

    Hi alex, I think you should add a note about initializer list and uniform initialisation.

    Only direct/copy initialisation will invoke the overloaded int constructor.
    It was hard for me to figure out. Thought arr{ 10 } called IntArray(int length). Indexing arr[] always invoke assert();

  • Andrew

    Why is the static cast to size_t necessary?

    • list.size() returns an @std::size_t (unsigned int), @m_length is an int.
      Comparing signed to unsigned will convert the signed value to an unsigned value, which could cause trouble. The code will cause the same problems, but by using the cast you're telling the compiler that you know what you're doing (You know @m_length is >= 0).

  • Dean Cutillar


    This was a very frustrating exercise for me.  The program that I wrote was doing well until the variable "array" was reassigned to the new array of ints.

    My program kept going to the Constructor with the std::initializer_list in the parameter, and I couldn't figure out why.

    I must have spent 3-4 hours just trying to figure this out.  I finally caved in and looked at the answer code.  

    When I looked closely at it, I was missing the word "const" in the parameter list on the overloaded assignment function.  When I put that in, the program worked fine.  Now my question:

    Why did leaving out that one word cause my program to malfunction?  Thanks.


    • Hi Dean!

      The same thing happens in line 82 of the solution. Since the call to @operator= is illegal, your compiler will do the next best thing. That is, constructing a temporary @IntArray from the initializer list and then calling @operator=(const IntArray &).

  • Carlton

    Pedantic but:

    "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."

    overriding --> overloading?

  • Nitin

    "it automatically convert it"  --> converts it.
    "This work analogously"  --> works analogously.

  • Ethan Partida

    I've just started on the next chapter learning inheritance. when you call IntArray(list.size()) in the initilizer list would this be considered a form of inheritance?

  • Jack

    Can someone please explain the

    line please? I get what it's doing, but why the static_cast? What is size_t? Code works just the same without it?

  • jgahr

    Hello. I noticed that if you make the overloaded operator= function use a return type like this:

    instead of returning a reference to the object's this pointer, I get the same result as if I had used a return type of intArray&.

    For what reason do we use one method over the other?

    • nascardriver

      Hi jgahr!

      You're probably not using the return value of @operator=:

      This code doesn't work with your implementation.

  • Johnny

    Hi Alex, thank you so much for these great tutorials!

    i had a problem that my newly allocated IntArray was all uninitialized values. The difference between my code and the example is that in my operator= function, the initializer_list is non-const.

    Through debugging i found out that since the param was non-const, during the assignment, the init_list constructor was actually being called because it was the only function that took a const init_list.

    So my question is does the compiler treat const and non-const initializer_list parameters as different types?

    • nascardriver

      Hi Johnny!

      > does the compiler treat const and non-const initializer_list parameters as different types?
      Yes it does, you can test this yourself

      Think about code like this

      There's no way this code could call a version of @a that takes a non-const initializer list, because that would mean the argument can be modified, which it obviously cannot.

    • Johnny

      Hi nascardriver!

      Thank you for such a speedy reply! My apologies, i realized that the source of my confusion was that i forgot initializer_list was being passed in as a reference, not a value, and here i was wondering why the const-ness of a pass-by value mattered lol.

      >>Think about code like this

      Is it correct to say that the parameter must be a const reference, because the init_list is being passed in as a literal, and literals are rvalues which can only be bound to const references?

      Thank you very much for your help!

      • nascardriver

        > Is it correct to say that the parameter must be a const reference, [...]
        I must partially correct my previous answer,

        could call a version of @a with non-const parameter, but only if it's copy-by-value.
        If the parameter is a reference, you are correct (for what you know so far, lesson 15.2 covers r-value references).

  • nascardriver

    Hi Alex!

    Is there any reason not to initialize @m_data in the member initializer list?

    • Alex

      It used to be bad because if the class allocated memory for multiple variables this way, any non-first allocation could throw an exception, causing the construction to abort and leaving the previous allocations not cleaned up. Because the constructor didn't finish, the destructor wouldn't even run.

      However, now that function try blocks exist, that issue has been addressed, so there's no remaining issues that I'm aware of.

  • nascardriver

    Hi Alex!

    You should add a cast to line 44 in the solution.

  • mctitkez

    Line 41.
    If don't write "const" in argument I will have an inexplicit result.

    Result on my computer:
    5 4 3 2 1
    -572662307 -572662307 -572662307 -572662307 -572662307 -572662307

    Why it's do that?

    • nascardriver

      Hi mctitkez!

      This isn't caused by not using the "const" keyword. You must have changed something else. Please share your full code.

      • mctitkez

        it's copyed code from above quiz solution. I'm tryed it again at home with other computer (both have visual studio 2017) and I get the same result... just delete "const" keyword from overload operator "="

        #include "stdafx.h"
        #include <cassert> // for assert()
        #include <initializer_list> // for std::initializer_list
        #include <iostream>

        class IntArray {
            int m_length;
            int *m_data;

            IntArray() :
                m_length(0), m_data(nullptr) {}

            IntArray(int length) :
                m_length(length) {
                m_data = new int[length];

            IntArray(const std::initializer_list<int> &list) : // allow IntArray to be initialized via list initialization
                IntArray(list.size()) // use delegating constructor to set up initial array
                // Now initialize our array from the list
                int count = 0;
                for (auto &element : list) {
                    m_data[count] = element;

            ~IntArray() {
                delete[] m_data;
                // we don't need to set m_data to null or m_length to 0 here, since the object will be destroyed immediately after this function anyway

            IntArray& operator=(std::initializer_list<int> &list) {
                // If the new list is a different size, reallocate it
                if (list.size() != m_length) {
                    // delete any existing elements
                    delete[] m_data;

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

                // Now initialize our array from the list
                int count = 0;
                for (auto &element : list) {
                    m_data[count] = element;

                return *this;

            int& operator[](int index) {
                assert(index >= 0 && index < m_length);
                return m_data[index];

            int getLength() { return m_length; }

        int main() {
            IntArray array { 5, 4, 3, 2, 1 }; // initializer list
            for (int count = 0; count < array.getLength(); ++count)
                std::cout << array[count] << ' ';

            std::cout << '\n';

            array = { 1, 3, 5, 7, 9, 11 };

            for (int count = 0; count < array.getLength(); ++count)
                std::cout << array[count] << ' ';

            return 0;

        • nascardriver

          Yep, sorry, I was wrong.

          When you remove the "const"

          can no longer invoke @operator=, because the initializer list cannot be converted to a non-const initializer list. What happens instead is @IntArray(const std::initializer_list<int> &list) is called to construct a new @IntArray followed by a shallow copy of @m_data being performed into @array. That new @IntArray is then destructed, deleting @m_data and @array is left with a dangling pointer.

          • mctitkez

            Awesome. Thanks for explaining. Gods will keep a warm place in heaven for you :)

            But I can't understand what's different between const and non const initializer list for compiler?

            • nascardriver

              Here's an easier example

              If we were to declare @i const the compiler would know that @myFunction doesn't try to modify any arguments. So if @i was const we could pass 5, of course we wouldn't be able to assign a new value to @i in @myFunction.

              • mctitkez

                One more try to understand :)
                As I see inside the initialize_list's code, the initialize_list have a constructor with a "constexpr" keyword:

                constexpr initializer_list(const _Elem *_First_arg,
                    const _Elem *_Last_arg) _NOEXCEPT
                    : _First(_First_arg), _Last(_Last_arg)

                As I can understood we must pass a const ref for "constexpr". If we don't, the compliler find it in "IntArray(const std::initializer_list<int> &list)" instead "IntArray& operator=(std::initializer_list<int> &list)"?

                am I right? (if yes I need 100500 applause :) )

                And from where compiller get "-572662307". m_data it's a dangling pointer? and step by step it return the same result on other computers, again and again.

                • nascardriver

                  constexpr is different from const in that it is evaluated at compile-time. The @std::initializer_list constructor isn't of interest here, because the same would happen with any other type. But you've got the important part right. If the compiler doesn't find a fitting operator= it will do the next best thing, which in this case is creating a new IntArray and copying contents.

                  The behavior of reading from dangling pointers is undefined. The program might crash, you might get seemingly random values. You code produces different output for me.

  • Saumitra Kulkarni

    In the overloaded assignment operator instead of this code,

    I used the earlier constructor instead.

    but this leads to undefined behaviour, When I debugged it, after executing the constructor it called the destructor which deleted the m_data and which caused undefined behaviour. I am unable to understand why is the destructor getting called after executing the constructor. Surely I am missing something silly I guess!

    • nascardriver

      Hi Saumitra!

      You're not calling the constructor of the current instance, you're creating a new @IntArray object which is immediately destroyed, because it's never used.

      This is essentially what you're doing.

      What you can to is moving the contents of the constructor into a separate function and call that function from the constructor and your operator=.

      • David

        I had a similar idea: why not call the overloaded assignment operator from the constructor?

        • nascardriver

          * m_length and m_data are uninitialized, this causes undefined behavior.
          * You're not using member initialization lists, but if you did, you'd need to initialize members to 0 in order for @operator= to work properly.

          Alex' solution looks good as it is, just use uniform initialization.

  • Alexxx

    Example at doesn't provide list assignment and it not fails because of std::vector type that handle allocations. But IntArray, indeed, fails when assignment used and no list assignment provided.


    Wouldn't this cause a memory leak if list.size() == m_length is true because then m_data wouldn't get deallocated before assigned new elements?

  • Luhan

    In this part

    Wouldn't be better if you did this

  • Ali

    Hi. I have a question. Why should we pass the std::initializer_list as const reference? I did it using non_const reference and it produced garbage results in the overloaded assignment operator and if used in constructor, it produces compiler errors.

    • Alex

      Using a const reference lets us accept std::initializer_list l-values and r-values. When we do something like this:

      The std::initializer_list containing the values is an r-value.

  • AMG

    Minor comment. Xcode gave a warning about implicit conversation from "unsigned int" to "int", and "static_cast" resolved the problem.

    Thank you for the best C++ tutorial!

  • Angmar

    Hi Alex.

    How can a user disambiguate an initializer_list from brace initialization? I have been told that if a class has a constructor taking std::initializer_list, it will override others. How can one elegantly avoid/disambiguate distinguish the constructor for the compiler?

    • Alex

      As you've noted, the initializer_list constructor takes precedence if there's a match, even if the other constructors could match. This seems a bit counterintuitive, but that's the way it is. talks about why this decision was made, if you want to do some really deep reading.

      If you want to ensure the non-initializer_list constructor is called, use direct initialization (parenthesis).

  • CrazyL

    Hello Alex,

    I got a question regarding the quiz solution (l:4):

    I understand that we need to reallocate memory if the old array was smaller than the new one. But why should we do that if the new array is smaller? Shouldn't we then just copy it into the old memory, leaving some space at the end unused (like vector objects, when they allocate a little more space for new entries to avoid resizing next time)?

    Or am I missing some important point here?

    Regarding your statement at the end:

    [..] 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. [..]

    Why is the IntArray left with a hanging pointer? The constructor used "new" to allocate memory for the anonymous object, shouldn't that memory have static duration until it's deleted by out IntArray?

    Thank you for this great tutorial page and all your replies, that's really helpful and very much appreciated!

    • Alex

      > Shouldn’t we then just copy it into the old memory, leaving some space at the end unused (like vector objects, when they allocate a little more space for new entries to avoid resizing next time)?

      You could! But then you'd some way differentiate the concepts of length vs capacity since the length and capacity are no longer the same. I did it this way just for simplicity of example.

      > Why is the IntArray left with a hanging pointer? The constructor used "new" to allocate memory for the anonymous object, shouldn’t that memory have static duration until it’s deleted by out IntArray?

      No. Without doing one of the above, the initializer_list is converted into a temporary IntArray, and that temporary IntArray is then shallow copied into array. At that point in time, both array and our temporary IntArray are pointing to the same m_data (due to the shallow copy!). When the temporary IntArray goes out of scope, it will delete m_data, leaving array's m_data hanging.

      From reading the article text, it wasn't clear what IntArray I was talking about in some cases. I've updated the wording to make it easier to follow.

  • Omri

    const "does it again":

    int count = 0;
    for (auto &element : list)
    { m_data[count] = element; ++count;}
    **Works fine**

    int count = 0;
    for (int element : list)
    { m_data[count] = element; ++count;}
    **Works fine**

    int count = 0;
    for (int &element : list)
    { m_data[count] = element; ++count;}
    !!Does not compile!!
    "element" is part of the "list".
    The "list" is an r-value.
    Thus "element" is an r_value.
    Thus cannot be referenced "normally" (but rather as a const... see below).
    The auto prefix seems take care of this "on its own".

    int count = 0;
    for (const int &element : list)
    { m_data[count] = element; ++count;}
    **works fine**

  • Omri

    Hello Alex,
    "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."
    My understanding up to now is as follows:
    We pass the list as reference (&) so we do not make a copy of the std::initializer_list object as it is passed to the "list" based constructor.
    The "const" is required since, in this case, the std::initializer_list object does not have an "ordinary" addressable location and thus in order to reference it the compiler needs to arrange for dedicated "means" as per const objects.
    Pls advise.

  • Zeyd


    Good job sir,
    Thank you very much for this impeccable tutorial.

    Best regards.

  • Alexander Kindel

    When I tried implementing the overloaded assignment operator to take an initializer list, I didn't make the std::initializer_list parameter const. My array ended up with garbage values in it by the time the program printed it the second time, and it took me a while of comparing with the answer provided to figure out that the omission of const was the reason. I thought the only thing const does in this context is alter what one is allowed to do with the value from the parameter, so that as long as a program compiles when the const is there, removing it won't change the functionality of the program. Apparently that's wrong in this case?

    • Alex

      I'm not sure. Since initializer lists are normally temporary r-values, I'd think if you wanted the std::initializer_list parameter to be a reference, you'd need to make it a const reference, since non-const references can't bind to r-values. When I tried removing the const from the parameter in the IntArray example, it wouldn't even compile (as I expected).

  • 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];


    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 all code inside code tags: [code]your code here[/code]