Featured image of post C++20 Designated Initializers: Modern Struct Initialization

C++20 Designated Initializers: Modern Struct Initialization

A deep dive into C++20 Designated Initializers to make struct initialization more readable and maintainable.

C++20 introduced Designated Initializers, a feature borrowed from C99, allowing you to initialize structs by explicitly specifying member variable names. This greatly improves code readability and maintainability.

Basic Concepts

Designated initializers use the syntax .member_name = value to explicitly specify the members to be initialized.

Traditional initialization (Pre-C++20):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct Point {
    int x;
    int y;
    int z;
};

// Must follow declaration order, error-prone
Point p1 = {1, 2, 3};  // Which is x, y, z?

// Adding a member requires updating all initialization code
Point p2 = {1, 2};     // z is 0, but it's not obvious

Designated Initializers (C++20):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct Point {
    int x;
    int y;
    int z;
};

// Clear and explicit
Point p1 = {.x = 1, .y = 2, .z = 3};

// Can skip certain members (unspecified members are zero-initialized)
Point p2 = {.x = 1, .z = 3};  // y = 0

Important Rules

  1. Must follow declaration order
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
struct Person {
    std::string name;
    int age;
    std::string city;
};

// ✅ Correct: Follows declaration order
Person p1 = {
    .name = "Alice",
    .age = 30,
    .city = "Beijing"
};

// ❌ Error: Violates declaration order
Person p2 = {
    .age = 30,        // age comes after name
    .name = "Bob",    // Compilation error: "name": designator must appear in the order of member declaration of class "Person"
    .city = "Shanghai"
};
  1. Can skip members
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct Person {
    std::string name;
    int age;
    std::string city;
};

Person p1 = {
    .name = "Alice",
    .age = 30,
};

Person p1 = {
    .age = 30
};
  1. Cannot mix initialization styles
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct Point {
    int x, y, z;
};

// ❌ Error: Cannot mix
Point p = {1, .y = 2, .z = 3};  // Compilation error!

// ✅ Correct: Use one style consistently
Point p1 = {1, 2, 3};              // Positional initialization
Point p2 = {.x = 1, .y = 2, .z = 3}; // Designated initialization
  1. Each member can only be initialized once
1
2
3
4
5
6
7
8
9
struct Point {
    int x, y, z;
};

// ❌ Error: Duplicate initialization
Point d = {
    .x = 10,
    .x = 20  // Error!
};
  1. Cannot use designators for array elements
1
2
3
4
5
6
7
8
struct Point {
    int coords[3];
};

 // ✅ Allowed for the array itself
Point p = {.coords = {1, 2, 3}}; 
// ❌ Cannot do this (unlike C99)
Point p1 = {.coords[0] = 1, .coords[1] = 2};  // Error in C++!
  1. Only applicable to aggregate types
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// ✅ Aggregate type: No user-defined constructors
struct Aggregate {
    int x;
    double y;
};

Aggregate a = {.x = 1, .y = 2.0};  // Correct

// ❌ Non-aggregate type: Has a constructor
struct NonAggregate {
    int x;
    double y;
  
    NonAggregate(int x, double y) : x(x), y(y) {}
};

NonAggregate na = {.x = 1, .y = 2.0};  // Error! Designated initialization can only be used to initialize aggregate class types

Examples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
struct ServerConfig {
    std::string host = "localhost";
    int port = 8080;
    int timeout = 30;
    bool useSSL = false;
    int maxConnections = 100;
};

// Clearly create different configurations
ServerConfig devConfig = {
    .port = 3000,
    .timeout = 60
};

ServerConfig prodConfig = {
    .host = "api.example.com",
    .port = 443,
    .useSSL = true,
    .maxConnections = 1000
};

Nested structures:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
struct Address {
    std::string street;
    std::string city;
};

struct Employee {
    std::string name;
    Address address;
    double salary;
};

// Nested designated initialization
Employee emp = {
    .name = "Job",
    .address = {
        .street = "Street 1",
        .city = "US",
    },
    .salary = 75000.0
};

Comparison with C99

C++ designated initializers have some restrictions compared to C99:

FeatureC99C++20
Out-of-order initialization✅ Allowed❌ Must be in order
Array designators[index]❌ Not supported
Nested designators.a.b❌ Must use nested braces
Mixed initialization✅ Allowed❌ Not allowed
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Out-of-order
struct Point p1 = {.y = 2, .x = 1};  // Allowed in C99, error in C++20

// Array designators
int arr[5] = {[0] = 1, [4] = 5};     // Allowed in C99, error in C++20

// Nested designators
struct S {
    struct { int x, y; } point;
};
struct S s = {.point.x = 1};         // Allowed in C99, error in C++20

https://en.cppreference.com/w/cpp/language/aggregate_initialization

P0329R4: Designated Initialization

https://open-std.org/JTC1/SC22/WG14/www/docs/n494.pdf

https://en.cppreference.com/w/c/language/struct_initialization.html

Licensed under CC BY-NC-SA 4.0