[2026] C++ Initializer-List Constructors | `std::initializer_list` Ctors Explained
이 글의 핵심
Constructors taking std::initializer_list enable `{1,2,3}` initialization. They often win overload resolution over `T(int)` for `{}`—design APIs carefully with vector size vs element-list confusion.
What is an initializer-list constructor?
A constructor that takes std::initializer_list
#include <initializer_list>
#include <vector>
class MyVector {
std::vector<int> data;
public:
MyVector(std::initializer_list<int> list) {
for (int x : list) data.push_back(x);
}
};
int main() {
MyVector vec = {1, 2, 3, 4, 5};
}
Priority over other ctors
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class MyClass {
public:
MyClass(int x) {
std::cout << "int ctor: " << x << "\n";
}
MyClass(std::initializer_list<int> list) {
std::cout << "initializer_list ctor\n";
}
};
int main() {
MyClass a(10); // int ctor
MyClass b{10}; // initializer_list ctor
MyClass c = {10}; // initializer_list ctor
}
Empty braces vs empty list
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class MyClass {
public:
MyClass() { std::cout << "default\n"; }
MyClass(std::initializer_list<int> list) { std::cout << "list\n"; }
};
int main() {
MyClass x; // default
MyClass y{}; // default
MyClass z{{}}; // initializer_list (empty)
}
How it works
The compiler typically allocates a const temporary array for {1,2,3} and passes pointer + length to initializer_list. Do not store an initializer_list past the full-expression unless you copy into a vector. Copies of initializer_list alias the same array.
Relation to std::vector
vector(n, val)vsvector{n, val}— different ctors (size/fill vs two-element list).- Custom types with both (int,int) and initializer_list need clear conventions.
Performance notes
- Passing
initializer_listis cheap; populating avectorfrom it may allocate. - Prefer range ctor or reserve when you know sizes upfront.
Real-world examples
Configuration classes
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Config {
std::map<std::string, std::string> settings;
public:
Config(std::initializer_list<std::pair<const std::string, std::string>> list)
: settings(list) {}
std::string get(const std::string& key) const {
auto it = settings.find(key);
return it != settings.end() ? it->second : "";
}
};
// Usage
Config cfg = {
{"host", "localhost"},
{"port", "8080"},
{"timeout", "30"}
};
Matrix initialization
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T, size_t Rows, size_t Cols>
class Matrix {
std::array<std::array<T, Cols>, Rows> data;
public:
Matrix(std::initializer_list<std::initializer_list<T>> list) {
size_t r = 0;
for (auto row : list) {
size_t c = 0;
for (auto val : row) {
if (r < Rows && c < Cols) data[r][c++] = val;
}
++r;
}
}
};
// Usage
Matrix<int, 2, 3> m = {{1, 2, 3}, {4, 5, 6}};
Builder pattern with initializer_list
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Query {
std::string table;
std::vector<std::string> columns;
public:
Query(std::string t, std::initializer_list<std::string> cols)
: table(std::move(t)), columns(cols) {}
std::string build() const {
std::string result = "SELECT ";
for (size_t i = 0; i < columns.size(); ++i) {
if (i > 0) result += ", ";
result += columns[i];
}
return result + " FROM " + table;
}
};
// Usage
Query q{"users", {"id", "name", "email"}};
std::cout << q.build(); // SELECT id, name, email FROM users
Performance benchmarks
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <chrono>
#include <vector>
#include <iostream>
// Benchmark: initializer_list vs reserve+push_back
void benchmark() {
const int N = 1000000;
// Method 1: initializer_list (compile-time known)
auto start1 = std::chrono::high_resolution_clock::now();
std::vector<int> v1 = {1, 2, 3, 4, 5};
auto end1 = std::chrono::high_resolution_clock::now();
// Method 2: reserve + push_back
auto start2 = std::chrono::high_resolution_clock::now();
std::vector<int> v2;
v2.reserve(5);
for (int i = 1; i <= 5; ++i) v2.push_back(i);
auto end2 = std::chrono::high_resolution_clock::now();
std::cout << "initializer_list: "
<< std::chrono::duration_cast<std::chrono::nanoseconds>(end1 - start1).count() << "ns\n";
std::cout << "reserve+push_back: "
<< std::chrono::duration_cast<std::chrono::nanoseconds>(end2 - start2).count() << "ns\n";
}
Results (typical):
- Small lists (< 10 elements): initializer_list is often faster due to compile-time optimization
- Large lists: reserve+push_back may be better if you need to modify during construction
- Move-only types: initializer_list copies; use other patterns
Common mistakes and fixes
Mistake 1: Storing initializer_list
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ BAD: dangling reference
class Bad {
std::initializer_list<int> list; // Danger!
public:
Bad(std::initializer_list<int> l) : list(l) {}
// list points to temporary array that's gone
};
// ✅ GOOD: copy to vector
class Good {
std::vector<int> data;
public:
Good(std::initializer_list<int> l) : data(l) {}
};
Mistake 2: Ambiguous overloads
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Container {
public:
Container(int size) { /* allocate size */ }
Container(std::initializer_list<int> list) { /* copy elements */ }
};
// What does this do?
Container c{10}; // initializer_list ctor! Not size ctor
// Fix: use parentheses for size
Container c(10); // size ctor
Container c{10, 20}; // clearly initializer_list
Mistake 3: Nested braces confusion
std::vector<std::vector<int>> v1 = {{1, 2}, {3, 4}}; // OK
std::vector<std::vector<int>> v2{{1, 2}, {3, 4}}; // OK
std::vector<std::vector<int>> v3({1, 2}, {3, 4}); // Error! Not a list
Compiler support
| Compiler | Version | initializer_list | Nested lists |
|---|---|---|---|
| GCC | 4.4+ | ✅ | ✅ |
| Clang | 3.1+ | ✅ | ✅ |
| MSVC | 2013+ | ✅ | ✅ |
| ICC | 14.0+ | ✅ | ✅ |
| Note: C++11 feature; all modern compilers support it fully. |
Debugging tips
Tip 1: Print which ctor is called
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Debug {
public:
Debug(int x) {
std::cout << "int ctor: " << x << "\n";
}
Debug(std::initializer_list<int> list) {
std::cout << "list ctor: size=" << list.size() << "\n";
}
};
Debug d1(5); // int ctor: 5
Debug d2{5}; // list ctor: size=1
Debug d3 = {5}; // list ctor: size=1
Tip 2: Use static_assert for type checks
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T>
class TypedList {
public:
TypedList(std::initializer_list<T> list) {
static_assert(std::is_trivially_copyable_v<T>,
"T must be trivially copyable for efficient init");
// ...
}
};
Tip 3: Inspect with compiler explorer
Visit Compiler Explorer and check assembly:
{1,2,3}often generates a static const array- Compiler may inline small lists completely
- Large lists may allocate on stack then copy
Advanced patterns
Pattern 1: Tag dispatch to avoid ambiguity
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct size_tag {};
struct list_tag {};
class SmartVector {
std::vector<int> data;
public:
// Size constructor
SmartVector(size_tag, int size) : data(size) {}
// List constructor
SmartVector(list_tag, std::initializer_list<int> list) : data(list) {}
};
// Usage
SmartVector v1(size_tag{}, 10); // size 10
SmartVector v2(list_tag{}, {1, 2, 3}); // elements 1,2,3
Pattern 2: Perfect forwarding with initializer_list
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
class Wrapper {
std::vector<T> data;
public:
template<typename....Args>
Wrapper(Args&&....args) : data{std::forward<Args>(args)...} {}
// Separate overload for initializer_list
Wrapper(std::initializer_list<T> list) : data(list) {}
};
Pattern 3: Compile-time validation
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T, size_t N>
class FixedArray {
std::array<T, N> data;
public:
constexpr FixedArray(std::initializer_list<T> list) {
if (list.size() != N) {
throw std::invalid_argument("Size mismatch");
}
std::copy(list.begin(), list.end(), data.begin());
}
};
// Compile-time check in C++20
constexpr FixedArray<int, 3> arr = {1, 2, 3}; // OK
// constexpr FixedArray<int, 3> arr2 = {1, 2}; // Compile error
FAQ
Q: When to use?
A: APIs that take “list of values” with homogeneous type. Best for small, known-size collections.
Q: Modifiable?
A: View is read-only—copy to vector or array if you need to mutate.
Q: Performance vs vector?
A: For small lists (< 10 elements), initializer_list is often faster due to compile-time optimization. For large or dynamic lists, use vector with reserve.
Q: How to avoid ambiguity?
A: Use parentheses () for non-list ctors, braces {} for lists. Document conventions in team guidelines.
Resources: Effective Modern C++ Item 7, cppreference initializer_list.
Related posts
- C++ initializer_list
- C++ explicit keyword
- C++ aggregate initialization
- C++ class template argument deduction