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

Reply via email to