An aggregate type in C++ is a type that can be initialised with a brace-enclosed list of initialisers. C++ contains three basic aggregate types, inherited from C:
- arrays
- structures
- unions
Since one of the design goals of C++ was to emulate the behaviour of built-in types it seems reasonable that you should be able to initialise user-defined aggregate types (containers, etc.) in the same way.
A std::initializer_list is a template class that allows a user-defined type to become an aggregate type.
When initialiser list syntax is used the compiler generates a std::initializer_list object containing the initialisation objects. A std::initializer_list is a simple container class that may be queried for its size; or iterated through.
If the class contains a constructor that takes a std::initializer_list as a parameter, this constructor is invoked and the std::initializer_list object passed.
Note, there is some syntactic sugar at work here – the lack of brackets ([]) in the declaration of aggr forces the compiler to construct the std::initializer_list (then call aggr‘s constructor) rather than creating an array of three Aggregate objects.
This is also a good place to insert some words of caution: Adding std::initializer_list constructor overloads may lead to unexpected results:
If a class has constructors overloaded for T and std::initializer_list<T> the compiler will always prefer the std::initializer_list overload. However, if you’ve provided a default constructor the compiler will always prefer that to calling the std::initializer_list overload with an empty initialiser list.
Initialiser lists can begin to look like so much magic and ‘handwavium’, so a brief look at an implementation of std::initializer_list is useful to dispel the mysticism:
When the compiler creates an std::initializer_list the elements of the list are constructed on the stack (or in static memory, depending on the scope of the initializer_list).
The compiler then creates the initializer_list object that holds the address of the first element and one-past-the-end of the last element. Note that the initializer_list is very small (two pointers) so can be passed by copy without a huge overhead; it does not pass the initialiser objects themselves. Once the initializer_list has been copied the receiver can access the elements and do whatever needs to be done with them.
Since C++11 all the STL containers support std::initializer_list construction; so now lists and vectors can be initialised in the same way as built-in arrays.
More information
Can’t wait? Download the full set of articles as a PDF, here.
To learn more about Feabhas’ Modern C++ training courses, click here.
- Practice makes perfect, part 3 – Idiomatic kata - February 27, 2020
- Practice makes perfect, part 2– foundation kata - February 13, 2020
- Practice makes perfect, part 1 – Code kata - January 30, 2020
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.
Just to add classes with public data members can also be brace initialized.
class Student
{
public:
int x;
int y;
int z;
};
Student obj{1,2,3};
You're quite right. This example is an aggregate initialization. Generally, I would recommend using struct for such POD types, rather than class, though.
The method should be called end() in the initializer_list implementation example.
Well spotted! Of course it should! 🙂
Thank you.