https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101027
Justen Di Ruscio <justend29 at gmail dot com> changed:
What |Removed |Added
----------------------------------------------------------------------------
CC| |justend29 at gmail dot com
--- Comment #9 from Justen Di Ruscio <justend29 at gmail dot com> ---
I encountered this bug using fold expressions on GCC major versions 15, 16, and
trunk commit ce59e88. Simple reproductions of this bug and two workarounds are
captured on godbolt (https://godbolt.org/z/rGjehjvoM), which illustrate that
the bug can be reproduced with any short-circuit logic:
```
Coro test_short_circuit(const bool run_inner) {
co_return run_inner && co_await inner(); // shouldn't run
}
```
First workaround: C++26 expansion statements allow correctly evaluating the
logic because the short-circuiting operators can be replaced with a traditional
if statement.
```
template <bool... pack>
Coro test_expansion(const bool run_inner) {
template for (constexpr auto p : {pack...}) {
if (p == run_inner) {
co_await inner();
}
}
co_return false;
}
```
Second workaround: if boolean operators are replaced with ternary statements
AND the result of the expression is unused, the co_awaits are correctly
invoked. This may not be as reliable.
```
template <bool... pack>
Coro test_ternary_no_result(const bool run_inner) {
((pack == run_inner ? co_await inner() : false), ...);
co_return false;
}
```
Entire reproduction:
```
#include <coroutine>
#include <print>
struct Coro {
struct promise_type {
Coro get_return_object() noexcept { return {}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(bool) noexcept {}
void unhandled_exception() noexcept { std::terminate(); }
};
bool await_ready() { return false; }
bool await_suspend(std::coroutine_handle<>) { return false; }
bool await_resume() { return false; }
};
Coro inner() {
std::println(" fail: invoked inner()");
co_return false;
}
Coro test_short_circuit(const bool run_inner) {
co_return run_inner && co_await inner();
}
template <bool... pack>
Coro test_fold(const bool run_inner) {
co_return ((pack == run_inner && co_await inner()), ...);
}
template <bool... pack>
Coro test_ternary_no_result(const bool run_inner) {
((pack == run_inner ? co_await inner() : false), ...);
co_return false;
}
template <bool... pack>
Coro test_expansion(const bool run_inner) {
template for (constexpr auto p : {pack...}) {
if (p == run_inner) {
co_await inner();
}
}
co_return false;
}
int main(const int argc, const char* const[]) {
// runtime value to avoid optimizing out boolean logic
const bool run_inner = argc > 1;
std::println("test_short_circuit");
test_short_circuit(run_inner);
std::println("test_fold");
test_fold<true, true, true>(run_inner);
std::println("test_ternary_no_result");
test_ternary_no_result<true, true, true>(run_inner);
std::println("test_expansion");
test_expansion<true, true, true>(run_inner);
}
```
x86-64 gcc 16.2
Compiler options for execution
-std=c++26 -Wall -Wextra -Wpedantic
Program returned: 0
Program stdout
test_short_circuit
fail: invoked inner()
test_fold
fail: invoked inner()
fail: invoked inner()
fail: invoked inner()
test_ternary_no_result
test_expansion