├── 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 | }
--------------------------------------------------------------------------------