最終更新日時:
が更新

履歴 編集

コルーチンのpromise型でreturn_valuereturn_voidの両方の宣言を許可 [P3950R1](C++29)

このページはC++29に採用された言語機能の変更を解説しています。

のちのC++規格でさらに変更される場合があるため関連項目を参照してください。

概要

C++29では、コルーチンのpromise型がreturn_valuereturn_voidの両方のメンバ関数を宣言できるようになる。これによって、1つのコルーチンの本体に、値をともなうco_return v;と値をともなわないco_return;の両方の文を書けるようになる。

task f(bool b) {
  if (b) {
    co_return 42;  // promise.return_value(42)の呼び出し
  }
  co_return;       // promise.return_void()の呼び出し
}

C++26までは、promise型のスコープでの名前return_voidreturn_valueの探索が両方とも宣言を見つけた場合、プログラムは不適格と規定されていた。この制限は名前の探索にもとづいていたため、両方の関数が同時にオーバーロード解決可能になることがないよう制約 (requires) を付けた宣言であっても、宣言が存在するだけで不適格となり、ジェネリックなpromise型の実装方法を不必要に制限していた。

仕様

  • promise型がreturn_valuereturn_voidの両方を宣言した場合にプログラムを不適格とする規定が削除される
  • コルーチン本体の終端到達の規定が、名前探索ではなくオーバーロード解決にもとづいて定義し直される
    • p.return_void()オーバーロード解決が成功する場合、コルーチン本体の終端到達はオペランドなしのco_returnと等価である
    • そうでない場合、コルーチン本体の終端到達は未定義動作である
  • 機能テストマクロ__cpp_impl_coroutineの値が202606Lに更新される

#include <coroutine>
#include <iostream>

struct task {
  struct promise_type {
    task get_return_object() { return {}; }
    std::suspend_never initial_suspend() { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void unhandled_exception() {}

    // C++26までは、この2つを同時に宣言するとプログラムが不適格だった
    void return_void() {
      std::cout << "void" << std::endl;
    }
    void return_value(int x) {
      std::cout << "value: " << x << std::endl;
    }
  };
};

task f(bool b) {
  if (b) {
    co_return 42;
  }
  co_return;
}

int main() {
  f(true);
  f(false);
}

出力

value: 42
void

この機能が必要になった背景・経緯

return_valuereturn_voidの同時宣言の禁止は、コルーチンが導入される前の初期の提案(N4499)から存在していた規定である。初期の設計にはコルーチンの「最終的な型 (eventual type)」という概念があり、戻り値の型を1つに定める必要があったが、この概念は最終的な仕様からは削除されており、禁止だけが残っていた。

コルーチンの本体は通常の関数の本体とは異なり、promiseオブジェクトと対話するためのプロトコルへ書き換えられるものであるため、「関数の戻り値は1つの型で1通り」という通常の関数の性質に合わせる必然性はない。特にC++26で導入されたstd::executionの完了シグネチャは、値をともなわない完了set_value_t()と値をともなう完了set_value_t(T...)の混在を表現できる。promise型はメンバ関数テンプレートによって複数の型のco_returnを異なる完了シグネチャへ対応付けられるが、値をともなわない完了だけはreturn_voidを宣言できないために特別なタグ型を受け取るといった回避策が必要で、コルーチンがstd::executionの表現力に追いつけない状態だった。

同じ目的の提案(P1713R0)は2019年のケルン会議で合意に至らなかったが、std::executionの採用という新しい状況を受けて本提案が再提案され、採択された。

関連項目

参照