Copying Values into Container Storage
A container such as a vector of numbers is a common case.
Building a Container Already Full
Often we want an object that already holds several values the moment it is declared. A container such as a vector of numbers is a common case. The slow way is to create an empty object and then add each element one at a time. C++ offers a shorter way: write the values directly, inside braces, and let the object be built from them. The mechanism that makes this possible is a special kind of constructor called an initializer-list constructor.
In this article we will follow one braced list from the moment it is written to the moment the finished object exists. The running example is a class named Vector, whose constructor accepts a list of double values. Keep the question in mind as you read: how many elements will the object hold, and how did the values get there?
From Braces to One Object
The braces are not passed to the constructor as text, and they are not handed over as a loose set of separate arguments. Instead, the list of values is packaged into a single std::initializer_list object, and that one object is what the constructor receives. The compiler performs this packaging whenever a braced list is used, so the same mechanism applies to any list of values written in braces, as long as the class provides a suitable constructor.
Read the diagram from top to bottom. The braced list becomes one std::initializer_list object before the constructor runs. The constructor then copies values from that object into storage belonging to the Vector. The number of values inside the braces determines how many elements the object holds, because the element count comes from the same list.
What std::initializer_list Holds
std::initializer_list is the type of the single object that the compiler creates from a braced list. The constructor receives this object as its argument. It gives the constructor access to the values written in the braces and to how many of them there are.
The key point is that std::initializer_list is a way of handing over the list, not a container that the object keeps. The list is read once. Its values are used as a source, and the object keeps its own copy of those values in its own memory. The picture below shows the list as a view over a group of values, with the cells holding the values in order.
Writing the Constructor Signature
An initializer-list constructor is recognized by its parameter. Its argument has the type std::initializer_list followed by the type of the values it holds. In the Vector class, the constructor takes a list of double values, so the parameter type is std::initializer_list<double>. The declaration below is the form used in the source.
- Vector:: names the class whose constructor is being defined
- std::
initializer_list<double> is the argument type, one packaged object holding the braced values - lst is the name the constructor uses to read the values
Once the constructor has the list, it must give the object its own storage and fill it. The member initializers allocate a new array of doubles sized by the list, and the element count is recorded by converting the list's size to an int. The body then copies every value from the list into the newly allocated storage. A simplified sketch of that shape is shown below. The copy loop is an illustration of the copying step described in the source, not a complete class.
- elem gets a new array sized by the number of values in the list
- sz records the element count, converted from the list's size to an int
- the loop copies each value from the list into the object's own storage
Tracing the Copy Step by Step
What do you think happens?
Given a Vector constructed from the braced list {1,2,3,4,5}, how many elements does the finished object hold?
Reveal answer
5
The number of elements in the constructed object equals the number of values written inside the braces. The five values in the list give five elements.
Following the braced list {1,2,3,4,5} into a Vector
A Vector with an initializer-list constructor is declared with the braced list {1,2,3,4,5}. Trace how the values reach the object.
- 1Step 1: the compiler packages the list
The braces are turned into one std::
initializer_listobject holding the five values. The braces themselves are not passed along as separate arguments. - 2Step 2: the constructor receives the object
The initializer-list constructor takes std::
initializer_list<double> as its argument, so the packaged object is the value it gets. - 3Step 3: the storage is sized
The member initializers allocate an array of doubles sized by the list. The element count is set to the list's size, which is 5.
- 4Step 4: the values are copied
The body copies each value from the list into the new array. The object keeps its own copy, so it does not depend on the list after the copy is made.
The Vector holds five elements, 1, 2, 3, 4, and 5, stored in its own memory.
Mistakes with Brace-List Initialization
Common mistake Thinking the braces are passed to the constructor as separate arguments
Why it failsThe source states the list is packaged into a single std::
initializer_listobject, and that one object is what the constructor receives.Do this insteadThink of the braced list as one object carrying all the values, and give the constructor a single parameter of type std::initializer_list.
Common mistake Assuming the object keeps a reference to the braced values
Why it failsThe constructor reads the list and copies the values into the object's own storage. The object holds its own copy.
Do this insteadExpect the object's elements to be its own copies, stored in memory the object owns.
Common mistake Expecting the element count to be set separately from the list
Why it failsThe element count of the constructed object equals the number of values written inside the braces.
Do this insteadCount the values inside the braces to know how many elements the object will hold.
Common mistake Treating braces and parentheses as interchangeable when a class has an initializer-list constructor
Practice: Counting and Tracing Values
A Vector is declared with the braced list {10, 20, 30}. Without running any code, state how many elements the object holds, and describe in order which object the constructor receives and where the values are copied.
Hints
- Count the values written inside the braces.
- Recall that the braces are packaged into one std::
initializer_listobject before the constructor runs.
Key Takeaways
- A braced list such as {1,2,3,4,5} is packaged by the compiler into one std::
initializer_listobject, and that object is what the constructor receives. - An initializer-list constructor takes std::
initializer_list<T> as its argument, as in Vector::Vector(std::initializer_list<double> lst). - The constructor sizes its storage from the list and copies each value into the object's own elem storage, so the object does not depend on the list afterward.
- The number of elements in the constructed object equals the number of values written inside the braces.