Sebuah function biasa punya aturan tegas: sekali dipanggil, ia berjalan sampai selesai, lalu seluruh variabel lokalnya dihapus dari memori. Coroutine melonggarkan aturan ini — fungsi bisa berhenti sementara di tengah jalan, menyerahkan satu nilai ke pemanggil, lalu dilanjutkan tepat dari titik berhentinya dengan state lokal masih utuh. C++20 membawa tiga kata kunci untuk itu (co_yield, co_await, co_return), tetapi pada awalnya pemakaiannya mengharuskan kita menulis mesin bernama promise type yang panjang dan penuh detail. C++23 mengubah cerita dengan std::generator: return type siap pakai yang menyembunyikan seluruh mesin itu, sehingga generator deret bisa ditulis dalam beberapa baris — dan di toolchain course ini (g++ 14.2) semuanya langsung jalan.

Tiga Kata Kunci Coroutine #

Aturan standarnya sederhana: function yang memuat salah satu kata kunci berikut otomatis menjadi coroutine, dan compiler menuntut return type yang mendukung (misalnya std::generator untuk dua kata kunci pertama).

Kata kunciPeranReturn type lazim
co_yieldSerahkan satu nilai, lalu berhenti sampai diminta lagistd::generator<T>
co_returnAkhiri coroutine (deret selesai / tugas selesai)std::generator<T>, std::expected, task type
co_awaitTunggu operasi asinkron selesai tanpa memblok threadtask/awaitable dari pustaka

co_yield dan co_return sudah cukup untuk chapter ini; co_await adalah pintu ke pemrograman asinkron (I/O jaringan, task) yang membutuhkan pustaka tambahan, jadi cukup kita kenali bentuknya.

Deret Malas dengan std::generator #

Program berikut memuat tiga generator: deret kuadrat berhingga, deret Fibonacci tak hingga, dan pemecah kalimat menjadi aliran kata. Ketiganya diiterasi seperti container biasa dan bahkan bisa dirangkai dengan views.

cpp
// pengantar-coroutines.cpp — std::generator: deret malas tanpa promise type manual
// Kompilasi: g++ -std=c++23 -Wall -Wextra pengantar-coroutines.cpp -o coroutines

#include <generator>
#include <print>
#include <ranges>
#include <string>
#include <string_view>

// Generator berhingga: co_yield menyerahkan satu nilai, lalu fungsi berhenti
std::generator<int> kuadrat(int n) {
    for (int i = 1; i <= n; ++i) {
        co_yield i * i;   // keluarkan nilai, jeda di sini sampai diminta lagi
    }
    // akhir badan fungsi = co_return implisit: deret selesai
}

// Generator tak hingga: state lokal tetap hidup antar pemanggilan
std::generator<long long> fibonacci() {
    long long a = 0, b = 1;
    while (true) {
        co_yield a;
        long long berikut = a + b;
        a = b;
        b = berikut;
    }
}

// Generator string: memecah kalimat menjadi aliran kata
std::generator<std::string> kata(std::string_view kalimat) {
    std::string sedangDibaca;
    for (char c : kalimat) {
        if (c == ' ') {
            if (!sedangDibaca.empty()) {
                co_yield sedangDibaca;
            }
            sedangDibaca.clear();
        } else {
            sedangDibaca += c;
        }
    }
    if (!sedangDibaca.empty()) {
        co_yield sedangDibaca;   // kata terakhir tanpa spasi di belakang
    }
}

int main() {
    std::print("kuadrat 1-5 :");
    for (int q : kuadrat(5)) {
        std::print(" {}", q);
    }
    std::print("\n");

    // Deret tak hingga aman dipakai: kita ambil secukupnya lewat view
    std::print("fibonacci   :");
    for (long long f : fibonacci() | std::views::take(10)) {
        std::print(" {}", f);
    }
    std::print("\n");

    // Pipeline: generator + view menghasilkan panjang tiap kata
    std::print("panjang kata:");
    for (std::size_t n : kata("belajar cpp modern dengan tenang")
                             | std::views::transform([](const std::string& k) {
                                   return k.size();
                               })) {
        std::print(" {}", n);
    }
    std::print("\n");
    return 0;
}

Output yang Dihasilkan:

text
kuadrat 1-5 : 1 4 9 16 25
fibonacci   : 0 1 1 2 3 5 8 13 21 34
panjang kata: 7 3 6 6 6

Mari kita bedah:

  1. Baris 11-16 (kuadrat): return type std::generator<int> dan satu co_yield di dalamnya — itu saja. Setiap kali main meminta elemen berikutnya, fungsi berjalan hingga co_yield i * i, menyerahkan nilai, lalu membeku di baris itu; pemanggilan berikutnya melanjutkan dari for yang sama.
  2. Baris 15: akhir badan fungsi berarti co_return implisit — generator menyatakan deret selesai, dan range-for di main berhenti rapi.
  3. Baris 19-27 (fibonacci): loop while (true) membuat deret tak hingga, dan variabel a, b tetap hidup di antara dua permintaan nilai — inilah pembeda utama dengan function biasa. Tidak ada angka yang dihitung sebelum diminta: pemanggilan fibonacci() sendiri belum mengeksekusi apa pun.
  4. Baris 56 (take(10)): deret tak hingga menjadi aman karena views::take menghentikan permintaan setelah sepuluh elemen; coroutine dihentikan dengan bersih ketika objek generator-nya dihapus di akhir loop.
  5. Baris 30-45 (kata): generator juga cocok sebagai pembangkit aliran — di sini kalimat dipecah menjadi kata satu per satu, tanpa menampung seluruh hasil ke vector terlebih dahulu.
  6. Baris 63-67 (pipeline): kata(...) diperlakukan sebagai range, lalu dirantai dengan views::transform — coroutines dan ranges memang dirancang saling melengkapi, dan hasilnya tercetak 7 3 6 6 6 (panjang tiap kata “belajar cpp modern dengan tenang”).

Di Balik Layar: Promise Type, Sekilas #

Sebelum C++23, return type generator harus kita bangun sendiri, dan inti bangunannya bernama promise type — sebuah class yang diperiksa compiler untuk mengatur tiga momen: coroutine dimulai, co_yield/co_return dieksekusi, dan coroutine selesai. Versi yang disederhanakan sekali:

cpp Ilustrasi inti promise_type — disederhanakan, bukan kode lengkap
template <typename T>
struct promise_type {
    T nilaiSaatIni;

    std::generator<T> get_return_object();        // objek yang dipegang pemanggil
    std::suspend_always yield_value(T nilai) {    // dijalankan tiap co_yield
        nilaiSaatIni = nilai;                     // simpan nilai yang diserahkan
        return {};                                // lalu JEDA coroutine di sini
    }
    std::suspend_always initial_suspend() { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() {}                         // dijalankan saat co_return
    void unhandled_exception() { std::terminate(); }
};

Dua hal perlu digarisbawahi: yield_value adalah penerima co_yield — ia menyimpan nilai lalu mengembalikan awaiter yang menyuruh coroutine berhenti; dan get_return_object adalah penghubung ke objek generator yang dipegang pemanggil. Detailnya (handle, iterator, alokasi frame) panjang dan mudah salah — itulah kenapa std::generator berharga: seluruh bagian di atas sudah ditulis, diuji, dan dioptimalkan oleh pustaka standar.

Kapan Coroutines Layak Dipakai #

Tiga kegunaan paling umum di kode nyata: deret malas (angka prima berikutnya, baris berikutnya dari log) yang tidak boleh dihitung semuanya di depan; pipeline transformasi data di mana tahapan filter/transform dirangkai seperti pada contoh kata + transform; dan operasi asinkron lewat co_await — gaya menulis kode jaringan yang terbaca berurutan tanpa callback berlapis. Untuk dua kegunaan pertama, std::generator sudah cukup; untuk yang ketiga, kita menunggu task type standar atau memakai pustaka eksternal, dan co_await-nya mengikuti prinsip yang sama seperti yang sudah kita lihat.

Generator memberi kita cara baru menata aliran data — berhenti, lanjut, dan menyerahkan nilai di tengah jalan. Berikutnya kita pindah ke perubahan yang bekerja di level paling dasar: cara compiler menemukan dan menghubungkan kode lewat modules, fitur besar yang menantang era header #include.

Rujukan resmi: cppreference — coroutines , cppreference — std::generator .