🙆

std::expected

2022/04/04に公開

P0323R10 std::expectedのメモ(->実装よりの説明はstd::expected(続)に続きます)

expected<T, E>は,T型の期待値,あるいはエラーEを含む語彙型です.予期せぬことが起こった場合には、より多くの型付けが必要となる。うまくいっているときは、ほとんどTが処理されているように見えるコードです。

クラステンプレート expected<T, E>には、以下のいずれかが含まれます。

  • 期待される値の型であるTの値,または
  • E型の値。予期しない結果が発生したときに使用されるエラー型。

このインタフェースは、基礎となる値が期待値(T型)か予期せぬ値(E型)かを問い合わせることができる。このアイデアはAndrei Alexandrescu C++ and Beyond 2012が元になっている。Systematic Error Handling in C++ Alexandrescu.Expected で、CppCon 2018 で本論文への言及を含め、再確認した。

インターフェースと rational は std::optional [N3793] に基づいている。expected<T, E>はoptional<T>の補足として考えており、期待値がオブジェクトに含まれない理由を表現している。

  1. Motivation
    C++の主なエラー機構は、例外とリターンコードの2つです。良いエラーメカニズムの特徴は、以下の通りです。

    1. Error visibility: 失敗例はコードレビュー中に表示されるべきです。エラーが隠されていると、デバッグが苦痛になることがあります。

    2. Information on errors: エラーは、その原因、原因、場合によっては解決方法などの情報を含んでいなければならない。

    3. Clean code: エラーの処理は、コードの別のレイヤーで、できるだけ見えないようにする必要があります。読者は、読むのを止める必要なく、例外的なケースの存在に気づくことができる。

    4. Non-Intrusive error: エラーは、通常のコードフロー専用の通信チャネルを独占してはいけません。可能な限り離散的でなければなりません。例えば、関数の戻り値は、エラー専用に確保されるべきではない通信路です。

1つ目の特徴と3つ目の特徴は矛盾しているように見えるので、さらに説明が必要である。前者は、処理されないエラーはコードに明確に表示されるべきだと指摘している。後者は、エラー処理が可読性を損なってはならない、つまり、正常な実行フローを明確に示すことを指示している。

ここでは、exceptionとreturn codesを比較します。

Exception Return error code
Visibility さらにコードを解析しないと見えない。しかし、例外が発生した場合は、スタックトレースを追うことができます。 呼び出された関数のプロトタイプを見れば、一目瞭然です。しかし、リターンコードを無視すると未定義の結果になることがあり、問題を把握するのが難しくなることがあります。
Exception 例外は任意に豊富である 歴史的には単純な整数。現在ではヘッダでよりリッチなエラーコードが提供されている。
Clean code クリーンなコードを提供し、例外は呼び出し元から完全に見えなくすることができます。 各関数呼び出しの後に、少なくともif文を追加することを強制します。
Non-Intrusive 適切なコミュニケーション・チャネル リターンチャネルの独占

期待される<T, E>クラスについても同様の分析を行い、古典的なエラー報告システムに対する優位性を観察することができる。

1. Error visibility:  例外とエラーコードのいいとこ取りをしています。
   戻り値の型がexpectable<T, E>であり、ユーザが含まれる値を取得したい場合、
   エラーケースを無視できないため、可視化されます。

2. Information on errors: 自由にエラー情報を格納できます。

3. Clean code: expectedのモナドインターフェースは、エラー処理を別のレイヤーの
   コードに委ねるフレームワークを提供します。expected<T, E>は例外指向のコード
   とnothrowの世界の橋渡しをすることもできることに注意してください。

4. Non-Intrusive error: リターンチャネルを独占せずに利用する。

その他、expected<T, E>の特徴として注目すべきは、以下の通りです。

  • エラーと計算のゴールを関連付ける。
  • 当然ながら、複数のエラーのインフライトを許容します。
  • テレポーテーションが可能。
  • スレッド境界を越える。
  • スレッドローカルストレージをサポートしない弱いエグゼキュータ上。
  • スローしないサブシステムの境界を越えて。
  • 時間を越えて: 今保存し、後で投げる。
  • エラーの収集、グループ化、結合。
  • コンパイラが最適化するのがより簡単になります。
  1. Implementation & Usage Experience
    この論文で指定されているstd::expectedの実装は、以下に示すように複数存在します。また、この論文で指定されたものと類似しているが同じではない実装も多数あり、それらは以下にリストアップされていない。

6.1. Sy Brand
最も人気のある実装はSy Brandのもので、GitHub上で500以上のスターを持ち、広く利用されています。
Code: https://github.com/TartanLlama/expected

6.2. Vicente J. Botet Escriba
std::expectedの原作者は実装を公開しています。

Code: https://github.com/viboes/std-make/blob/master/include/experimental/fundamental/v3/expected2/expected.hpp

6.3. WebKit
WebKit ウェブブラウザ(Safari で使用)には、そのコードベース全体で使用される実装が含まれています。

※参考動画
CppCon 2018: Andrei Alexandrescu “Expect the expected”
https://www.youtube.com/watch?v=PH4WBuE1BHI

※メモ
モナディックな書き方もできるらしい。P2505R0 Monadic Functions for std::expected

tl::expected<void, std::string> voidWork() { return {}; }
tl::expected<int, std::string> work2() { return 42; }
void errorhandling(std::string){}
	
tl::expected <int, std::string> result = voidWork ()
      .and_then (work2);
result.map_error ([&] (std::string result) {errorhandling (result);});

※メモ2
optinalの例ですが以下のページに例があります
Traditional Approach with if/else and optional C++20

※メモ3
zenn内では以下でもまとめらていました。感謝
std::optional のモナド的操作

Discussion