The interaction between constexpr
/consteval
and coroutines is an interesting topic in C++20 and beyond. Let's explore this:
As of C++20, coroutines cannot be constexpr
or consteval
. The coroutine machinery involves runtime operations that are not compatible with compile-time evaluation.
First, let's recall what a basic coroutine looks like:
#include <coroutine>
#include <iostream>
struct MyCoroutine {
struct promise_type {
int value;
MyCoroutine get_return_object() {
return MyCoroutine{this};
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
void unhandled_exception() {}
std::suspend_always yield_value(int v) {
value = v;
return {};
}
};
std::coroutine_handle<promise_type> h;
MyCoroutine(promise_type* p)
: h(std::coroutine_handle<
promise_type>::from_promise(*p)) {}
~MyCoroutine() {
if (h) h.destroy();
}
};
MyCoroutine simple_coroutine() {
co_yield 1;
co_yield 2;
co_yield 3;
}
int main() {
auto cor = simple_coroutine();
for (int i = 0; i < 3; ++i) {
cor.h.resume();
std::cout << cor.h.promise().value << ' ';
}
}
1 2 3
constexpr
Coroutines involve several runtime operations:
These operations are not compatible with compile-time evaluation, which is why coroutines can't be constexpr
or consteval
.
While coroutines themselves can't be constexpr
, you can still use constexpr
functions to generate or manipulate coroutine-related data:
#include <array>
#include <coroutine>
#include <iostream>
constexpr std::array<int, 3> generate_values() {
return {1, 2, 3};
}
struct MyCoroutine {/*...*/};
MyCoroutine value_coroutine() {
constexpr auto values = generate_values();
for (int v : values) {
co_yield v;
}
}
int main() {
auto cor = value_coroutine();
for (int i = 0; i < 3; ++i) {
cor.h.resume();
std::cout << cor.h.promise().value << ' ';
}
}
1 2 3
The C++ committee is discussing potential ways to make coroutines more compile-time friendly in future standards. Some proposals include:
However, as of C++20, these are still in the proposal stage and not part of the standard.
For compile-time sequential computation, you might consider using template metaprogramming techniques instead of coroutines:
While this approach is less intuitive than coroutines, it achieves compile-time sequential computation.
#include <iostream>
template <int... Sequence>
struct sequence {};
template <int N, int... Sequence>
struct generate_sequence : generate_sequence<
N - 1, N - 1, Sequence...> {};
template <int... Sequence>
struct generate_sequence<0, Sequence...> {
using type = sequence<Sequence...>;
};
template <int... Seq>
constexpr void print_sequence(sequence<Seq...>) {
((std::cout << Seq << ' '), ...);
}
int main() {
print_sequence(
typename generate_sequence<3>::type{}
);
}
0 1 2
In conclusion, while coroutines themselves cannot be constexpr
or consteval
in C++20, you can still use compile-time computation in conjunction with coroutines.
As the language evolves, we may see more integration between compile-time evaluation and coroutines in future C++Â standards.
Answers to questions are automatically generated and may not have been reviewed.
Learn how to implement functionality at compile-time using constexpr
and consteval