본문으로 건너뛰기
Previous
Next
C++ Designated Initializers (C++20)

C++ Designated Initializers (C++20)

C++ Designated Initializers (C++20)

이 글의 핵심

C++20 designated initializers let you name struct members in brace initialization. Declaration order required; differs from C flexibility. Aggregate-only; compare with list and copy initialization.

What Are Designated Initializers?

Before C++20, initializing a struct with many fields was either verbose (naming each one explicitly in a constructor) or fragile (positional, breaking when you reorder fields):

struct ServerConfig {
    std::string host;
    int port;
    bool ssl;
    int timeoutSeconds;
    int maxConnections;
};

// Pre-C++20 — positional: which arg is which?
ServerConfig cfg{"localhost", 8080, true, 30, 100};

Positional initialization is fragile: if you add a field between port and ssl, every existing initialization in your codebase silently shifts — often compiling fine but passing the wrong values.

Designated initializers (C++20) let you name the fields:

// C++20 — self-documenting, robust to field reordering
ServerConfig cfg = {
    .host = "localhost",
    .port = 8080,
    .ssl = true,
    .timeoutSeconds = 30,
    .maxConnections = 100
};

Now the meaning is clear, and the compiler will catch mismatches between field names and your struct definition.


Basic Syntax

#include <string>
#include <iostream>

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

int main() {
    // All fields named
    Point p1 = {.x = 1, .y = 2, .z = 3};

    // Skip fields — omitted fields are value-initialized (zero for int)
    Point p2 = {.x = 1, .z = 3};
    // p2.y == 0 (value-initialized)

    // Fields must appear in declaration order
    // Point p3 = {.z = 3, .x = 1};  // compile error — wrong order
}

The order constraint is a key difference from C99 designated initializers, which allow out-of-order designators. C++20 requires you to specify fields in their declaration order.


Default Member Initializers Work Together

If a struct has default member initializers, omitted designated fields use those defaults:

struct DatabaseConfig {
    std::string host = "localhost";
    int port = 5432;
    std::string database;          // no default — empty string
    int connectionPoolSize = 10;
    bool sslMode = false;
};

// Only override what differs from defaults
DatabaseConfig dev = {
    .database = "myapp_dev"
    // host = "localhost", port = 5432, connectionPoolSize = 10, sslMode = false
};

DatabaseConfig prod = {
    .host = "db.example.com",
    .database = "myapp_prod",
    .connectionPoolSize = 50,
    .sslMode = true
};

This pattern is excellent for configuration structs where most settings have sensible defaults and you only need to override a few.


Nested Structs

Nested structs use nested designated initializers:

struct Address {
    std::string street;
    std::string city;
    std::string country = "USA";
    int zipCode;
};

struct Person {
    std::string name;
    int age;
    Address address;
    bool active = true;
};

Person alice = {
    .name = "Alice Smith",
    .age = 30,
    .address = {
        .street = "123 Main St",
        .city = "Austin",
        .zipCode = 78701
        // .country defaults to "USA"
    }
    // .active defaults to true
};

std::cout << alice.name << " lives in " << alice.address.city << '\n';
// Alice Smith lives in Austin

Aggregate Requirements

Designated initializers only work with aggregate types. A type is an aggregate when it has:

  • No user-provided constructors
  • No private or protected non-static data members
  • No virtual functions
  • No virtual, private, or protected base classes
// Aggregate — designated init works
struct Config {
    int width;
    int height;
};

Config c = {.width = 1920, .height = 1080};  // OK

// NOT aggregate — has user-provided constructor
struct Widget {
    int width;
    int height;
    Widget(int w, int h) : width(w), height(h) {}  // user ctor
};

// Widget w = {.width = 100, .height = 50};  // compile error — not an aggregate
Widget w(100, 50);  // must use constructor

If you need invariants or complex initialization logic, use a constructor. If you’re just grouping data fields, keep it an aggregate and benefit from designated initializers.


Comparison with Other Initialization Styles

StyleSyntaxNotes
PositionalPoint p{1, 2, 3}Compact but fragile to field reordering
Designated (C++20)Point p{.x=1, .y=2, .z=3}Self-documenting, order must match declaration
ConstructorPoint p(1, 2, 3)Requires user-provided constructor (non-aggregate)
Named parameters patternbuilder.x(1).y(2).z(3)Fluent API, flexible order, more boilerplate

For large config structs with many optional fields, designated initializers are almost always the clearest choice.


Practical Examples

HTTP Request Configuration

struct HttpRequest {
    std::string method = "GET";
    std::string url;
    std::string body;
    int timeoutMs = 5000;
    bool followRedirects = true;
    int maxRedirects = 5;
};

// Reading a resource — mostly defaults
HttpRequest get = {
    .url = "https://api.example.com/users"
};

// Posting data — override method, body, timeout
HttpRequest post = {
    .method = "POST",
    .url = "https://api.example.com/users",
    .body = R"({"name":"Alice","email":"[email protected]"})",
    .timeoutMs = 10000
};

Test Data Setup

Designated initializers are particularly useful in tests, where you want to make the intent of each test case clear:

struct Employee {
    std::string name;
    std::string department;
    int salary;
    bool isManager = false;
    int yearsExperience = 0;
};

void test_manager_bonus_calculation() {
    Employee manager = {
        .name = "Alice",
        .department = "Engineering",
        .salary = 100000,
        .isManager = true,
        .yearsExperience = 8
    };

    Employee junior = {
        .name = "Bob",
        .department = "Engineering",
        .salary = 70000,
        .yearsExperience = 1
        // .isManager defaults to false
    };

    // Test bonus logic...
}

Arrays of Structs

struct MenuItem {
    std::string name;
    double price;
    bool vegetarian = false;
};

MenuItem menu[] = {
    {.name = "Caesar Salad",   .price = 12.99, .vegetarian = true},
    {.name = "Grilled Salmon", .price = 24.99},
    {.name = "Veggie Burger",  .price = 14.99, .vegetarian = true},
    {.name = "Ribeye Steak",   .price = 39.99}
};

Common Mistakes

Mistake 1: Out-of-order designators

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

// Wrong — z before x
// Point p = {.z = 3, .x = 1};  // compile error

// Correct — same order as declaration
Point p = {.x = 1, .z = 3};  // y is zero-initialized

Mistake 2: Trying to use with non-aggregate types

#include <vector>

// std::vector is not an aggregate — has user constructors
// std::vector<int> v = {.size = 5};  // compile error

// For non-aggregates, use the regular constructor or named parameter pattern
std::vector<int> v(5, 0);  // 5 elements, all zero

Mistake 3: Forgetting that class with virtual functions is not aggregate

struct Base {
    virtual void process() {}
    int data = 0;
};

// Base is not an aggregate (has virtual function)
// Base b = {.data = 42};  // compile error
Base b;
b.data = 42;

Mistake 4: Struct refactor breaks designator names

struct Config {
    int maxConnections;  // renamed from "connections"
};

// Config c = {.connections = 100};  // compile error — field renamed
Config c = {.maxConnections = 100};   // update designators after rename

This is a feature, not a bug — the compile error tells you where to update call sites. With positional initialization, a rename might silently succeed while matching the wrong field.


Enabling C++20

Designated initializers require C++20. Enable it with:

# GCC / Clang
g++ -std=c++20 main.cpp

# CMake
set(CMAKE_CXX_STANDARD 20)

GCC 8+ and Clang 9+ have partial support; full C++20 support is in GCC 10 and Clang 10+.


Key Takeaways

  • Designated initializers name struct fields in brace initialization: .field = value
  • Fields must appear in declaration order — out-of-order is a compile error (unlike C99)
  • Only aggregates — no user constructors, no private members, no virtual functions
  • Omitted fields are value-initialized (zero for scalars) or use default member initializers if present
  • Excellent for configuration structs, test data setup, and any struct with many optional fields
  • The compile error on unknown field names is a feature — refactoring is safer than with positional initialization

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++20 designated initializers let you name struct members in brace initialization. Declaration order required; differs f… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


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

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

  • [C++ Copy Initialization: The = Form, explicit, and Copy](/en/blog/cpp-copy-initialization/
  • [C++ map vs unordered_map: When to Use Each and How They Work](/en/blog/cpp-stl-map-unordered-map/
  • [C++ Functions: Parameters, Return Values, Overloading, and](/en/blog/cpp-function-basics/

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

C++, designated initializers, C++20, struct, aggregate 등으로 검색하시면 이 글이 도움이 됩니다.