通过 co_await 语句进行聚合初始化的调用顺序

Mil*_*ndt 11 c++ language-lawyer c++20 c++-coroutine

我正在追踪 gcc 中的一个错误,并遇到了一种我不知道可接受的行为是什么的情况。假设我们在协程中的某处有一行,例如:

Pair{.x{}, .y=(co_await std::suspend_always{}, 1)};
Run Code Online (Sandbox Code Playgroud)

其中Pair是一些可以使用成员.xX具有默认构造函数的类型)和(具有 int 构造函数的.y类型)进行聚合初始化的结构。Y这些类型可能都有重要的析构函数。这段代码可以做什么?


C++20 标准第 9.4.1/6 节指出:

聚合元素的初始化按元素顺序进行评估。也就是说,与给定元素相关的所有值计算和副作用都按顺序排列在其后面的任何元素的计算和副作用之前。

这向我表明该行唯一正确的行为如下:

  1. 在协程框架中为 类型的对象保留空间Pair
  2. .x通过调用初始化成员X::X()
  3. 计算co_await表达式(调用await_readyawait_suspend);暂停。

然后,如果协程恢复,我期望:

  1. 叫来await_resume服务员。
  2. .y通过调用初始化成员Y::Y(int)
  3. 调用Pair::~Pair()临时销毁。

如果协程被破坏,我预计:

  1. 打电话X::~X().x会员。

我相当确定这是正确的行为,尽管不太确定这是否是唯一允许的行为 - 而且我的困惑由于没有一个主要编译器针对这种情况输出合理的代码而变得更加严重。godbolt 上的完整示例表明,gcc 和 clang 生成的代码中对析构函数和构造函数的调用不正确匹配(这是我首先在 gcc 中寻找的错误)。MSVC 给出内部编译器错误。

建议的行为正确吗?这是唯一正确的行为吗?

小智 -2

在您提供的具体代码中:

  1. 在协程框架中为 Pair 类型的对象保留空间

  2. 通过调用 X::X() 初始化 .x 成员(假设 X 有一个默认构造函数)。

  3. 评估 co_await 表达式(调用await_ready 和await_suspend);暂停。

  4. 如果协程恢复:

    • 对等待者调用await_resume。
    • 通过调用 Y::Y(int) 初始化 .y 成员。
    • 调用 Pair::~Pair() 来销毁临时对象。
  5. 如果协程被破坏:

    • 对 .x 成员调用 X::~X()。
    #include <iostream>
    #include <coroutine>
    
    struct X {
        X() { std::cout << "X::X()\n"; }
        ~X() { std::cout << "X::~X()\n"; }
    };
    
    struct Y {
        Y(int) { std::cout << "Y::Y(int)\n"; }
        ~Y() { std::cout << "Y::~Y()\n"; }
    };
    
    struct Pair {
        X x;
        Y y;
    };
    
    struct Awaiter {
        bool await_ready() { return false; }
        void await_suspend(std::coroutine_handle<>) {}
        void await_resume() {}
    };
    
    struct Coroutine {
        struct promise_type {
            Promise* get_return_object() { return nullptr; }
            std::suspend_always initial_suspend() { return {}; }
            std::suspend_always final_suspend() noexcept { return {}; }
            void return_void() {}
            void unhandled_exception() {}
        };
    
        using promise_type = promise_type;
    
        std::coroutine_handle<promise_type> handle;
    
        Coroutine(std::coroutine_handle<promise_type> h) : handle(h) {}
        ~Coroutine() {
            if (handle)
                handle.destroy();
        }
    };
    
    Coroutine createCoroutine() {
        co_await std::suspend_always{};
        co_return;
    }
    
    int main() {
        Coroutine coroutine = createCoroutine();
        std::cout << "Coroutine created\n";
        coroutine.handle.resume();
        std::cout << "Coroutine resumed\n";
        coroutine.handle.destroy();
        std::cout << "Coroutine destroyed\n";
    
        return 0;
    }
Run Code Online (Sandbox Code Playgroud)