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

Reply via email to