├── CMakeLists.txt ├── LICENSE ├── README.md ├── gcc-coroutine-example.cpp ├── generator.h └── main.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | 3 | #set(CMAKE_C_COMPILER "gcc") 4 | #set(CMAKE_CXX_COMPILER "g++") 5 | 6 | # set option to link the C++ std lib (for g++ or clang++) 7 | if(DEFINED CMAKE_CXX_COMPILER) 8 | message(STATUS "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}") 9 | if(CMAKE_CXX_COMPILER MATCHES "^(.*/)?g\\+\\+$") 10 | set(CXX_LIB_OPTN "-static-libstdc++") 11 | elseif(CMAKE_CXX_COMPILER MATCHES "^(.*/)?clang\\+\\+$") 12 | set(CXX_LIB_OPTN "-stdlib=libc++") 13 | else() 14 | message(STATUS "unknown C++ compiler: CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}") 15 | endif() 16 | endif() 17 | message(STATUS "CXX_LIB_OPTN=${CXX_LIB_OPTN}") 18 | 19 | project(coroutines) 20 | 21 | if(DEFINED ENV{OS} AND "$ENV{OS}" STREQUAL "Windows_NT") 22 | set(OS_PLATFORM "win32") 23 | else() 24 | set(OS_PLATFORM "linux") 25 | endif() 26 | 27 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wno-unknown-pragmas -std=c11") 28 | 29 | set(CMAKE_C_STANDARD 11) 30 | 31 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas -std=c++20 ${CXX_LIB_OPTN}") 32 | message(STATUS "CMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}") 33 | 34 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 35 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_DEBUG") 36 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_DEBUG") 37 | endif() 38 | 39 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,'$ORIGIN/'") 40 | 41 | set(SOURCE_FILES main.cpp) 42 | 43 | SET(LIBRARY_OUTPUT_PATH "${coroutines_SOURCE_DIR}/${CMAKE_BUILD_TYPE}") 44 | 45 | SET(EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH}") 46 | 47 | add_executable(${PROJECT_NAME} ${SOURCE_FILES}) 48 | 49 | #target_link_libraries(${PROJECT_NAME} rt dl pthread) 50 | 51 | set_target_properties(${PROJECT_NAME} PROPERTIES 52 | RUNTIME_OUTPUT_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}" 53 | ) 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 roger-dv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exploring C++20 coroutines 2 | 3 | ## Introduction 4 | 5 | A simple program implementing one specific capability of C++20 coroutines[1](#fn1) — a generator. The `coro::generator` template class also supports C++20 range iteration, e.g., `std::ranges::for_each()` can be used to consume an instantiated generator-based function's output. 6 | 7 | This generator example generates the Fibonacci Sequence up to some specified ceiling. 8 | 9 | ## Description 10 | 11 | Here is the function - its the use of `co_yield` that make it a C++20 coroutine (as opposed to an ordinary function): 12 | 13 | ```cpp 14 | template 15 | generator fibonacci(const T ceiling) { 16 | T j = 0; 17 | T i = 1; 18 | co_yield j; 19 | if (ceiling > j) { 20 | do { 21 | co_yield i; 22 | T tmp = i; 23 | i += j; 24 | j = tmp; 25 | } while (i <= ceiling); 26 | } 27 | } 28 | ``` 29 | The generator function's return value `generator` is an iterator for the type of its template argument. In this programming example it is using the template class `coro::generator<>` which the implementation of is provided in `generator.h`. 30 | 31 | **NOTE:** The template class `coro::generator<>` has been customized off of Rainer Grimm's implementation.[2](#fn2) 32 | 33 | Here is code that consumes generated values from `fibonacci()`: 34 | ```cpp 35 | const auto demo_ceiling = std::numeric_limits::max() / 1'000.0f; 36 | 37 | auto iter = fibonacci(demo_ceiling); 38 | 39 | while(iter.next()) { 40 | const auto value = iter.getValue(); 41 | std::cout << value << '\n'; 42 | } 43 | 44 | ``` 45 | This will print out 1463 values of the Fibonacci Sequence. 46 | 47 | The consuming code and the generator code are executing on the same thread context and yet the `fibonacci()` function enjoys a preserved local scope state as it executes and then resumes from `co_yield`. The generator function just falls out of the loop when the specified ceiling is exceeded to terminate itself - the consuming code will detect this in the `while(iter.next()) {...}` loop condition and fall out of the loop. 48 | 49 | ## C++17 pmr allocators 50 | 51 | The `coro::generator` template class now uses C++17 pmr `memory_resource` allocators. By default the `std::pmr::new_delete_resource` allocator is used which relies on the global `new` and `delete`. It is also `std::pmr::synchronized_pool_resource` so is thread-safe. The function `coro::set_pmr_mem_pool()` can be used to set an alternative or custom pmr allocator. The helper class `coro::fixed_buffer_pmr_allocator` can be used to setup a stack-based, fixed-size buffer (or, say, a data segment fixed-sized buffer). 52 | 53 | This program shows two cases of instantiating and invoking a generator where the function `coro::set_pmr_mem_pool()` is used to specify a stack-based pmr allocator. 54 | 55 | Then the function `coro::reset_default_pmr_mem_pool()` can be invoked to reset the `coro::generator` template class back to using the default allocator. 56 | 57 | ## Building the program 58 | 59 | The program has been built with cmake and with g++ version 12.1.0 or clang++ version 16.0.0. [3](#fn3) 60 | 61 | **NOTE:** On my computer I installed version 16 of clang/llvm from a downloaded `.tar.gz` file; per the directory as to where I *untarred* to, I then had to update these symbolic links to reference the version 16 clang shared libraries: 62 | ``` 63 | /lib/x86_64-linux-gnu/libc++.so.1.0 64 | /lib/x86_64-linux-gnu/libc++abi.so.1.0 65 | /lib/x86_64-linux-gnu/libunwind.so.1.0 66 | ``` 67 | 68 | 1: [cppreference.com - Coroutines (C++20)](https://en.cppreference.com/w/cpp/language/coroutines) 69 | 70 | 2: [Rainer Grimm, Concurrency with Modern C++ (Leanpub, 2017 - 2019), 207-209.](https://leanpub.com/concurrencywithmodernc) 71 | 72 | 3: [Clang-LLVM downloads](http://releases.llvm.org/download.html#16.0.0) 73 | -------------------------------------------------------------------------------- /gcc-coroutine-example.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by github roger-dv on 10/11/2020 3 | * Updated by github roger-dv on 04/16/2023 4 | * 5 | * Based on example code (but with significant cleanup) found in: 6 | * Rainer Grimm, Concurrency with Modern C++ (Leanpub, 2017 - 2019), 207-209. 7 | * 8 | * Latest updates were to remove use of Clang experimental coroutines 9 | * and to instead use C++20 official coroutine implementation. Also, 10 | * C++20 Concepts are used to constrain template types instead of C++11 11 | * SFINAE type traits. The current implementation has been verified via 12 | * gcc/g++ v12.1 and with clang++ v16. 13 | * 14 | * Compile with gcc/g++: 15 | * g++ -O3 -std=c++20 -o g++-coroutines gcc-coroutine-example.cpp 16 | */ 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | static const auto demo_ceiling1 = std::numeric_limits::max() / 1'000ul; 24 | static const auto demo_ceiling2 = std::numeric_limits::max() / 1'000ul; 25 | static const auto demo_ceiling3 = std::numeric_limits::max() / 1'000.0f; 26 | static const auto demo_ceiling4 = std::numeric_limits::max() / 1'000.0f; 27 | 28 | 29 | namespace coro { 30 | 31 | /** 32 | * General purpose C++20 coroutine generator template class. 33 | * 34 | * @tparam T the type of value that the generator returns to the caller 35 | */ 36 | template 37 | class [[nodiscard]] generator { 38 | public: 39 | struct promise_type; 40 | using coro_handle_type = std::coroutine_handle; 41 | private: 42 | coro_handle_type coro; 43 | public: 44 | explicit generator(coro_handle_type h) : coro{h} {} 45 | generator(const generator &) = delete; // do not allow copy construction 46 | generator &operator=(const generator &) = delete; // do not allow copy assignment 47 | generator(generator &&oth) noexcept : coro{std::move(oth.coro)} { 48 | oth.coro = nullptr; // insure the other moved handle is null 49 | } 50 | generator &operator=(generator &&other) noexcept { 51 | if (this != &other) { // ignore assignment to self 52 | if (coro) { // destroy self current handle 53 | coro.destroy(); 54 | } 55 | coro = std::move(other.coro); // move other coro handle into self 56 | other.coro = nullptr; // insure other moved handle is null 57 | } 58 | return *this; 59 | } 60 | ~generator() { 61 | if (coro) { 62 | coro.destroy(); 63 | coro = nullptr; 64 | } 65 | } 66 | 67 | public: // API 68 | bool next() { 69 | if (!coro || coro.done()) return false; // nothing more to process 70 | coro.resume(); 71 | return !coro.done(); 72 | } 73 | 74 | std::optional getValue() { 75 | return coro ? std::make_optional(coro.promise().current_value) : std::nullopt; 76 | } 77 | 78 | public: 79 | // implementation of above opaque declaration promise_type 80 | struct promise_type { 81 | private: 82 | T current_value{}; 83 | friend class generator; 84 | public: 85 | promise_type() = default; 86 | ~promise_type() = default; 87 | promise_type(const promise_type&) = delete; 88 | promise_type(promise_type&&) = delete; 89 | promise_type &operator=(const promise_type&) = delete; 90 | promise_type &operator=(promise_type&&) = delete; 91 | 92 | auto get_return_object() { 93 | return generator{coro_handle_type::from_promise(*this)}; 94 | } 95 | 96 | auto initial_suspend() { 97 | return std::suspend_always{}; 98 | } 99 | 100 | auto final_suspend() noexcept { 101 | return std::suspend_always{}; 102 | } 103 | 104 | auto return_void() { 105 | return std::suspend_never{}; 106 | } 107 | 108 | auto yield_value(T some_value) { 109 | current_value = some_value; 110 | return std::suspend_always{}; 111 | } 112 | 113 | void unhandled_exception() { 114 | std::terminate(); 115 | } 116 | }; 117 | }; 118 | 119 | } // namespace coro 120 | 121 | 122 | // concept to constrain function templates that follow to only accept arithmetic types 123 | template 124 | concept arithmetic = std::integral || std::floating_point; 125 | 126 | /** 127 | * Returns number in ascending sequence starting at specified value. 128 | * 129 | * @tparam T arithmetic type of number returned 130 | * @param start value to begin sequence at 131 | * @return coroutine task iterator 132 | */ 133 | template 134 | coro::generator ascending_sequence(const T start) { 135 | T i = start; 136 | while (true) { 137 | T j = i++; 138 | co_yield j; 139 | } 140 | } 141 | 142 | /** 143 | * Generates Fibonacci sequence up to specified ceiling value. 144 | * 145 | * @tparam T arithmetic type of number returned 146 | * @param ceiling terminates generation of sequence when reaching 147 | * @return coroutine task iterator 148 | */ 149 | template 150 | coro::generator fibonacci(const T ceiling) { 151 | T j = 0; 152 | T i = 1; 153 | co_yield j; 154 | if (ceiling > j) { 155 | do { 156 | co_yield i; 157 | T tmp = i; 158 | i += j; 159 | j = tmp; 160 | } while (i <= ceiling); 161 | } 162 | } 163 | 164 | // C++ (C++17 fold expressions) 165 | template 166 | void print_one(T &&arg) { 167 | std::cout << arg << ' '; 168 | } 169 | template 170 | void print(Ts &&... args) { 171 | (print_one(std::forward(args)), ...); 172 | } 173 | 174 | int main() { 175 | std::cout << "Example using C++20 coroutines to implement Simple Integer and Fibonacci Sequence generators" << '\n'; 176 | 177 | std::cout << '\n' << "Simple Integer Sequence Generator" << '\n' << ' '; 178 | auto iter1 = ascending_sequence(0); 179 | try { 180 | for(int i : std::ranges::views::iota(1, 11)) { 181 | if (iter1.next()) { 182 | const auto value = iter1.getValue().value(); 183 | print(i, ": bytes", sizeof(value), ':', value, '\n'); 184 | } 185 | } 186 | } catch(const std::bad_optional_access& e) { 187 | // calling iter1.next() with true result prior to calling iter1.getValue().value() 188 | // should insure a value is always returned, so should never reach here 189 | std::cerr << e.what() << '\n'; 190 | } 191 | 192 | auto const invoke_fib_seq = [](auto&& iter) { 193 | std::cout << '\n' << "Fibonacci Sequence Generator" << '\n' << ' '; 194 | try { 195 | for (int i = 1; iter.next(); i++) { 196 | const auto value = iter.getValue().value(); 197 | print(i, ": bytes", sizeof(value), ':', value, '\n'); 198 | } 199 | } catch(const std::bad_optional_access& e) { 200 | // calling iter.next() with true result prior to calling iter.getValue().value() 201 | // should insure a value is always returned, so should never reach here 202 | std::cerr << e.what() << '\n'; 203 | } 204 | }; 205 | 206 | invoke_fib_seq(fibonacci(demo_ceiling1)); 207 | invoke_fib_seq(fibonacci(demo_ceiling2)); 208 | invoke_fib_seq(fibonacci(demo_ceiling3)); 209 | invoke_fib_seq(fibonacci(demo_ceiling4)); 210 | } -------------------------------------------------------------------------------- /generator.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by github roger-dv on 09/29/2019 3 | * Updated by github roger-dv on 04/16/2023 4 | * 5 | * Based on example code (but with significant cleanup) found in: 6 | * Rainer Grimm, Concurrency with Modern C++ (Leanpub, 2017 - 2019), 207-209. 7 | * 8 | * Latest updates were to remove use of Clang experimental coroutines 9 | * and to instead use C++20 official coroutine implementation. The 10 | * current implementation has been verified via gcc/g++ v12.1 and with 11 | * clang++ v16. 12 | */ 13 | #ifndef GENERATOR_H 14 | #define GENERATOR_H 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace coro { 24 | 25 | // the default pmr memory resource is a thread-safe allocator that uses the global new and delete 26 | inline static std::pmr::synchronized_pool_resource mem_pool{std::pmr::new_delete_resource()}; // default allocator 27 | 28 | inline static thread_local std::pmr::memory_resource* pmem_pool = &mem_pool; // never owns any supplied mem resource 29 | inline static void set_pmr_mem_pool(std::pmr::memory_resource* mem_pool_cust) { 30 | pmem_pool = mem_pool_cust; // set a custom pmr allocator (but does not take ownership) 31 | } 32 | inline static void reset_default_pmr_mem_pool() { 33 | pmem_pool = &mem_pool; // reset using the default allocator (does not take ownership) 34 | } 35 | 36 | /** 37 | * General purpose C++20 coroutine generator template class. 38 | * 39 | * @tparam T the type of value that the generator returns to the caller 40 | */ 41 | template 42 | class [[nodiscard]] generator { 43 | public: 44 | struct promise_type; 45 | using coro_handle_type = std::coroutine_handle; 46 | private: 47 | coro_handle_type coro; 48 | public: 49 | explicit generator(coro_handle_type h) : coro{h} {} 50 | generator(const generator &) = delete; // do not allow copy construction 51 | generator &operator=(const generator &) = delete; // do not allow copy assignment 52 | generator(generator &&oth) noexcept : coro{std::move(oth.coro)} { 53 | oth.coro = nullptr; // insure the other moved handle is null 54 | } 55 | generator &operator=(generator &&other) noexcept { 56 | if (this != &other) { // ignore assignment to self 57 | if (coro) { // destroy self current handle 58 | coro.destroy(); 59 | } 60 | coro = std::move(other.coro); // move other coro handle into self 61 | other.coro = nullptr; // insure other moved handle is null 62 | } 63 | return *this; 64 | } 65 | ~generator() { 66 | if (coro) { 67 | coro.destroy(); 68 | coro = nullptr; 69 | } 70 | } 71 | 72 | public: // API 73 | bool next() const { 74 | if (!coro || coro.done()) return false; // nothing more to process 75 | coro.resume(); 76 | return !coro.done(); 77 | } 78 | 79 | std::optional getValue() noexcept { 80 | return coro ? std::make_optional(coro.promise().current_value) : std::nullopt; 81 | } 82 | 83 | public: 84 | // implementation of above opaque declaration promise_type 85 | struct promise_type { 86 | public: 87 | void* operator new(std::size_t sz) { 88 | assert(pmem_pool != nullptr); 89 | return pmem_pool->allocate(sz); 90 | } 91 | void operator delete(void* ptr, std::size_t sz) { 92 | assert(pmem_pool != nullptr); 93 | pmem_pool->deallocate(ptr, sz); 94 | } 95 | private: 96 | T current_value{}; 97 | friend class generator; 98 | public: 99 | promise_type() = default; 100 | ~promise_type() = default; 101 | promise_type(const promise_type&) = delete; 102 | promise_type(promise_type&&) = delete; 103 | promise_type &operator=(const promise_type&) = delete; 104 | promise_type &operator=(promise_type&&) = delete; 105 | 106 | auto get_return_object() { 107 | return generator{coro_handle_type::from_promise(*this)}; 108 | } 109 | 110 | auto initial_suspend() { 111 | return std::suspend_always{}; 112 | } 113 | 114 | auto final_suspend() noexcept { 115 | return std::suspend_always{}; 116 | } 117 | 118 | void return_void() {} 119 | 120 | auto yield_value(T some_value) { 121 | current_value = some_value; 122 | return std::suspend_always{}; 123 | } 124 | 125 | void unhandled_exception() { 126 | std::terminate(); 127 | } 128 | }; 129 | 130 | struct iterator { 131 | using difference_type [[maybe_unused]] = std::ptrdiff_t; 132 | using value_type [[maybe_unused]] = T; 133 | coro_handle_type hdl = nullptr; 134 | iterator() = default; 135 | iterator(coro_handle_type h) : hdl{h} {} 136 | void getNext() { 137 | if (hdl) { 138 | hdl.resume(); 139 | if (hdl.done()) { 140 | hdl = nullptr; 141 | } 142 | } 143 | } 144 | T& operator*() const { 145 | assert(hdl); 146 | return hdl.promise().current_value; 147 | } 148 | iterator& operator++() { // pre-incrementable 149 | getNext(); 150 | return *this; 151 | } 152 | void operator ++ (int) { // post-incrementable 153 | ++*this; 154 | } 155 | bool operator==(const iterator& i) const = default; 156 | }; 157 | 158 | iterator begin() const { 159 | if (!coro || coro.done()) { 160 | return iterator{nullptr}; 161 | } 162 | iterator itr{coro}; 163 | itr.getNext(); 164 | return itr; 165 | } 166 | 167 | iterator end() const { 168 | return iterator{nullptr}; 169 | } 170 | }; 171 | 172 | /** 173 | * Helper class for establishing a pmr memory_resource compliant 174 | * allocator that allocates from a supplied fixed size buffer, e.g., 175 | * such as a buffer allocated on the stack. Allocations are not 176 | * freed so depends on buffer context being reclaimed when going 177 | * out of scope (this class does not take ownership of the buffer). 178 | */ 179 | class fixed_buffer_pmr_allocator : public std::pmr::memory_resource { 180 | private: 181 | void * const buf; 182 | size_t buf_size; 183 | public: 184 | const size_t max_buf_size; 185 | fixed_buffer_pmr_allocator(void* buf, size_t buf_size) : buf(buf), buf_size(buf_size), max_buf_size(buf_size) {} 186 | fixed_buffer_pmr_allocator() = delete; 187 | fixed_buffer_pmr_allocator(const fixed_buffer_pmr_allocator&) = delete; 188 | fixed_buffer_pmr_allocator(fixed_buffer_pmr_allocator&&) = delete; 189 | fixed_buffer_pmr_allocator& operator=(const fixed_buffer_pmr_allocator&) = delete; 190 | fixed_buffer_pmr_allocator& operator=(fixed_buffer_pmr_allocator&&) = delete; 191 | private: 192 | virtual void* do_allocate(size_t bytes, size_t alignment) { 193 | if (bytes > buf_size) { 194 | std::cerr << "requested bytes: " << bytes << ", remaining bytes capacity: " << buf_size << '\n'; 195 | throw std::bad_alloc(); 196 | } 197 | buf_size -= bytes; 198 | return buf; 199 | } 200 | virtual void do_deallocate(void* p, size_t bytes, size_t alignment) {} 201 | virtual bool do_is_equal(const memory_resource& __other) const noexcept { return false; } 202 | }; 203 | 204 | } // namespace coro 205 | 206 | #endif //GENERATOR_H 207 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /** main.cpp 2 | * 3 | * Copyright 2023 Roger D. Voss 4 | * 5 | * Created by github user roger-dv on 09/28/2019. 6 | * Updated by github user roger-dv on 04/16/2023. 7 | * 8 | * Licensed under the MIT License - refer to LICENSE project document. 9 | * 10 | * Latest updates were to remove use of Clang experimental coroutines 11 | * and to instead use C++20 official coroutine implementation. Also, 12 | * C++20 Concepts are used to constrain template types instead of C++11 13 | * SFINAE type traits. The current implementation has been verified via 14 | * gcc/g++ v12.1 and with clang++ v16. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include "generator.h" // general purpose C++20 coroutine generator template class 20 | 21 | static const auto demo_ceiling1 = std::numeric_limits::max() / 1'000ul; 22 | static const auto demo_ceiling2 = std::numeric_limits::max() / 1'000ul; 23 | static const auto demo_ceiling3 = std::numeric_limits::max() / 1'000.0f; 24 | static const auto demo_ceiling4 = std::numeric_limits::max() / 1'000.0f; 25 | 26 | // concept to constrain function templates that follow to only accept arithmetic types 27 | template 28 | concept arithmetic = std::integral || std::floating_point; 29 | 30 | /** 31 | * Returns number in ascending sequence starting at specified value. 32 | * 33 | * @tparam T arithmetic type of number returned 34 | * @param start value to begin sequence at 35 | * @return coroutine task iterator 36 | */ 37 | template 38 | coro::generator ascending_sequence(const T start) { 39 | T i = start; 40 | while (true) { 41 | T j = i++; 42 | co_yield j; 43 | } 44 | } 45 | 46 | /** 47 | * Generates Fibonacci sequence up to specified ceiling value. 48 | * 49 | * @tparam T arithmetic type of number returned 50 | * @param ceiling terminates generation of sequence when reaching 51 | * @return coroutine task iterator 52 | */ 53 | template 54 | coro::generator fibonacci(const T ceiling) { 55 | T j = 0; 56 | T i = 1; 57 | co_yield j; 58 | if (ceiling > j) { 59 | do { 60 | co_yield i; 61 | T tmp = i; 62 | i += j; 63 | j = tmp; 64 | } while (i <= ceiling); 65 | } 66 | } 67 | 68 | // C++ (C++17 fold expressions) 69 | template 70 | void print_one(T &&arg) { 71 | std::cout << arg << ' '; 72 | } 73 | template 74 | void print(Ts &&... args) { 75 | (print_one(std::forward(args)), ...); 76 | } 77 | 78 | int main() { 79 | std::cout << "Example using C++20 coroutines to implement Simple Integer and Fibonacci Sequence generators" << '\n'; 80 | 81 | using coro_gen_ints_promise_type = coro::generator::promise_type; 82 | using coro_gen_ints = coro::generator; 83 | std::cerr << sizeof(coro_gen_ints_promise_type) << " bytes : coro::generator::promise_type\n"; 84 | std::cerr << sizeof(coro_gen_ints) << " bytes : coro::generator\n"; 85 | 86 | // insure instantiation of a decltype(0) coro::generator promise_type is on the stack - not the heap 87 | std::cerr << "set coro::generator to stack memory buffer pmr allocator\n"; 88 | // size_t buf_size = sizeof(coro_gen_ints_promise_type); 89 | size_t buf_size = 64; // allocating sizeof promise_type is insufficient for g++ 90 | coro::fixed_buffer_pmr_allocator pmr_alloc{ alloca(buf_size), buf_size }; 91 | coro::set_pmr_mem_pool(&pmr_alloc); 92 | 93 | std::cout << '\n' << "Simple Integer Sequence Generator" << '\n' << ' '; 94 | try { 95 | auto iter1 = ascending_sequence(0); 96 | for(int i = 1; i <= 10 && iter1.next(); i++) { 97 | const auto value = iter1.getValue().value(); 98 | print(i, ": bytes", sizeof(value), ':', value, '\n'); 99 | } 100 | } catch(const std::bad_optional_access& e) { 101 | // calling iter1.next() with true result prior to calling iter1.getValue().value() 102 | // should insure a value is always returned, so should never reach here 103 | std::cerr << e.what() << '\n'; 104 | } catch(const std::bad_alloc& e) { 105 | // only reaches here if stack buffer was insufficient for allocating the coro::generator promise_type context 106 | std::cerr << e.what() << '\n'; 107 | } 108 | 109 | using coro_gen_ulongs_promise_type = coro::generator::promise_type; 110 | using coro_gen_ulongs = coro::generator; 111 | using coro_gen_ulonglongs_promise_type = coro::generator::promise_type; 112 | using coro_gen_ulonglongs = coro::generator; 113 | using coro_gen_doubles_promise_type = coro::generator::promise_type; 114 | using coro_gen_doubles = coro::generator; 115 | using coro_gen_ldoubles_promise_type = coro::generator::promise_type; 116 | using coro_gen_ldoubles = coro::generator; 117 | 118 | std::cerr << sizeof(coro_gen_ulongs_promise_type) << " bytes : coro::generator::promise_type\n"; 119 | std::cerr << sizeof(coro_gen_ulongs) << " bytes : coro::generator\n"; 120 | 121 | std::cerr << sizeof(coro_gen_ulonglongs_promise_type) << " bytes : coro::generator::promise_type\n"; 122 | std::cerr << sizeof(coro_gen_ulonglongs) << " bytes : coro::generator\n"; 123 | 124 | std::cerr << sizeof(coro_gen_doubles_promise_type) << " bytes : coro::generator::promise_type\n"; 125 | std::cerr << sizeof(coro_gen_doubles) << " bytes : coro::generator\n"; 126 | 127 | std::cerr << sizeof(coro_gen_ldoubles_promise_type) << " bytes : coro::generator::promise_type\n"; 128 | std::cerr << sizeof(coro_gen_ldoubles) << " bytes : coro::generator\n"; 129 | 130 | // reset the coro::generator class to the default promise_type allocator (global new and delete) 131 | std::cerr << "reset coro::generator to default pmr allocator (global new and delete)\n"; 132 | coro::reset_default_pmr_mem_pool(); 133 | 134 | auto const invoke_fib_seq = [](auto&& iter) { 135 | std::cout << '\n' << "Fibonacci Sequence Generator" << '\n' << ' '; 136 | try { 137 | for (int i = 1; iter.next(); i++) { 138 | const auto value = iter.getValue().value(); 139 | print(i, ": bytes", sizeof(value), ':', value, '\n'); 140 | } 141 | } catch(const std::bad_optional_access& e) { 142 | // calling iter.next() with true result prior to calling iter.getValue().value() 143 | // should insure a value is always returned, so should never reach here 144 | std::cerr << e.what() << '\n'; 145 | } 146 | }; 147 | 148 | invoke_fib_seq(fibonacci(demo_ceiling1)); 149 | invoke_fib_seq(fibonacci(demo_ceiling2)); 150 | invoke_fib_seq(fibonacci(demo_ceiling3)); 151 | 152 | std::cerr << "now set coro::generator to stack memory buffer pmr allocator\n"; 153 | // insure instantiation of a decltype(demo_ceiling4) coro::generator promise_type is on the stack - not the heap 154 | // buf_size = sizeof(coro_gen_ldoubles_promise_type); 155 | buf_size = 256; // allocating sizeof promise_type is insufficient for g++ 156 | coro::fixed_buffer_pmr_allocator pmr_alloc_ldbl{ alloca(buf_size), buf_size }; 157 | coro::set_pmr_mem_pool(&pmr_alloc_ldbl); 158 | 159 | try { 160 | invoke_fib_seq(fibonacci(demo_ceiling4)); // instantiates a coro::generator fibonacci for decltype(demo_ceiling4) 161 | } catch(const std::bad_alloc& e) { 162 | // only reaches here if stack buffer was insufficient for allocating the coro::generator promise_type context 163 | std::cerr << e.what() << '\n'; 164 | } 165 | 166 | // reset the coro::generator class to the default promise_type allocator (global new and delete) 167 | std::cerr << "reset coro::generator again to default pmr allocator (global new and delete)\n"; 168 | coro::reset_default_pmr_mem_pool(); 169 | 170 | // use the generator's coroutine task iterator 171 | try { 172 | std::cout << '\n' << "Fibonacci Sequence Generator" << '\n' << ' '; 173 | auto rng = fibonacci(demo_ceiling1); 174 | static_assert(std::ranges::input_range); 175 | int i = 1; 176 | std::ranges::for_each(rng, [&i](const auto &value){ print(i++, ": bytes", sizeof(value), ':', value, '\n'); }); 177 | } catch(const std::bad_optional_access& e) { 178 | // should never reach here 179 | std::cerr << e.what() << '\n'; 180 | } 181 | } --------------------------------------------------------------------------------