Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Initializer Lists and Sequence Constructors

Last updated Jan 1, 2003.

Initializer lists are another aspect of the new initialization syntax of C++09. An initializer list enables you to use a sequence of values wherever an initializer may appear. For example, you can initialize a vector with a random number of literal values. Behind the scenes, C++09 equips STL containers with a new type of a constructor that intercepts ={0,1} initializers. Find out all the details here.

What's An Initializer List?

An initializer list is a sequence of values packed in braces such as:

{0, 1, 3.14}

Using an initializer list to initialize a container seems simple and intuitive. However, C++03 won't let you do this:

vector <double> vd = {0, 1, 3.14}; //error in C++03

The problem is that the brace notation in C++03 is reserved for aggregates. Because vector isn't an aggregate, the code above is rejected. Replacing the braces with other forms of initialization won't work either:

vector <double> vd (0, 1, 3.14); //error in C++03
vector <double> vd = (0, 1, 3.14); //error in C++03

Instead, you need to default-constructor the vector, populating it subsequently with a sequence of push_back() calls:

vector <double> vd;

Many C++ programmers have moaned about the lack of an intuitive means for initializing an object with a list of initializers. The standards committee also realized that C++ needed such a mechanism. The only problem was how to design an intuitive and consistent initialization notation without causing existing code to break. The chosen solution was a combination of two new concepts: a standard class template called std::initializer_list and a new type of constructors called sequence constructors.

Aggregates As Opposed To Classes with Constructors

As you already know, the existence of four different initialization notations in C++ confuses novices and complicates the design of generic code unduly. It was obvious that the new initialization list proposal would stick to a uniform syntax for all types: aggregates, built-in scalar types (int, char etc.), classes with constructors, and containers.

A C++09 compiler can tell whether an initializer in the form ={0,1,31.4} is an aggregate initializer, as in the following case:

double arr[]={0,1,31.4};

Or whether it's the new C++09 initializer list as in:

vector <double> vd = {0, 1, 3.14}; //C++09

The programmer isn't bothered by these technicalities; the use of an initializer list should "just work" in both cases, albeit with different semantics. Let's see how exactly it works.

The Solution: Sequence Constructors

A sequence constructor is one that takes a single argument of type std::initializer_list<T>. For example:

class C 
 C(initializer_list<int>); // construct from a sequence of ints
C c={1,12,23,455,0};

In essence, initializer_list<T> (defined in the new header <initializer_list>) transforms a sequence of values of type T into an array of T and uses that array to populate its object.

The initializer_list class has three member functions that allow access to the sequence:

template<class E> class initializer_list 
//implementation (a pair of pointers or a pointer + length)
constexpr initializer_list(const E*, const E*); // [first,last)
constexpr initializer_list(const E*, int); // [first, first+length)
constexpr int size() const; // no of elements
constexpr const T* begin() const; // first element 
constexpr const T* end() const; // one-past-the-last element

C++09 already adds a sequence constructor to every STL container. The sequence constructor of std::vector for instance might look like this:

template<class T> class vector {
T* elem;
size_t n;
vector (initializer_list<T> s) // C++09 sequence constructor. Constructs from a sequence of Ts
n= s.size();
reserve(n); //allocate raw storage for the initializer_list values
uninitialized_copy(s.begin(),s.end(),elem); //copy the values into the vector
// ... the rest as before

Consider our original example:

vector <double> vd = {0, 1, 3.14}; //C++09

In implementation in which vector doesn't define a sequence constructor (that is, C++03 and C++98 implementations), the declaration of vd will cause a compilation error. In C++09-compliant implementations the declaration of vd becomes valid. Under the hood, the initializer list {0, 1, 3.14} is interpreted as a temporary object constructed like this:

//for exposition only; C++09 code
double temp[] = {double(1), double(2), 3.14 } ;
initializer_list<double> tmp(temp, sizeof(temp)/sizeof(double)); //(first, length[
vector<double> v(tmp);

That is, a C++09 compiler constructs an array containing the initializers converted to the desired type (double in this example). This array is passed to vector's sequence constructor as an initializer_list object. The sequence constructor then copies the values from the array into its own buffer:

vector<initializer_list<double> tmp) {

Notice that the initializer_list object is passed by value. No need to worry about that since an initializer_list is a small object (typically containing only two pointers). Passing it by value enables implementations to inline most of the code.

Sequence Constructors and Aggregates

To avoid undesirable conversions and confusion between traditional K&R aggregate initializers and C++09 initializer lists, the compiler first checks whether vd has a declared constructor:

double vd[]={0,1,31.4}; //K&R aggregate initialization
vector <double> vd = {0, 1, 3.14}; //C++09

If a constructor is present the compiler operates according to the following rules:

  • Look for a sequence constructor and use it if it's the best match; if there's no sequence constructor or if the sequence constructor doesn't match
  • Look for a constructor (excluding sequence constructors) and use it if it matches the arguments provided; Otherwise
  • Issue an error message

If vd has no declared constructor the compiler operates in this way:

  • Check whether vd is an aggregate or a built-in type and treat the initializers between the braces as traditional POD initializers.
  • If vd is neither an aggregate nor a built-in type, issue an error message.

Using these restrictions and precedence rules, the compiler can silently distinguish between traditional K&R brace initializers (which are still valid in C++09 of course) and the new C++09 sequence constructors.