はじめに
C++で、成功すれば結果を、失敗すればエラー情報を返したい関数を設計する際、従来はエラーコードを別途引数で受け取る、std::optional
を使う、例外を投げる、などの方法がありました。
C++23では、この「成功または失敗」を表現するための、より優れた選択肢として std::expected
が導入されました。std::expected
は、以下のいずれかの値を保持します。
- 正常に処理が完了した場合の「期待される (expected) 値」
- 処理が失敗した場合の「予期しない (unexpected) エラー値」
これにより、関数の成功・失敗の意図が明確になり、呼び出し側でのエラーハンドリングも簡潔に記述できます。
【前提】C++23とは?
C++23は、2023年に正式化されたC++言語の比較的新しい規格です。std::expected
はこのC++23で追加された機能のため、利用するにはC++23に対応した最新のコンパイラが必要です。
std::expected
を使ったサンプルコード
このコードは、文字列を整数に変換する関数 string_to_int
を定義します。変換に成功すればint
型の値を、失敗すればエラーメッセージのstring
を、std::expected
に格納して返します。
完成コード
#include <iostream>
#include <string>
#include <expected> // expected, unexpected
#include <charconv> // from_chars
using namespace std;
// 文字列を整数に変換する関数
// 戻り値: 成功ならint, 失敗ならstring
expected<int, string> string_to_int(const string& s) {
int result;
// from_charsで文字列から数値への変換を試みる
auto [ptr, ec] = from_chars(s.data(), s.data() + s.size(), result);
if (ec == errc()) {
// 1. 成功した場合: 正常値を返す
return result;
} else {
// 2. 失敗した場合: std::unexpectedでエラー値を返す
return unexpected("変換に失敗しました。");
}
}
int main() {
// --- 成功するケース ---
auto result1 = string_to_int("123");
// 3. bool値として評価し、成功したかチェック
if (result1) {
// 4. .value()で正常値にアクセス
cout << "成功1: " << result1.value() << endl;
}
// --- 失敗するケース ---
auto result2 = string_to_int("abc");
if (result2) {
cout << "成功2: " << result2.value() << endl;
} else {
// 5. .error()でエラー値にアクセス
cout << "失敗2: " << result2.error() << endl;
}
return 0;
}
コードの解説
1. expected<int, string>
std::expected
のテンプレート引数として、expected<正常値の型, エラー値の型>
を指定します。この関数は、「成功すればint
を、失敗すればstring
を返す」という意味になります。
2. return result;
/ return unexpected(...)
- 成功時:
return result;
のように、正常値の型の値を直接返します。expected
オブジェクトが暗黙的に構築されます。 - 失敗時:
std::unexpected
でエラー値のオブジェクトを作成して返します。これにより、このexpected
がエラー状態であることが示されます。
3. if (result1)
expected
オブジェクトは、if
文などの条件式に置くと、正常値を持っているかどうかをbool
値で評価できます。
- 正常値を持っている場合 →
true
- エラー値を持っている場合 →
false
4. .value()
expected
が正常値を持っている場合に、その値にアクセスするには .value()
メンバ関数を使います。もしエラー値を持っているオブジェクトに対して .value()
を呼び出すと、例外がスローされます。
5. .error()
expected
がエラー値を持っている場合に、その値にアクセスするには .error()
メンバ関数を使います。
まとめ
今回は、C++23の std::expected
を使って、関数の成功・失敗をエレガントに表現する方法を解説しました。
std::expected<T, E>
: 正常値T
またはエラー値E
のどちらかを保持する。- 成功時は値を直接
return
し、失敗時はstd::unexpected
でラップしてreturn
する。 - 呼び出し側では、
if
文で成否を判定し、.value()
で正常値を、.error()
でエラー値を取得する。
std::expected
は、エラーが関数の正常な振る舞いの一部である(例外的な状況ではない)場合に、例外処理のオーバーヘッドを避けつつ、型安全なエラーハンドリングを実現するための非常に優れたツールです。