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
| Style | Syntax | Notes |
|---|---|---|
| Positional | Point 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 |
| Constructor | Point p(1, 2, 3) | Requires user-provided constructor (non-aggregate) |
| Named parameters pattern | builder.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 등으로 검색하시면 이 글이 도움이 됩니다.