std::generator(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

hiii bestie πŸ’– this is std::generator(3) from the C++ Standard Libary β€” manual name folded in near the top, and we're about to make this coroutine generator page make sense ✨

πŸŽ€ NAME πŸŽ€

std::generator - std::generator β€” that's the name, bestie, no cap πŸ’…

πŸ¦‹ Synopsis πŸ¦‹

okay so first: Defined in header <generator>, periodt. Then we got the main character template (1) (since C++23):

template<
    class Ref,
    class V = void,
    class Allocator = void >
class generator
    : public ranges::view_interface<generator<Ref, V, Allocator>>

and the pmr convenience alias (2) (since C++23) is giving:

namespace pmr {
    template< class Ref, class V = void >
    using generator =
        std::generator<Ref, V, std::pmr::polymorphic_allocator<>>;
}
  1. The class template std::generator presents a view of the elements yielded by the evaluation of a coroutine. iconic behavior fr fr πŸ’–
  2. Convenience alias template for the generator using the polymorphic allocator. cute and useful, we love to see it 🫢

A std::generator generates a sequence of elements by repeatedly resuming the coroutine from which it was returned. Each time a co_yield statement is evaluated, the coroutine produces one element of the sequence. When the co_yield statement is of the form co_yield ranges::elements_of(rng), each element of the range rng is successively produced as an element of the sequence. that's the whole vibe ✨

std::generator models view and input_range β€” periodt πŸ’…

🚨 warning, bestie: The behavior of a program that adds a specialization for std::generator is undefined. no cap, don't do it πŸ’€

🌸 Template parameters 🌸

  • Ref β€” the reference type (ranges::range_reference_t) of the generator. If V is void, both the reference type and the value type are inferred from Ref.
  • V β€” the value type (range_value_t) of the generator, or void.
  • Allocator β€” an allocator type or void.
⚠️ If Allocator is not void, then the behavior is undefined if Allocator does not meet the Allocator requirements. read that twice, bestie 😭

✨ Member types ✨

  • value (private) β€” std::conditional_t<std::is_void_v<V>, std::remove_cvref_t<Ref>, V>; (exposition-only member type*)
  • reference (private) β€” std::conditional_t<std::is_void_v<V>, Ref&&, Ref>; (exposition-only member type*)
  • yielded β€” std::conditional_t<std::is_reference_v<reference>, reference, const reference&>

πŸ’… Type requirements πŸ’…

  • std::allocator_traits<Allocator>::pointer is a pointer type.
  • value is a cv-unqualified object type.
  • reference is either a reference type, or a cv-unqualified object type that models copy_constructible.
  • Let /*RRef*/ denote: std::remove_reference_t</*reference*/>&&, if /*reference*/ is a reference type, and /*reference*/ otherwise.
    • std::common_reference_with</*reference*/&&, /*value*/&> is modeled.
    • std::common_reference_with</*reference*/&&, /*RRef*/&&> is modeled.
    • std::common_reference_with</*RRef*/&&, const /*value*/&> is modeled.
πŸ’€ The program is ill-formed if any of these type requirements is not satisfied. not me being dramatic, that's literally what it says.

🧚 Member objects 🧚

  • active_ (private) β€” Internally, each active instance of std::generator is associated with a stack (handled as if by object of type std::unique_ptr<std::stack<std::coroutine_handle<>>>).
    • When begin is called, a new stack is created and the generator is added to the stack.
    • When co_yield ranges::elements_of(rng) is evaluated in a generator body, rng is converted to a generator and added to the stack that contains the enclosing generator.
    • When a generator iterator is incremented, the coroutine at the top of the associated stack is resumed.
    • When a generator finishes (i.e. when final_suspend is called), it is removed from the stack. (exposition-only member object*)
  • coroutine_ (private) β€” a handle of type std::coroutine_handle<promise_type> (exposition-only member object*)

πŸ”₯ Member functions πŸ”₯

  • constructor β€” constructs a generator object (public member function)
  • destructor β€” effectively destroys the entire stack of yielded generators (public member function)
  • operator= β€” assigns a generator object (public member function)
  • begin β€” resumes the initially suspended coroutine and returns an iterator to its handle (public member function)
  • end β€” returns std::default_sentinel (public member function)

🫢 Inherited from std::ranges::view_interface 🫢

  • empty (C++20) β€” returns whether the derived view is empty. Provided if it satisfies sized_range or forward_range. (public member function of std::ranges::view_interface<D>)
  • cbegin (C++23) β€” returns a constant iterator to the beginning of the range. (public member function of std::ranges::view_interface<D>)
  • cend (C++23) β€” returns a sentinel for the constant iterator of the range. (public member function of std::ranges::view_interface<D>)
  • operator bool (C++20) β€” returns whether the derived view is not empty. Provided if ranges::empty is applicable to it. (public member function of std::ranges::view_interface<D>)

🌈 Nested classes 🌈

  • promise_type β€” the promise type (public member class)
  • iterator β€” the iterator type (exposition-only member class*)

⭐ Notes ⭐

  • Feature-test macro: __cpp_lib_generator β€” value 202207L, std (C++23), feature std::generator – synchronous coroutine generator for ranges

πŸ“ Example πŸ“

Can be tried in Compiler Explorer πŸ’–

// Run this code

#include <generator>
#include <iostream>

template<typename T>
struct Tree
{
    T value;
    Tree *left{}, *right{};

    std::generator<const T&> traverse_inorder() const
    {
        if (left)
            for (const T& x : left->traverse_inorder())
                co_yield x;

        co_yield value;
        if (right)
            for (const T& x : right->traverse_inorder())
                co_yield x;
    }
};

int main()
{
    Tree<char> tree[]
    {
                                    {'D', tree + 1, tree + 2},
        //                            β”‚
        //            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        //            β”‚                                β”‚
                    {'B', tree + 3, tree + 4},       {'F', tree + 5,
       tree + 6},
        //            β”‚                                β”‚
        //  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        //  β”‚                       β”‚      β”‚                         β”‚
          {'A'},                  {'C'}, {'E'},
       {'G'}
    };

    for (char x : tree->traverse_inorder())
        std::cout << x << ' ';
    std::cout << '\n';
}

Output:

A B C D E F G

πŸ¦‹ References πŸ¦‹

  • C++23 standard (ISO/IEC 14882:2023):
    • 26.8 Range generators [coro.generator]

πŸ’œ See also πŸ’œ

  • noop_coroutine (C++20) β€” creates a coroutine handle that has no observable effects when resumed or destroyed (function)

source: http://cppreference.com Β· version/date: 2024.06.10 Β· page: std::generator(3) πŸ’–βœ¨

πŸ“¦ where this page comes from