C++23 std::generator — 코루틴 기반 시퀀스 생성
std::generator<T>는 C++23에서 도입된 코루틴 기반 범위(range) 생성기입니다. co_yield로 값을 하나씩 생산하고 호출자가 가져갈 때까지 실행을 중단합니다. Python의 yield 생성기와 동일한 개념입니다.
1. 기본 사용
섹션 제목: “1. 기본 사용”#include <generator>#include <iostream>#include <ranges>
std::generator<int> iota(int start = 0){ while (true) co_yield start++;}
int main(){ // 처음 10개만 소비 for (int v : iota(1) | std::views::take(10)) std::cout << v << ' '; // 1 2 3 4 5 6 7 8 9 10}2. 유한 시퀀스
섹션 제목: “2. 유한 시퀀스”#include <generator>#include <vector>
std::generator<int> fibonacci(){ int a = 0, b = 1; while (true) { co_yield a; auto next = a + b; a = b; b = next; }}
std::generator<int> range(int from, int to, int step = 1){ for (int i = from; i < to; i += step) co_yield i;}
// 사용for (int v : range(0, 10, 2)) std::cout << v << ' '; // 0 2 4 6 83. 재귀 generator — co_yield*
섹션 제목: “3. 재귀 generator — co_yield*”co_yield*로 다른 generator의 모든 값을 위임합니다.
#include <generator>
struct TreeNode { int value; TreeNode* left = nullptr; TreeNode* right = nullptr;};
std::generator<int> inorder(TreeNode* node){ if (!node) co_return;
co_yield std::ranges::elements_of(inorder(node->left)); // 왼쪽 서브트리 co_yield node->value; co_yield std::ranges::elements_of(inorder(node->right)); // 오른쪽 서브트리}
// 사용for (int v : inorder(root)) std::cout << v << ' '; // 중위 순회 결과4. ranges 파이프라인과 연동
섹션 제목: “4. ranges 파이프라인과 연동”#include <generator>#include <ranges>#include <algorithm>
std::generator<int> primes(){ auto is_prime = [](int n) { if (n < 2) return false; for (int i = 2; i * i <= n; ++i) if (n % i == 0) return false; return true; };
for (int n = 2; ; ++n) if (is_prime(n)) co_yield n;}
int main(){ // 처음 10개 소수 auto first10 = primes() | std::views::take(10) | std::ranges::to<std::vector>(); // {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
// 100 이하의 소수 합 int sum = 0; for (int p : primes() | std::views::take_while([](int n){ return n <= 100; })) sum += p;}5. 파일 라인 스트리밍
섹션 제목: “5. 파일 라인 스트리밍”#include <generator>#include <fstream>#include <string>
std::generator<std::string> read_lines(const std::string& path){ std::ifstream file(path); std::string line; while (std::getline(file, line)) co_yield line;}
// 대용량 파일을 메모리에 전부 읽지 않고 처리for (const auto& line : read_lines("large.log") | std::views::filter([](const std::string& l){ return l.contains("ERROR"); })){ process(line);}6. generator vs coroutine 직접 구현
섹션 제목: “6. generator vs coroutine 직접 구현”| 항목 | std::generator | 직접 코루틴 구현 |
|---|---|---|
| 코드 양 | 최소 | 많음 (promise_type 등) |
| 성능 | 최적화됨 | 직접 제어 가능 |
| ranges 호환 | 기본 지원 | 직접 구현 필요 |
| 적합 용도 | 시퀀스 생성 | 복잡한 비동기 흐름 |
7. 컴파일러 지원 현황 (2025년 기준)
섹션 제목: “7. 컴파일러 지원 현황 (2025년 기준)”- GCC 14+: 지원
- Clang 18+: 지원 (
-std=c++23) - MSVC 19.38+: 지원
g++ -std=c++23 -O2 main.cpp8. 값 전달 방식 — 참조 vs 값
섹션 제목: “8. 값 전달 방식 — 참조 vs 값”#include <generator>#include <string>
// 기본: T로 yield — 값 복사std::generator<std::string> generate_names_by_value(){ co_yield std::string("Alice"); co_yield std::string("Bob");}
// std::generator<T, yielded>로 참조 yield — 복사 없음// yielded = T& 또는 const T& 로 설정std::generator<std::string, const std::string&> generate_names_by_ref(){ std::string name = "Alice"; co_yield name; // const 참조로 yield — 복사 없음 name = "Bob"; co_yield name;}
// 대용량 객체는 참조 yield가 성능에 유리struct LargeData { std::array<int, 10000> data{}; };
std::generator<LargeData, const LargeData&> produce_large(){ LargeData item; for (int i = 0; i < 100; ++i) { item.data[0] = i; co_yield item; // 10000개 int 복사 없이 참조 전달 }}9. 성능 특성과 힙 할당 회피
섹션 제목: “9. 성능 특성과 힙 할당 회피”// std::generator는 힙 할당 최소화를 위해 설계됨// 컴파일러 최적화(HALO: Heap Allocation eLision Optimization)가 적용되면// 코루틴 프레임이 스택에 할당될 수 있음
// 성능 팁 1: 불필요한 중간 컨테이너 생성 방지std::generator<int> process_data(const std::vector<int>& data){ for (int v : data) if (v > 0) co_yield v * 2; // 중간 vector 없음 — 지연 평가}
// 성능 팁 2: views와 결합 시 완전한 지연 평가 파이프라인auto pipeline = process_data(huge_data) | std::views::take(100) | std::views::filter([](int n){ return n % 3 == 0; });
// 최대 100개만 처리하며, 조건 불만족 시 건너뜀// huge_data 전체를 순회하지 않음
// 성능 팁 3: 재귀 generator는 스택 깊이 주의// co_yield std::ranges::elements_of(...)는 O(depth) 스택 사용10. 실전 패턴 — CSV 파서
섹션 제목: “10. 실전 패턴 — CSV 파서”#include <generator>#include <fstream>#include <string>#include <sstream>#include <vector>
// 한 줄씩 지연 읽기std::generator<std::string> read_lines(const std::string& path){ std::ifstream file(path); if (!file) co_return;
std::string line; while (std::getline(file, line)) co_yield line;}
// CSV 행을 파싱해 벡터로 반환std::generator<std::vector<std::string>> parse_csv(const std::string& path){ for (const auto& line : read_lines(path)) { std::vector<std::string> fields; std::istringstream ss(line); std::string field; while (std::getline(ss, field, ',')) fields.push_back(field); co_yield fields; }}
// 사용 — 수백만 줄 CSV를 메모리에 전부 올리지 않고 처리for (const auto& row : parse_csv("huge_data.csv") | std::views::drop(1) // 헤더 건너뜀 | std::views::take(1000)) // 최대 1000행만{ process_row(row);}11. std::generator vs 수동 코루틴 구현 비교
섹션 제목: “11. std::generator vs 수동 코루틴 구현 비교”// 수동 구현 — promise_type, coroutine_handle 직접 작성template<typename T>struct ManualGenerator { /* 50~100줄 코드 */ };
ManualGenerator<int> manual_range(int n){ for (int i = 0; i < n; ++i) co_yield i;}
// std::generator — 표준 라이브러리 활용std::generator<int> standard_range(int n){ for (int i = 0; i < n; ++i) co_yield i;}// 코드가 훨씬 간결하고 ranges와 자동 통합| 항목 | std::generator | 수동 코루틴 구현 |
|---|---|---|
| 코드 양 | 함수 본문만 | promise_type 등 50~100줄 추가 |
| ranges 호환 | 기본 제공 | 직접 구현 필요 |
| 참조 yield | generator<T, T&> 지원 | 직접 설계 |
| 재귀 위임 | elements_of 기본 지원 | 복잡한 수동 체이닝 |
| 성능 | 표준 라이브러리 최적화 | 직접 최적화 가능 |
std::generator는 지연 평가 시퀀스를 직관적인 코루틴 문법으로 구현합니다. 무한 시퀀스, 트리 순회, 대용량 파일 스트리밍 등 “다음 값을 요청할 때만 계산”하는 패턴에 이상적입니다. std::views와 조합하면 강력한 지연 파이프라인을 간결하게 표현할 수 있습니다.
사용 권장 시나리오:
- 무한 또는 대용량 시퀀스 (메모리 절약)
- 트리/그래프 순회 (재귀
elements_of) - 대용량 파일 스트리밍
- 복잡한 이터레이터 로직을 단순화할 때