Initializer Lists
/ 3 min read
Table of Contents
Initializer lists in C++11 finally gave us a consistent way to initialize everything. You no longer have to guess which syntax to use.
What Initializer Lists Actually Are
std::initializer_list<T> is a lightweight wrapper around an array of values. It lets you pass multiple values using the {} syntax:
std::vector<int> numbers = {1, 2, 3, 4, 5};// Calls vector's initializer_list constructorThe compiler creates a temporary array and wraps it in std::initializer_list.
Uniform Initialization
The best part is uniform syntax across all types:
int x{42};std::string name{"John"};std::vector<int> nums{1, 2, 3};MyClass obj{arg1, arg2};No more wondering whether to use () or = or {}. Just use {} everywhere.
When I Actually Use Initializer Lists
Most of the time, I encounter them in these situations:
- Container initialization: The most common case:
std::map<std::string, int> scores = { {"Alice", 95}, {"Bob", 87}, {"Charlie", 92}};
std::set<std::string> keywords{"auto", "const", "void", "int"};- Custom class constructors: Making my classes work with
{}syntax:
class Point { double x_, y_;public: Point(double x, double y) : x_(x), y_(y) {} Point(std::initializer_list<double> values) { auto it = values.begin(); x_ = (it != values.end()) ? *it++ : 0.0; y_ = (it != values.end()) ? *it++ : 0.0; }};
Point p1{3.14, 2.71}; // Uses initializer_list constructorPoint p2(3.14, 2.71); // Uses regular constructor- Function parameters: Passing multiple values directly:
void process_numbers(std::initializer_list<int> numbers) { for (auto num : numbers) { std::cout << num << " "; }}
process_numbers({10, 20, 30, 40}); // Clean syntax- Return values: Returning multiple values as containers:
std::vector<std::string> get_names() { return {"Alice", "Bob", "Charlie"}; // Direct return}The Gotchas
Initializer lists can sometimes be ambiguous:
std::vector<int> v1(10, 5); // 10 elements, each with value 5std::vector<int> v2{10, 5}; // 2 elements: 10 and 5When a class has both regular constructors and initializer_list constructors, {} syntax prefers the initializer_list version.
Behind the Scenes
std::initializer_list is basically this:
template<class T>class initializer_list { const T* data_; size_t size_;public: const T* begin() const; const T* end() const; size_t size() const;};The elements are stored in read-only memory, so you can’t modify them through the initializer_list.
My Pattern
I always provide initializer_list constructors for container-like classes:
class MyContainer { std::vector<int> data_;public: MyContainer(std::initializer_list<int> init) : data_(init) {}
MyContainer& operator=(std::initializer_list<int> init) { data_ = init; return *this; }};
MyContainer container{1, 2, 3, 4};container = {5, 6, 7, 8}; // Assignment also worksThe Rule I Follow
Use {} initialization everywhere unless you specifically need the old syntax. It’s more consistent and catches narrowing conversions:
int x = 3.14; // Silently truncates to 3int y{3.14}; // Compilation error - narrowing conversionInitializer lists make C++ feel more modern and consistent. You don’t have to memorize different initialization syntaxes for different types.