The Complete Guide to C++20 Designated Initializers | Clear Struct Initialization

The Complete Guide to C++20 Designated Initializers | Clear Struct Initialization

이 글의 핵심

C++20 designated initializers: .field = value syntax for aggregates—ordering rules vs C and mixing with positional init.

What Are Designated Initializers? Why Do We Need Them?

Problem Scenario: Confusion in Struct Initialization

The Problem: When a struct has many members, listing values in order can make it unclear which value corresponds to which member.

struct Config {
    std::string host;
    int port;
    bool ssl;
    int timeout;
    int max_connections;
};

int main() {
    // It's unclear which value corresponds to which member
    Config cfg = {"localhost", 8080, true, 30, 100};
    // Is port 8080? Is timeout 30?
}

The Solution: Designated Initializers (introduced in C++20) allow you to initialize struct members by explicitly specifying their names. This makes the code more readable and safe, even if the order of members changes.

int main() {
    Config cfg = {
        .host = "localhost",
        .port = 8080,
        .ssl = true,
        .timeout = 30,
        .max_connections = 100
    };
    // Clear and easy to read!
}
flowchart LR
    subgraph before["Traditional Initialization"]
        b1["Config cfg = {val1, val2, val3, val4, val5}"]
        b2["Requires remembering the order"]
        b3["Confusing"]
    end
    subgraph after["Designated Initializers"]
        a1["Config cfg = {.host=..., .port=...}"]
        a2["Explicit member names"]
        a3["Clear"]
    end
    b1 --> b2 --> b3
    a1 --> a2 --> a3

Table of Contents

  1. Basic Syntax
  2. Order Rules
  3. Nested Structs
  4. Default Values and Partial Initialization
  5. Common Errors and Solutions
  6. Production Patterns
  7. Complete Example: HTTP Request Configuration

1. Basic Syntax

Basic Usage

struct Point {
    int x;
    int y;
};

int main() {
    // C++20 Designated Initializers
    Point p1 = {.x = 10, .y = 20};
    
    // Curly braces can be omitted
    Point p2{.x = 30, .y = 40};
    
    std::cout << p1.x << ", " << p1.y << '\n';  // 10, 20
}

Comparison with Traditional Initialization

struct Person {
    std::string name;
    int age;
    std::string city;
};

int main() {
    // Traditional initialization (order matters)
    Person p1 = {"Alice", 30, "Seoul"};
    
    // Designated Initializers (explicit member names)
    Person p2 = {
        .name = "Bob",
        .age = 25,
        .city = "Busan"
    };
}

2. Order Rules

Must Follow Declaration Order

In C++20, Designated Initializers must follow the declaration order of struct members (unlike in C).

struct Data {
    int a;
    int b;
    int c;
};

int main() {
    // ✅ In declaration order
    Data d1 = {.a = 1, .b = 2, .c = 3};  // OK
    
    // ❌ Out of order
    // Data d2 = {.b = 2, .a = 1, .c = 3};  // Error in C++20
    
    // ✅ Skipping members is OK
    Data d3 = {.a = 1, .c = 3};  // b = 0 (default value)
}

Differences Between C and C++20

FeatureCC++20
OrderFlexibleMust follow declaration order
MixingAllowedNot allowed
ArraysFree indexSequential only
// Allowed in C
struct Data { int a, b, c; };
Data d = {.c = 3, .a = 1, .b = 2};  // C: OK, C++20: Error

3. Nested Structs

Nested Initialization

struct Address {
    std::string city;
    std::string street;
    int zipcode;
};

struct Employee {
    std::string name;
    int id;
    Address address;
};

int main() {
    Employee emp = {
        .name = "Alice",
        .id = 100,
        .address = {
            .city = "Seoul",
            .street = "Gangnam",
            .zipcode = 12345
        }
    };
    
    std::cout << emp.address.city << '\n';  // Seoul
}

Deep Nesting

struct Location {
    double lat;
    double lon;
};

struct Address {
    std::string street;
    Location location;
};

struct Person {
    std::string name;
    Address address;
};

int main() {
    Person p = {
        .name = "Bob",
        .address = {
            .street = "Main St",
            .location = {
                .lat = 37.5,
                .lon = 127.0
            }
        }
    };
}

4. Default Values and Partial Initialization

Providing Default Values

struct Settings {
    int width = 800;
    int height = 600;
    bool fullscreen = false;
    int fps = 60;
};

int main() {
    // Specify some values, others use defaults
    Settings s1 = {
        .width = 1920,
        .height = 1080
    };
    // fullscreen = false, fps = 60
    
    Settings s2 = {
        .fullscreen = true
    };
    // width = 800, height = 600, fps = 60
}

Empty Initialization

struct Point {
    int x;
    int y;
};

int main() {
    Point p = {};  // x = 0, y = 0
}

5. Common Errors and Solutions

Error 1: Out of Order

Symptom: error: designator order for field does not match declaration order.

struct Data {
    int a;
    int b;
    int c;
};

int main() {
    // ❌ Out of order
    // Data d = {.b = 2, .a = 1};  // Error
    
    // ✅ Correct order
    Data d = {.a = 1, .b = 2};  // OK
}

Error 2: Mixing Initializations

Symptom: error: cannot mix designated and non-designated initializers.

struct Point {
    int x;
    int y;
    int z;
};

int main() {
    // ❌ Mixing not allowed
    // Point p = {10, .y = 20, .z = 30};  // Error
    
    // ✅ Only designated
    Point p1 = {.x = 10, .y = 20, .z = 30};  // OK
    
    // ✅ Only traditional
    Point p2 = {10, 20, 30};  // OK
}

Error 3: Non-Aggregate Types

Symptom: error: designated initializers cannot be used with a non-aggregate type.

Cause: Classes with constructors or private members are not aggregates.

// ❌ Has a constructor
class MyClass {
public:
    MyClass(int x) : value(x) {}
    int value;
};

// MyClass obj = {.value = 10};  // Error

// ✅ Aggregate (no constructor)
struct MyStruct {
    int value;
};

MyStruct obj = {.value = 10};  // OK

6. Production Patterns

Pattern 1: Configuration Structs

struct ServerConfig {
    std::string host = "0.0.0.0";
    int port = 8080;
    bool ssl = false;
    int timeout_ms = 30000;
    int max_connections = 1000;
    std::string log_level = "info";
};

ServerConfig load_config() {
    // Use defaults and override some
    return ServerConfig{
        .host = "localhost",
        .port = 9000,
        .ssl = true
        // Others use defaults
    };
}

Pattern 2: Replacing the Builder Pattern

// Before: Builder Pattern
class ConfigBuilder {
public:
    ConfigBuilder& setHost(std::string h) { host = h; return *this; }
    ConfigBuilder& setPort(int p) { port = p; return *this; }
    Config build() { return Config{host, port, ...}; }
private:
    std::string host;
    int port;
};

// After: Designated Initializers (simpler)
struct Config {
    std::string host = "localhost";
    int port = 8080;
    bool ssl = false;
};

Config cfg = {
    .host = "example.com",
    .port = 443,
    .ssl = true
};

Pattern 3: Test Data

struct TestCase {
    std::string name;
    int input;
    int expected;
    bool should_throw = false;
};

std::vector<TestCase> tests = {
    {.name = "zero", .input = 0, .expected = 0},
    {.name = "positive", .input = 5, .expected = 25},
    {.name = "negative", .input = -3, .expected = 9},
    {.name = "overflow", .input = 999999, .expected = 0, .should_throw = true}
};

void run_tests() {
    for (const auto& test : tests) {
        std::cout << "Running test: " << test.name << '\n';
        // ... run test ...
    }
}

Pattern 4: Optional Parameters

struct RenderOptions {
    int width = 800;
    int height = 600;
    bool antialiasing = true;
    int samples = 4;
    std::string output_format = "png";
};

void render(const std::string& scene, const RenderOptions& options = {}) {
    std::cout << "Rendering " << scene 
              << " at " << options.width << "x" << options.height << '\n';
}

int main() {
    // Use all defaults
    render("scene1.obj");
    
    // Override some options
    render("scene2.obj", {.width = 1920, .height = 1080});
    
    // Override more options
    render("scene3.obj", {
        .width = 3840,
        .height = 2160,
        .samples = 8,
        .output_format = "exr"
    });
}

Pattern 5: Factory Functions

struct DatabaseConfig {
    std::string host = "localhost";
    int port = 5432;
    std::string database;
    std::string user;
    std::string password;
    int pool_size = 10;
    bool ssl = false;
};

// Factory with sensible defaults
DatabaseConfig create_dev_config() {
    return {
        .database = "dev_db",
        .user = "dev_user",
        .password = "dev_pass"
        // Others use defaults
    };
}

DatabaseConfig create_prod_config() {
    return {
        .host = "prod.example.com",
        .database = "prod_db",
        .user = "prod_user",
        .password = "secure_pass",
        .pool_size = 50,
        .ssl = true
    };
}

7. Complete Example: HTTP Request Configuration

#include <string>
#include <map>
#include <chrono>
#include <iostream>

struct HttpHeaders {
    std::string content_type = "application/json";
    std::string authorization;
    std::string user_agent = "MyApp/1.0";
};

struct HttpRequest {
    std::string method = "GET";
    std::string url;
    HttpHeaders headers;
    std::string body;
    std::chrono::seconds timeout = std::chrono::seconds(30);
    bool follow_redirects = true;
    int max_redirects = 5;
};

void send_request(const HttpRequest& req) {
    std::cout << req.method << " " << req.url << '\n';
    std::cout << "Content-Type: " << req.headers.content_type << '\n';
    std::cout << "Timeout: " << req.timeout.count() << "s\n";
}

int main() {
    // GET request (using defaults)
    HttpRequest get_req = {
        .url = "https://api.example.com/users"
    };
    send_request(get_req);
    
    // POST request (overriding some values)
    HttpRequest post_req = {
        .method = "POST",
        .url = "https://api.example.com/users",
        .headers = {
            .content_type = "application/json",
            .authorization = "Bearer token123"
        },
        .body = R"({"name":"Alice","age":30})",
        .timeout = std::chrono::seconds(60)
    };
    send_request(post_req);
}

Performance Considerations

Zero Overhead

Designated initializers have zero runtime overhead compared to traditional initialization. They are purely a compile-time feature.

// Traditional
Config c1 = {"localhost", 8080, true};

// Designated
Config c2 = {.host = "localhost", .port = 8080, .ssl = true};

// Both compile to identical assembly code

Compile-Time Verification

Designated initializers provide compile-time safety:

struct Point {
    int x;
    int y;
};

// ❌ Typo in member name
// Point p = {.x = 10, .z = 20};  // Error: no member named 'z'

// ✅ Compiler catches the error
Point p = {.x = 10, .y = 20};  // OK

When to Use

ScenarioTraditionalDesignated
Few members (2-3)OKOptional
Many members (4+)ConfusingRecommended
Default valuesVerboseClean
Partial initializationError-proneSafe
ReadabilityLowHigh

Recommendation: Use designated initializers for:

  • Configuration structs
  • API parameters
  • Test data
  • Any struct with 4+ members

Memory Layout

Designated initializers do not affect memory layout or size:

struct Data {
    int a;
    int b;
    int c;
};

Data d1 = {1, 2, 3};
Data d2 = {.a = 1, .b = 2, .c = 3};

// sizeof(d1) == sizeof(d2)
// Memory layout is identical

Summary

ConceptDescription
Designated InitializersInitialize struct members by name
Syntax{.member = value}
OrderMust follow declaration order (C++20)
MixingCannot mix designated and traditional initializations
Use CasesConfigurations, test data, API parameters

Designated Initializers significantly improve code readability and make struct initialization clearer.


FAQ

Q1: What are the differences between C and C++20?

A: In C, the order of designated initializers is flexible, and skipping members is allowed. In C++20, you must follow the declaration order, and mixing designated and traditional initializations is not allowed.

Q2: What happens if I don’t follow the order?

A: A compile-time error will occur. You must specify members in the same order they are declared.

Q3: What if I only initialize some members?

A: The remaining members will be initialized to their default values (if provided) or zero.

Q4: Can I use Designated Initializers with classes?

A: Only Aggregate types support Designated Initializers. Classes with constructors or private members are not aggregates.

Q5: What about arrays?

A: In C++20, Designated Initializers for arrays are limited. You can only initialize elements sequentially, and skipping indices is not allowed.

Q6: Can I use designated initializers with inheritance?

A: No, designated initializers only work with aggregate types. Classes with base classes are not aggregates.

struct Base { int x; };
struct Derived : Base { int y; };

// ❌ Error: Derived is not an aggregate
// Derived d = {.x = 10, .y = 20};

// ✅ Use composition instead
struct Derived {
    Base base;
    int y;
};
Derived d = {.base = {.x = 10}, .y = 20};

Q7: What about performance?

A: Designated initializers have zero runtime overhead. They are a compile-time feature that improves readability without affecting performance.

Q8: Can I use designated initializers with std::array or std::vector?

A: No, designated initializers only work with aggregate types (plain structs/arrays). Use traditional initialization for STL containers.

// ❌ Error: std::array is not a plain array
// std::array<int, 3> arr = {.[0] = 1, .[1] = 2};

// ✅ Traditional initialization
std::array<int, 3> arr = {1, 2, 3};

Q9: How do I migrate existing code?

A: Gradually replace traditional initialization with designated initializers:

  1. Start with configuration structs
  2. Add default values to members
  3. Update initialization sites one by one
  4. Enable -Wc++20-designator warning to catch issues

Q10: Where can I learn more about Designated Initializers?

A:

Related posts: Aggregate Initialization, Uniform Initialization, List Initialization.

One-line summary: C++20 Designated Initializers make struct initialization clear and safe by explicitly naming members, with zero runtime overhead.

## 같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

- [C++ Aggregate Initialization 완벽 가이드 | 집합 초기화](/blog/cpp-aggregate/)
- [C++ 균일 초기화 | "Uniform Initialization" 가이드](/blog/cpp-uniform-initialization/)
- [C++ List Initialization | "리스트 초기화" 가이드](/blog/cpp-list-initialization/)

---

---

## 이 글에서 다루는 키워드 (관련 검색어)

C++, initializer, cpp20, struct, syntax, aggregate 등으로 검색하시면 이 글이 도움이 됩니다.