콘텐츠로 이동

C++23 std::generator — 코루틴 기반 시퀀스 생성

std::generator<T>는 C++23에서 도입된 코루틴 기반 범위(range) 생성기입니다. co_yield로 값을 하나씩 생산하고 호출자가 가져갈 때까지 실행을 중단합니다. Python의 yield 생성기와 동일한 개념입니다.


#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
}

#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 8

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 << ' '; // 중위 순회 결과

#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;
}

#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);
}

항목std::generator직접 코루틴 구현
코드 양최소많음 (promise_type 등)
성능최적화됨직접 제어 가능
ranges 호환기본 지원직접 구현 필요
적합 용도시퀀스 생성복잡한 비동기 흐름

7. 컴파일러 지원 현황 (2025년 기준)

섹션 제목: “7. 컴파일러 지원 현황 (2025년 기준)”
  • GCC 14+: 지원
  • Clang 18+: 지원 (-std=c++23)
  • MSVC 19.38+: 지원
Terminal window
g++ -std=c++23 -O2 main.cpp

#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 복사 없이 참조 전달
}
}

// 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) 스택 사용

#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 호환기본 제공직접 구현 필요
참조 yieldgenerator<T, T&> 지원직접 설계
재귀 위임elements_of 기본 지원복잡한 수동 체이닝
성능표준 라이브러리 최적화직접 최적화 가능

std::generator는 지연 평가 시퀀스를 직관적인 코루틴 문법으로 구현합니다. 무한 시퀀스, 트리 순회, 대용량 파일 스트리밍 등 “다음 값을 요청할 때만 계산”하는 패턴에 이상적입니다. std::views와 조합하면 강력한 지연 파이프라인을 간결하게 표현할 수 있습니다.

사용 권장 시나리오:

  • 무한 또는 대용량 시퀀스 (메모리 절약)
  • 트리/그래프 순회 (재귀 elements_of)
  • 대용량 파일 스트리밍
  • 복잡한 이터레이터 로직을 단순화할 때