Last Update:
Designated Initializers in C++20

New Standard, new ways to initialize objects!
With C++20, we get a handy way of initializing data members. The new feature is called designated initializers and might be familiar to C programmers.
Let’s have a look at this small feature:
The basics
Designated Initialization is a form of Aggregate Initialization.
As of C++20, an Aggregate type::
- is an array type or,
- is a class type that:
- has no private or protected direct non-static data members
- has no user-declared or inherited constructors
- has no virtual, private, or protected base classes
- has no virtual member functions
In a basic form in C++20, you can write:
Type obj = { .designator = val, .designator = val2, ... };
// or
Type obj = { .designator { val }, .designator { val2 }, ... };
For example:
struct Point {
double x { 0.0 };
double y { 0.0 };
};
const Point p { .x = 10.0, .y = 20.0 };
const Point offset { .x { 100.0 }, .y { -100.0 } };
// mix also possible:
const Point translation { .x = 50.0, .y { -40.0 } };
Play @Compiler Explorer
Designator points to a name of a non-static data member from our class, like .x
or .y
.
This article started as a preview for Patrons, sometimes even months before the publication. If you want to get extra content, previews, free ebooks and access to our Discord server, join the C++ Stories Premium membership.
Why are designated initializers handy?
One of the main reasons to use this new kind of initialization is to increase readability.
It’s easier to read:
struct Date {
int year;
int month;
int day;
};
Date inFuture { .year = 2050, .month = 4, .day = 10 };
Than:
Date inFuture { 2050, 4, 10 };
In the case of the date class, it might not be clear what’s the order of days/month or month/days. With designated initializers, it’s effortless to see the order.
Or have a look at some Configuration class:
struct ScreenConfig {
bool autoScale { false };
bool fullscreen { false };
int bits { 24 };
int planes { 2 };
};
// hmmmm.... ?
ScreenConfig cfg { true, false, 8, 1 };
// better?
ScreenConfig playbackCfg {
.autoScale = true, .fullscreen = false, .bits = 8, .planes = 1
};
Rules for designated initializers
The following rules apply to designated initializers:
- Designated initializers work only for aggregate initialization
- Designators can only refer to non-static data members.
- Designators in the initialization expression must have the same order of data members in a class declaration.
- Not all data members must be specified in the expression.
- You cannot mix regular initialization with designaters.
- There can be only one designator for a data member.
- You cannot nest designators
For example, the following lines won’t compile:
struct Date {
int year;
int month;
int day;
MinAndHour mh;
static int mode;
};
Date d1 { .mode = 10; } // err, mode is static!
Date d2 { .day = 1, .year = 2010 }; // err, out of order!
Date d3 { 2050, .month = 12 }; // err, mix!
Date d4 { .mh.min = 55 }; // err, nested!
Advantages of designated initialization
- Readability. A designator points out to the specific data member, so it’s impossible to make mistakes here.
- Flexibility. You can skip some data members and rely on default values for others.
- Compatibility with C. In C99, it’s popular to use a similar initialization form (although even more relaxed). With the C++20 feature, it’s possible to have a very similar code and share it.
- Standardization. Some compilers like GCC or clang already had some extensions for this feature, so it’s a natural step to enable it in all compilers.
Examples
Let’s have a look at some examples:
#include <iostream>
#include <string>
struct Product {
std::string name_;
bool inStock_ { false };
double price_ = 0.0;
};
void Print(const Product& p) {
std::cout << "name: " << p.name_ << ", in stock: "
<< std::boolalpha << p.inStock_ << ", price: "
<< p.price_ << '\n';
}
struct Time { int hour; int minute; };
struct Date { Time t; int year; int month; int day; };
int main() {
Product p { .name_ = "box", .inStock_ {true }};
Print(p);
Date d {
.t { .hour = 10, .minute = 35 },
.year = 2050, .month = 5, .day = 10
};
// pass to a function:
Print({.name_ = "tv", .inStock_ {true }, .price_{100.0}});
// not all members used:
Print({.name_ = "car", .price_{2000.0}});
}
Play @Compiler Explorer
It’s also interesting that we can also use designated initialization inside another designated initialization, for example:
struct Time { int hour; int minute; };
struct Date { Time t; int year; int month; int day; };
Date d {
.t { .hour = 10, .minute = 35 },
.year = 2050, .month = 5, .day = 10
};
But we can’t use “nested” ones like:
Date d {
.t.hour = 10, .t.minute = 35, .year = 2050, .month = 5, .day = 10
};
The syntax .t.hour
won’t work.
Summary
As you can see, with designated initializers, we got a handy and usually more readable way of initializing aggregate types. The new technique is also common in other programming languages, like C or Python, so having it in C++ makes the programming experience even better.
More in the paper P0329 and wording in P0329R4 and @CppReference.
The feature is available in GCC 8.0, Clang 10.0, and MSVC 2019 16.1
Have you tried Designated Initializers?
Do you use Designated Initializers from C++20? #cpp20
— Bartlomiej Filipek (@fenbf) November 22, 2021
I've prepared a valuable bonus if you're interested in Modern C++!
Learn all major features of recent C++ Standards!
Check it out here: