|
| 1 | +# コルーチンのpromise型で`return_value`と`return_void`の両方の宣言を許可 [P3950R1] |
| 2 | +* cpp29[meta cpp] |
| 3 | + |
| 4 | +<!-- start lang caution --> |
| 5 | + |
| 6 | +このページはC++29に採用された言語機能の変更を解説しています。 |
| 7 | + |
| 8 | +のちのC++規格でさらに変更される場合があるため[関連項目](#relative-page)を参照してください。 |
| 9 | + |
| 10 | +<!-- last lang caution --> |
| 11 | + |
| 12 | +## 概要 |
| 13 | +C++29では、コルーチンのpromise型が`return_value`と`return_void`の両方のメンバ関数を宣言できるようになる。これによって、1つのコルーチンの本体に、値をともなう`co_return v;`と値をともなわない`co_return;`の両方の文を書けるようになる。 |
| 14 | + |
| 15 | +```cpp |
| 16 | +task f(bool b) { |
| 17 | + if (b) { |
| 18 | + co_return 42; // promise.return_value(42)の呼び出し |
| 19 | + } |
| 20 | + co_return; // promise.return_void()の呼び出し |
| 21 | +} |
| 22 | +``` |
| 23 | +
|
| 24 | +C++26までは、promise型のスコープでの名前`return_void`と`return_value`の探索が両方とも宣言を見つけた場合、プログラムは不適格と規定されていた。この制限は名前の探索にもとづいていたため、両方の関数が同時にオーバーロード解決可能になることがないよう制約 (`requires`) を付けた宣言であっても、宣言が存在するだけで不適格となり、ジェネリックなpromise型の実装方法を不必要に制限していた。 |
| 25 | +
|
| 26 | +
|
| 27 | +## 仕様 |
| 28 | +- promise型が`return_value`と`return_void`の両方を宣言した場合にプログラムを不適格とする規定が削除される |
| 29 | +- コルーチン本体の終端到達の規定が、名前探索ではなくオーバーロード解決にもとづいて定義し直される |
| 30 | + - `p.return_void()`のオーバーロード解決が成功する場合、コルーチン本体の終端到達はオペランドなしの`co_return`と等価である |
| 31 | + - そうでない場合、コルーチン本体の終端到達は未定義動作である |
| 32 | +- 機能テストマクロ`__cpp_impl_coroutine`の値が`202606L`に更新される |
| 33 | +
|
| 34 | +
|
| 35 | +## 例 |
| 36 | +```cpp |
| 37 | +#include <coroutine> |
| 38 | +#include <iostream> |
| 39 | +
|
| 40 | +struct task { |
| 41 | + struct promise_type { |
| 42 | + task get_return_object() { return {}; } |
| 43 | + std::suspend_never initial_suspend() { return {}; } |
| 44 | + std::suspend_never final_suspend() noexcept { return {}; } |
| 45 | + void unhandled_exception() {} |
| 46 | +
|
| 47 | + // C++26までは、この2つを同時に宣言するとプログラムが不適格だった |
| 48 | + void return_void() { |
| 49 | + std::cout << "void" << std::endl; |
| 50 | + } |
| 51 | + void return_value(int x) { |
| 52 | + std::cout << "value: " << x << std::endl; |
| 53 | + } |
| 54 | + }; |
| 55 | +}; |
| 56 | +
|
| 57 | +task f(bool b) { |
| 58 | + if (b) { |
| 59 | + co_return 42; |
| 60 | + } |
| 61 | + co_return; |
| 62 | +} |
| 63 | +
|
| 64 | +int main() { |
| 65 | + f(true); |
| 66 | + f(false); |
| 67 | +} |
| 68 | +``` |
| 69 | +* std::suspend_never[link /reference/coroutine/suspend_never.md] |
| 70 | + |
| 71 | +このコードはC++29の規則のもとでは適格だが、2026年9月時点でこの変更を実装した処理系はない(GCC・Clang・MSVCのいずれも「promise型が`return_value`と`return_void`の両方を宣言している」というエラーになる)。 |
| 72 | + |
| 73 | +### 出力 |
| 74 | +``` |
| 75 | +value: 42 |
| 76 | +void |
| 77 | +``` |
| 78 | + |
| 79 | + |
| 80 | +## この機能が必要になった背景・経緯 |
| 81 | +`return_value`と`return_void`の同時宣言の禁止は、コルーチンが導入される前の初期の提案(N4499)から存在していた規定である。初期の設計にはコルーチンの「最終的な型 (eventual type)」という概念があり、戻り値の型を1つに定める必要があったが、この概念は最終的な仕様からは削除されており、禁止だけが残っていた。 |
| 82 | + |
| 83 | +コルーチンの本体は通常の関数の本体とは異なり、promiseオブジェクトと対話するためのプロトコルへ書き換えられるものであるため、「関数の戻り値は1つの型で1通り」という通常の関数の性質に合わせる必然性はない。特にC++26で導入された[`std::execution`](/reference/execution.md)の完了シグネチャは、値をともなわない完了`set_value_t()`と値をともなう完了`set_value_t(T...)`の混在を表現できる。promise型はメンバ関数テンプレートによって複数の型の`co_return`を異なる完了シグネチャへ対応付けられるが、値をともなわない完了だけは`return_void`を宣言できないために特別なタグ型を受け取るといった回避策が必要で、コルーチンが`std::execution`の表現力に追いつけない状態だった。 |
| 84 | + |
| 85 | +同じ目的の提案(P1713R0)は2019年のケルン会議で合意に至らなかったが、`std::execution`の採用という新しい状況を受けて本提案が再提案され、採択された。 |
| 86 | + |
| 87 | + |
| 88 | +## <a id="relative-page" href="#relative-page">関連項目</a> |
| 89 | +- [C++20 コルーチン](/lang/cpp20/coroutines.md) |
| 90 | + |
| 91 | + |
| 92 | +## 参照 |
| 93 | +- [P3950R1 `return_value` & `return_void` Are Not Mutually Exclusive](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3950r1.pdf) |
| 94 | +- [P1713R0 Allowing both `co_return;` and `co_return value;` in the same coroutine](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1713r0.pdf) |
| 95 | + - 同じ目的の以前の提案。2019年のケルン会議で合意に至らなかった |
0 commit comments