Namespaces
Variants

Coroutines (C++20)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

코루틴은 실행을 일시 중단했다가 나중에 재개할 수 있는 함수입니다. 코루틴은 스택리스(stackless)입니다: 호출자에게 반환하여 실행을 중단하며, 실행을 재개하는 데 필요한 데이터는 스택과 별도로 저장됩니다. 이를 통해 비동기적으로 실행되는 순차적 코드(예: 명시적 콜백 없이 논블로킹 I/O를 처리하기 위해)가 가능하며, 지연 계산되는 무한 시퀀스에 대한 알고리즘과 기타 용도도 지원합니다.

함수는 다음과 같은 요소가 정의에 포함될 경우 코루틴입니다:

  • the co_await 표현식 — 재개될 때까지 실행을 일시 중단함
task<> tcp_echo_server()
{
    char data[1024];
    while (true)
    {
        std::size_t n = co_await socket.async_read_some(buffer(data));
        co_await async_write(socket, buffer(data, n));
    }
}
  • the co_yield 표현식 — 값을 반환하며 실행을 일시 중단하기 위한
generator<unsigned int> iota(unsigned int n = 0)
{
    while (true)
        co_yield n++;
}
  • co_return 문 — 값을 반환하며 실행을 완료합니다
lazy<int> f()
{
    co_return 7;
}

모든 코루틴은 아래에 명시된 여러 요구사항을 충족하는 반환 타입을 가져야 합니다.

목차

제한 사항

코루틴은 가변 인수 , 일반 return 문, 또는 자리 표시자 반환 타입 ( auto 또는 Concept )을 사용할 수 없습니다.

Consteval 함수 , constexpr 함수 , 생성자 , 소멸자 , 그리고 main 함수 는 코루틴이 될 수 없습니다.

실행

각 코루틴은 다음과 연관됩니다

  • 프로미스 객체 , 코루틴 내부에서 조작됩니다. 코루틴은 이 객체를 통해 결과나 예외를 제출합니다. 프로미스 객체는 std::promise 와 아무런 관련이 없습니다.
  • 코루틴 핸들 , 코루틴 외부에서 조작됩니다. 이는 코루틴 실행을 재개하거나 코루틴 프레임을 파괴하는 데 사용되는 비소유 핸들입니다.
  • 코루틴 상태 , 내부적이며 동적으로 할당된 저장 공간(할당이 최적화되지 않는 한)을 포함하는 객체입니다.
  • promise 객체
  • 매개변수들 (모두 값으로 복사됨)
  • 현재 중단 지점의 일부 표현으로, resume이 어디서 계속해야 하는지 알고 destroy가 어떤 지역 변수들이 범위 내에 있었는지 알 수 있도록 함
  • 현재 중단 지점을 가로지르는 수명을 가진 지역 변수들과 임시 객체들

코루틴이 실행을 시작할 때, 다음과 같은 작업을 수행합니다:

  • 코루틴 상태 객체를 operator new 를 사용하여 할당합니다.
  • 모든 함수 매개변수를 코루틴 상태에 복사합니다: 값에 의한 전달 매개변수는 이동되거나 복사되며, 참조에 의한 전달 매개변수는 참조로 유지됩니다(따라서 참조된 객체의 수명이 종료된 후 코루틴이 재개되면 댕글링 참조가 될 수 있습니다 — 예제는 아래 참조).
  • promise 객체의 생성자를 호출합니다. promise 타입이 모든 코루틴 매개변수를 취하는 생성자를 가지고 있는 경우, 해당 생성자가 사후 복사된 코루틴 인자들과 함께 호출됩니다. 그렇지 않으면 기본 생성자가 호출됩니다.
  • promise. get_return_object ( ) 를 호출하고 결과를 지역 변수에 보관합니다. 이 호출의 결과는 코루틴이 처음 일시 중단될 때 호출자에게 반환됩니다. 이 단계까지 발생하는 모든 예외는 promise에 배치되지 않고 호출자에게 전파됩니다.
  • promise. initial_suspend ( ) 를 호출하고 그 결과를 co_await 합니다. 일반적인 Promise 타입은 지연 시작 코루틴의 경우 std::suspend_always 를 반환하거나, 즉시 시작 코루틴의 경우 std::suspend_never 를 반환합니다.
  • co_await promise. initial_suspend ( ) 가 재개될 때, 코루틴의 본문 실행을 시작합니다.

매개변수가 댕글링(dangling)이 되는 몇 가지 예시:

#include <coroutine>
#include <iostream>
struct promise;
struct coroutine : std::coroutine_handle<promise>
{
    using promise_type = ::promise;
};
struct promise
{
    coroutine get_return_object() { return {coroutine::from_promise(*this)}; }
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
};
struct S
{
    int i;
    coroutine f()
    {
        std::cout << i;
        co_return;
    }
};
void bad1()
{
    coroutine h = S{0}.f();
    // S{0} 소멸됨
    h.resume(); // 재개된 코루틴이 std::cout << i를 실행하며, 해제된 후의 S::i를 사용함
    h.destroy();
}
coroutine bad2()
{
    S s{0};
    return s.f(); // 반환된 코루틴은 use after free를 발생시키지 않고는 재개할 수 없음
}
void bad3()
{
    coroutine h = [i = 0]() -> coroutine // 코루틴이기도 한 람다
    {
        std::cout << i;
        co_return;
    }(); // 즉시 호출됨
    // 람다 소멸됨
    h.resume(); // 해제된 후의 (익명 람다 타입)::i를 사용함
    h.destroy();
}
void good()
{
    coroutine h = [](int i) -> coroutine // i를 코루틴 매개변수로 만듦
    {
        std::cout << i;
        co_return;
    }(0);
    // 람다 소멸됨
    h.resume(); // 문제 없음, i가 값에 의한 매개변수로 코루틴 프레임에 복사됨
                // 프레임에 값에 의한 매개변수로 복사됨
    h.destroy();
}

코루틴이 일시 중단 지점에 도달했을 때

  • 이전에 획득한 반환 객체는, 필요한 경우 코루틴의 반환 타입으로 암시적 변환된 후 호출자/재개자에게 반환됩니다.

코루틴이 co_return 문에 도달하면 다음을 수행합니다:

  • promise. return_void ( ) 를 호출합니다
  • co_return ;
  • co_return expr ; 여기서 expr 의 타입은 void 입니다
  • 또는 promise. return_value ( expr ) 를 호출합니다 - co_return expr ; 의 경우, 여기서 expr 이 void가 아닌 타입을 가질 때
  • 자동 저장 기간을 가진 모든 변수들을 생성된 역순으로 파괴합니다
  • promise. final_suspend ( ) 를 호출하고 그 결과를 co_await 합니다

코루틴의 끝까지 도달하는 것은 co_return ; 와 동일하지만, Promise 범위 내에서 return_void 선언을 찾을 수 없는 경우 동작은 정의되지 않습니다. 함수 본문에 정의 키워드가 하나도 없는 함수는 반환 타입과 관계없이 코루틴이 아니며, 끝까지 도달했을 때 반환 타입이 (cv 한정자가 있을 수 있는) void 가 아닌 경우 동작은 정의되지 않습니다.

// task가 어떤 코루틴 task 타입이라고 가정
task<void> f()
{
    // 코루틴이 아니므로 정의되지 않은 동작
}
task<void> g()
{
    co_return;  // OK
}
task<void> h()
{
    co_await g();
    // OK, 암시적 co_return;
}

코루틴이 잡히지 않은 예외로 종료되면 다음을 수행합니다:

  • 예외를 포착하고 catch 블록 내에서 promise. unhandled_exception ( ) 를 호출합니다
  • promise. final_suspend ( ) 를 호출하고 결과를 co_await 합니다 (예: 연속 실행을 재개하거나 결과를 게시하기 위해). 이 시점에서 코루틴을 재개하는 것은 정의되지 않은 동작입니다.

코루틴 상태가 파괴될 때, 즉 co_return 이나 포착되지 않은 예외로 인해 종료되었거나, 핸들을 통해 파괴된 경우 다음과 같은 작업을 수행합니다:

  • promise 객체의 소멸자를 호출합니다.
  • 함수 매개변수 복사본들의 소멸자들을 호출합니다.
  • 코루틴 상태에 사용된 메모리를 해제하기 위해 operator delete 를 호출합니다.
  • 실행을 호출자/재개자에게 되돌립니다.

동적 할당

코루틴 상태는 비배열 operator new 를 통해 동적으로 할당됩니다.

만약 Promise 타입이 클래스 수준의 대체를 정의하면, 그것이 사용될 것이며, 그렇지 않으면 전역 operator new 가 사용될 것입니다.

만약 Promise 타입이 추가 매개변수를 취하는 operator new 의 배치 형태를 정의하고, 첫 번째 인자가 요청된 크기(타입 std::size_t )이며 나머지가 코루틴 함수 인자와 일치하는 인자 목록과 매치된다면, 해당 인자들은 operator new 에 전달됩니다 (이를 통해 코루틴에 대해 선행-할당자-규약 을 사용할 수 있습니다).

operator new 호출은 다음과 같은 경우 최적화되어 제거될 수 있습니다(사용자 정의 할당자를 사용하는 경우에도).

  • 코루틴 상태의 수명은 호출자의 수명 내에서 엄격하게 중첩되며,
  • 코루틴 프레임의 크기는 호출 지점에서 알려져 있습니다.

이 경우, 코루틴 상태는 호출자의 스택 프레임(호출자가 일반 함수인 경우) 또는 코루틴 상태(호출자가 코루틴인 경우)에 내장됩니다.

할당이 실패하면 코루틴은 std::bad_alloc 을(를) 던집니다. 단, Promise 타입이 멤버 함수 Promise :: get_return_object_on_allocation_failure ( ) 을 정의한 경우는 예외입니다. 해당 멤버 함수가 정의된 경우, 할당은 operator new 의 nothrow 형식을 사용하며, 할당 실패 시 코루틴은 즉시 Promise :: get_return_object_on_allocation_failure ( ) 에서 얻은 객체를 호출자에게 반환합니다. 예를 들어:

struct Coroutine::promise_type
{
    /* ... */
    // 비-던지기 operator-new 사용 보장
    static Coroutine get_return_object_on_allocation_failure()
    {
        std::cerr << __func__ << '\n';
        throw std::bad_alloc(); // 또는, return Coroutine(nullptr);
    }
    // 사용자 정의 비-던지기 new 오버로드
    void* operator new(std::size_t n) noexcept
    {
        if (void* mem = std::malloc(n))
            return mem;
        return nullptr; // 할당 실패
    }
};

Promise

Promise 타입은 컴파일러가 코루틴의 반환 타입으로부터 std::coroutine_traits 를 사용하여 결정합니다.

공식적으로, 다음과 같이 정의합니다.

  • R Args... 는 각각 코루틴의 반환 타입과 매개변수 타입 목록을 나타냅니다,
  • ClassT 는 비정적 멤버 함수로 정의된 경우 코루틴이 속한 클래스 타입을 나타냅니다,
  • cv 는 비정적 멤버 함수로 정의된 경우 함수 선언 에서 선언된 cv 한정자를 나타냅니다,

Promise 유형은 다음과 같이 결정됩니다:

예를 들어:

코루틴이 다음과 같이 정의된 경우 ... 해당 Promise 타입은 ...
task < void > foo ( int x ) ; std:: coroutine_traits < task < void > , int > :: promise_type
task < void > Bar :: foo ( int x ) const ; std:: coroutine_traits < task < void > , const Bar & , int > :: promise_type
task < void > Bar :: foo ( int x ) && ; std:: coroutine_traits < task < void > , Bar && , int > :: promise_type

co_await

단항 연산자 co_await 는 코루틴을 일시 중단하고 호출자에게 제어권을 반환합니다.

co_await expr

co_await 표현식은 일반 potentially-evaluated 표현식 내에서만 정규 함수 본문 (람다 표현식의 함수 본문을 포함하여) 안에 나타날 수 있으며, 다음 위치에서는 나타날 수 없습니다

  • 핸들러 내에서,
  • 선언 문에서, 단 해당 선언 문의 초기화 부분에 나타나는 경우를 제외하고,
  • 단순 선언 init-statement 에서 ( if , switch , for 및 [[../range- for |range- for ]] 참조), 단 해당 init-statement 의 초기화 부분에 나타나는 경우를 제외하고,
  • 기본 인자 에서, 또는
  • 정적 또는 스레드 저장 기간 을 가진 블록 범위 변수의 초기화 부분에서.

co_await 표현식은 잠재적으로 평가되는 하위 표현식으로 계약 어설션 의 조건식에 사용될 수 없습니다.

(since C++26)

먼저, expr 은 다음과 같이 awaitable로 변환됩니다:

  • expr 이 초기 일시 중단 지점, 최종 일시 중단 지점 또는 yield 표현식에 의해 생성된 경우, awaitable은 원본 그대로의 expr 입니다.
  • 그렇지 않고 현재 코루틴의 Promise 타입에 await_transform 멤버 함수가 있는 경우, awaitable은 promise. await_transform ( expr ) 입니다.
  • 그렇지 않은 경우, awaitable은 원본 그대로의 expr 입니다.

그런 다음, 다음과 같이 awaiter 객체를 획득합니다:

  • operator co_await 에 대한 오버로드 해상도가 단일 최적 오버로드를 제공하는 경우, awaiter는 해당 호출의 결과입니다:
  • awaitable. operator co_await ( ) 멤버 오버로드의 경우,
  • operator co_await ( static_cast < Awaitable && > ( awaitable ) ) 비멤버 오버로드의 경우.
  • 그렇지 않고, 오버로드 해결이 operator co_await 를 찾지 못하면, awaiter는 있는 그대로 awaitable입니다.
  • 그렇지 않고, 오버로드 해결이 모호하면 프로그램은 잘못된 형식입니다.

위의 표현식이 prvalue 인 경우, awaiter 객체는 그것으로부터 glvalue 인 경우, awaiter 객체는 그것이 참조하는 객체입니다.

그런 다음, awaiter. await_ready ( ) 가 호출됩니다 (이는 결과가 준비되었거나 동기적으로 완료될 수 있다고 알려진 경우 일시 중단 비용을 피하기 위한 단축 경로입니다). 부울 값으로 문맥적으로 변환된 결과가 false 인 경우,

코루틴이 일시 중단됩니다 (해당 코루틴 상태는 지역 변수와 현재 중단 지점으로 채워집니다).
awaiter. await_suspend ( handle ) 가 호출됩니다. 여기서 handle은 현재 코루틴을 나타내는 코루틴 핸들입니다. 이 함수 내에서 일시 중단된 코루틴 상태는 해당 핸들을 통해 관찰 가능하며, 이 함수의 책임은 이를 어떤 실행기에서 재개하도록 스케줄링하거나 파괴하는 것입니다 (false를 반환하는 것도 스케줄링으로 간주됩니다).
  • 만약 await_suspend void 를 반환하면, 제어는 현재 코루틴의 호출자/재개자에게 즉시 반환됩니다 (이 코루틴은 계속 일시 중단된 상태로 유지됩니다). 그렇지 않으면
  • 만약 await_suspend bool 를 반환하면,
  • true 는 현재 코루틴의 호출자/재개자에게 제어를 반환합니다.
  • false 는 현재 코루틴을 재개합니다.
  • 만약 await_suspend 이 다른 코루틴의 코루틴 핸들을 반환하면, 해당 핸들이 재개됩니다 ( handle. resume ( ) 호출을 통해) (이는 결국 현재 코루틴이 재개되도록 연쇄적으로 발생할 수 있습니다).
  • 만약 await_suspend 이 예외를 던지면, 예외가 포착되고 코루틴이 재개된 후 예외가 즉시 다시 던져집니다.

마지막으로, awaiter. await_resume ( ) 가 호출되고 (코루틴이 일시 중단되었는지 여부와 관계없이), 그 결과가 전체 co_await expr 표현식의 결과가 됩니다.

코루틴이 co_await 표현식에서 일시 중단된 후, 나중에 재개되는 경우, 재개 지점은 awaiter. await_resume ( ) 호출 바로 전입니다.

코루틴이 awaiter. await_suspend ( ) 에 진입하기 전에 완전히 일시 중단된다는 점에 유의하십시오. 해당 핸들은 다른 스레드와 공유될 수 있으며 await_suspend ( ) 함수가 반환되기 전에 재개될 수 있습니다. (기본 메모리 안전성 규칙이 여전히 적용되므로, 락 없이 코루틴 핸들이 여러 스레드 간에 공유되는 경우 awaiter는 최소한 release semantics 를 사용해야 하며, 재개 측에서는 최소한 acquire semantics 를 사용해야 합니다.) 예를 들어, 코루틴 핸들을 콜백 내에 배치하여 비동기 I/O 작업이 완료될 때 스레드 풀에서 실행되도록 예약할 수 있습니다. 이 경우 현재 코루틴이 이미 재개되어 awaiter 객체의 소멸자가 실행되었을 수 있으므로, await_suspend ( ) 가 현재 스레드에서 실행을 계속하는 동안 모든 작업이 동시에 발생할 수 있습니다. 따라서 await_suspend ( ) * this 가 소멸된 것으로 간주하고, 핸들이 다른 스레드에 공개된 후에는 이에 접근하지 않아야 합니다.

예제

#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
auto switch_to_new_thread(std::jthread& out)
{
    struct awaitable
    {
        std::jthread* p_out;
        bool await_ready() { return false; }
        void await_suspend(std::coroutine_handle<> h)
        {
            std::jthread& out = *p_out;
            if (out.joinable())
                throw std::runtime_error("Output jthread parameter not empty");
            out = std::jthread([h] { h.resume(); });
            // Potential undefined behavior: accessing potentially destroyed *this
            // std::cout << "New thread ID: " << p_out->get_id() << '\n';
            std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
        }
        void await_resume() {}
    };
    return awaitable{&out};
}
struct task
{
    struct promise_type
    {
        task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};
task resuming_on_new_thread(std::jthread& out)
{
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
int main()
{
    std::jthread out;
    resuming_on_new_thread(out);
}

가능한 출력:

Coroutine started on thread: 139972277602112
New thread ID: 139972267284224
Coroutine resumed on thread: 139972267284224

참고: awaiter 객체는 코루틴 상태의 일부이며(생명주기가 일시 중단 지점을 가로지르는 임시 객체로서), co_await 표현식이 완료되기 전에 파괴됩니다. 이는 추가적인 동적 할당 없이 일부 비동기 I/O API에서 요구하는 작업별 상태를 유지하는 데 사용될 수 있습니다.

표준 라이브러리는 두 가지 간단한 awaitable을 정의합니다: std::suspend_always std::suspend_never .

promise_type :: await_transform 및 프로그램 제공 어웨이터 데모

예제

#include <cassert>
#include <coroutine>
#include <iostream>
struct tunable_coro
{
    // 생성자 매개변수를 통해 "준비 상태"가 결정되는 awaiter.
    class tunable_awaiter
    {
        bool ready_;
    public:
        explicit(false) tunable_awaiter(bool ready) : ready_{ready} {}
        // 세 가지 표준 awaiter 인터페이스 함수:
        bool await_ready() const noexcept { return ready_; }
        static void await_suspend(std::coroutine_handle<>) noexcept {}
        static void await_resume() noexcept {}
    };
    struct promise_type
    {
        using coro_handle = std::coroutine_handle<promise_type>;
        auto get_return_object() { return coro_handle::from_promise(*this); }
        static auto initial_suspend() { return std::suspend_always(); }
        static auto final_suspend() noexcept { return std::suspend_always(); }
        static void return_void() {}
        static void unhandled_exception() { std::terminate(); }
        // 사용자가 제공하는 커스텀 awaiter를 반환하는 변환 함수:
        auto await_transform(std::suspend_always) { return tunable_awaiter(!ready_); }
        void disable_suspension() { ready_ = false; }
    private:
        bool ready_{true};
    };
    tunable_coro(promise_type::coro_handle h) : handle_(h) { assert(h); }
    // For simplicity, declare these 4 special functions as deleted:
    tunable_coro(tunable_coro const&) = delete;
    tunable_coro(tunable_coro&&) = delete;
    tunable_coro& operator=(tunable_coro const&) = delete;
    tunable_coro& operator=(tunable_coro&&) = delete;
    ~tunable_coro()
    {
        if (handle_)
            handle_.destroy();
    }
    void disable_suspension() const
    {
        if (handle_.완료())
            return;
        handle_.promise().disable_suspension();
        handle_();
    }
    bool operator()()
    {
        if (!handle_.완료())
            handle_();
        return !handle_.완료();
    }
private:
    promise_type::coro_handle handle_;
};
tunable_coro generate(int n)
{
    for (int i{}; i != n; ++i)
    {
        std::cout << i << ' ';
        // co_await에 전달된 awaiter는 promise_type::await_transform으로 전달되며
        // 초기에는 일시 중단을 유발하는 tunable_awaiter 문제(호출자에게 반환)
        // 각 반복에서 main), 그러나 disable_suspension 호출 후에는 일시 중단 없음
        // 발생하고 루프가 main()으로 돌아가지 않고 끝까지 실행됩니다.
        co_await std::suspend_always{};
    }
}
int main()
{
    auto coro = generate(8);
    coro(); // 첫 번째 요소가 0인 경우에만 출력
    for (int k{}; k < 4; ++k)
    {
        coro(); // 1 2 3 4를 각 반복마다 하나씩 출력함
        std::cout << ": ";
    }
    coro.disable_suspension();
    coro(); // 5 6 7 테일 번호를 한 번에 출력합니다
}

출력:

0 1 : 2 : 3 : 4 : 5 6 7

co_yield

co_yield 표현식은 호출자에게 값을 반환하고 현재 코루틴을 일시 중단합니다: 이것은 재개 가능한 제너레이터 함수의 공통 구성 요소입니다.

co_yield expr
co_yield braced-init-list

다음과 동일합니다

co_await promise.yield_value(expr)

일반적인 제너레이터의 yield_value 는 인자를 제너레이터 객체에 저장(복사/이동하거나 인자의 수명이 co_await 내의 중단 지점을 가로지르므로 주소만 저장)하고 std::suspend_always 를 반환하여 호출자/재개자에게 제어권을 이전합니다.

#include <coroutine>
#include <cstdint>
#include <exception>
#include <iostream>
template<typename T>
struct Generator
{
    // 클래스 이름 'Generator'는 우리의 선택이며 코루틴에 필수적인 것은 아닙니다
    // 마법. 컴파일러는 'co_yield' 키워드의 존재로 코루틴을 인식합니다.
    // 'MyGenerator' (또는 다른 이름)을 사용할 수 있습니다. 단, 포함하기만 하면 됩니다
    // 'MyGenerator get_return_object()' 메서드를 가진 중첩 구조체 promise_type.
    // (참고: 생성자와 소멸자의 선언을 조정하는 것이 필요합니다
    //  (리네이밍 시 주의하십시오.)
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    struct promise_type // required
    {
        T value_;
        std::exception_ptr exception_;
        Generator get_return_object()
        {
            return Generator(handle_type::from_promise(*this));
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { exception_ = std::current_exception(); } // 저장
                                                                              // 예외
        template<std::convertible_to<T> From> // C++20 concept
        std::suspend_always yield_value(From&& from)
        {
            value_ = std::forward<From>(from); // 결과를 promise에 캐싱
            return {};
        }
        void return_void() {}
    };
    handle_type h_;
    Generator(handle_type h) : h_(h) {}
    ~Generator() { h_.destroy(); }
    explicit operator bool()
    {
        fill(); // 코루틴이 완료되었는지 여부를 안정적으로 알아내는 유일한 방법은,
                // 다음에 생성될 값이 있는지 여부(co_yield)
                // 코루틴에서 C++ getter(아래 operator ())를 통한 접근은 실행/재개를 의미합니다
                // 다음 co_yield 지점까지 코루틴을 실행합니다(또는 끝까지 실행되도록 합니다).
                // 그런 다음 결과를 promise에 저장/캐시하여 getter(아래 operator())가 접근할 수 있도록 합니다
                // 코루틴을 실행하지 않고 가져오기 위해
        return !h_.완료();
    }
    T operator()()
    {
        fill();
        full_ = false; // 이전에 캐시된 내용을 이동합니다
                       // 프라미스를 다시 비우기 위한 결과
        return std::move(h_.promise().value_);
    }
private:
    bool full_ = false;
    void fill()
    {
        if (!full_)
        {
            h_();
            if (h_.promise().exception_)
                std::rethrow_exception(h_.promise().exception_);
            // 호출된 컨텍스트에서 코루틴 예외 전파
            full_ = true;
        }
    }
};
Generator<std::uint64_t>
fibonacci_sequence(unsigned n)
{
    if (n == 0)
        co_return;
    if (n > 94)
        throw std::runtime_error("피보나치 수열이 너무 큽니다. 요소들이 오버플로우될 것입니다.");
    co_yield 0;
    if (n == 1)
        co_return;
    co_yield 1;
    if (n == 2)
        co_return;
    std::uint64_t a = 0;
    std::uint64_t b = 1;
    for (unsigned i = 2; i < n; ++i)
    {
        std::uint64_t s = a + b;
        co_yield s;
        a = b;
        b = s;
    }
}
int main()
{
    try
    {
        auto gen = fibonacci_sequence(10); // uint64_t 오버플로우 전 최대 94
        for (int j = 0; gen; ++j)
            std::cout << "fib(" << j << ")=" << gen() << '\n';
    }
    catch (const std::exception& ex)
    {
        std::cerr << "Exception: " << ex.무엇() << '\n';
    }
    catch (...)
    {
        std::cerr << "알 수 없는 예외.\n";
    }
}

출력:

fib(0)=0
fib(1)=1
fib(2)=1
fib(3)=2
fib(4)=3
fib(5)=5
fib(6)=8
fib(7)=13
fib(8)=21
fib(9)=34

참고 사항

기능 테스트 매크로 표준 기능
__cpp_impl_coroutine 201902L (C++20) 코루틴 (컴파일러 지원)
__cpp_lib_coroutine 201902L (C++20) 코루틴 (라이브러리 지원)
__cpp_lib_generator 202207L (C++23) std::generator : 범위를 위한 동기식 코루틴 생성기

키워드

co_await , co_return , co_yield

라이브러리 지원

코루틴 지원 라이브러리 는 코루틴을 위한 컴파일 타임 및 런타임 지원을 제공하는 여러 타입을 정의합니다.

결함 보고서

다음 동작 변경 결함 보고서는 이전에 발표된 C++ 표준에 소급 적용되었습니다.

DR 적용 대상 게시된 동작 올바른 동작
CWG 2556 C++20 잘못된 return_void 가 코루틴 끝에 도달하는
동작을 정의되지 않음으로 만듦
이 경우 프로그램이
잘못된 형식임
CWG 2668 C++20 co_await 가 람다 표현식에
나타날 수 없었음
허용됨
CWG 2754 C++23 * this 가 명시적 객체 멤버 함수에 대한
promise 객체를 생성할 때 취해짐
* this 는 이 경우
취해지지 않음

참고 항목

(C++23)
동기식 view 코루틴 제너레이터를 나타내는
(클래스 템플릿)

외부 링크

1. Lewis Baker, 2017-2022 - Asymmetric Transfer.
2. David Mazières, 2021 - Tutorial on C++20 coroutines.
3. Chuanqi Xu & Yu Qi & Yao Han, 2021 - C++20 Principles and Applications of Coroutine. (Chinese)
4. Simon Tatham, 2023 - Writing custom C++20 coroutine systems.