https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127415
Bug ID: 127415
Summary: g++ fails to generate destructors for structurally
bound objects in coroutine frames
Product: gcc
Version: 15.3.1
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: [email protected]
Target Milestone: ---
```
// g++-15 regression: the destructor of a structured binding is never called
// when the binding is promoted into a coroutine frame (i.e. when it is still
// in scope across a co_await). An ordinary local in the same position is
// destroyed correctly, and the same structured binding in a non-coroutine
// function is destroyed correctly.
//
// g++-15 -std=c++20 sb.cc && ./a.out -> FAIL
// g++-14 -std=c++20 sb.cc && ./a.out -> PASS
#include <coroutine>
#include <cstdio>
#include <tuple>
static int constructed = 0;
static int destroyed = 0;
struct Noisy {
Noisy() {
++constructed;
printf(" Noisy() this=%p\n", static_cast<const void*>(this));
}
Noisy(const Noisy& o) {
++constructed;
printf(" Noisy(const Noisy&) this=%p from=%p\n",
static_cast<const void*>(this), static_cast<const void*>(&o));
}
~Noisy() {
++destroyed;
printf(" ~Noisy() this=%p\n", static_cast<const void*>(this));
}
};
struct Task {
struct promise_type {
Task get_return_object() {
return
Task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
std::coroutine_handle<promise_type> h;
};
Task coro() {
printf("coro: enter\n");
auto [i, n] = std::tuple<int, Noisy>{}; // <-- ~Noisy() never runs
printf("coro: suspending\n");
co_await std::suspend_always{};
printf("coro: resumed, falling off the end\n");
}
int main() {
Task t = coro();
printf("main: resuming\n");
t.h.resume();
printf("main: destroying frame\n");
t.h.destroy();
printf("main: constructed=%d destroyed=%d\n", constructed, destroyed);
printf("%s\n", constructed == destroyed
? "PASS: binding destroyed"
: "FAIL: binding destructor never called");
return constructed == destroyed ? 0 : 1;
}
```
Output:
```
coro: enter
Noisy() this=0x171052cc
coro: suspending
main: resuming
coro: resumed, falling off the end
main: destroying frame
main: constructed=1 destroyed=0
FAIL: binding destructor never called
```
g++ version:
```
$ ./xg++ --version
xg++ (GCC) 15.3.1 20260915
Copyright (C) 2025 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```
Commit 7287b1f75dab3a7ac1e335cb259c2611fced2098