std::optionalをさらに便利にするvalue_or()
C++17 以降で個人的に一番好きな機能はstd::optional
なのですが、std::optional
はよく以下のように三項演算子とセットで使われます。
std::optional<std::string> name;
std::cout << "Name is: " << (name ? name.value() : "<missing>") << std::endl;
name
に文字列が入っている場合は、その文字列を出力する、入っていない場合は、違う文字列を出力します。
こういう時は、以下のように C++17 でstd::optional
と一緒に追加されたvalue_or
というメンバー関数を使うといいです。
std::cout << "Name is: " << name.value_or("<missing>") << std::endl;
短いですし、三項演算子より読みやすいと思います。
でもvalue_or
は名前の通りname.value()
で取得できる値が対象なので、残念ながら以下のようにvalue()
以外の値を使いたい場合はvalue_or
が使えないです。
std::cout << "Name has length: " << (name ? name->length() : 0) << std::endl;
こういう場合にはC++23 からstd::optional
に追加されるモナド的操作と呼ばれるtransform
, and_then
, or_else
というメンバー関数使えます。
どれにも引数を指定できるのですが、有効値か無効値によってその関数をコールし、その戻り値をstd::optional
として返してくれます。
指定できる関数に関して以下の違いがあります。
-
transform
にはT function(T)
のような関数が指定できる -
and_then
とor_else
にはstd::optional<T> function(T)
のような関数が指定できる
指定された関数がコールされる条件も以下の違いがあります。
-
transform
とand_then
は、無効値の場合はそのまま無効値を返しますが、有効値の場合は指定された関数をコールし、有効値を渡し、戻り値を返します。 -
or_else
は、有効値の場合はそのまま有効値を返しますが、無効値の場合は指定された関数をコールし、戻り値を返します。
実際に使ってみると、もう少し分かりやすくなると思います。
transform
にはstd::string::length
を以下のように直接指定できます。
std::cout << "Name is: " << name.transform(&std::string::length).value_or(0) << std::endl;
- 有効値の場合は、
std::string::length
がコールされ、有効値が渡せれ、その戻り値が返ってきます。 - 無効値の場合は、そのまま無効値が返ってきます。
どちらの場合も戻り値がstd::optional<std::string>
型になります。name
が無効値の場合はそのまま無効値が返ってくるので、value_or
をコールしています。
有効値の場合はそのまま返して、無効値の場合は0
を返します。
and_then
には引数に指定できるのは戻り値がstd::optional<T>
型の関数である必要があるため、ラムダ式関数を使っています。その中でstd::string::length
をコールし、その戻り値をstd::optional<std::string>
にラッピングして返します。
引数に指定した関数は「有効値の場合に呼び出される」ため、その関数の引数で有効値を受け取ります。
std::cout << "Name is: " << name.and_then([](std::string text) { return std::optional(text.length()); }).value_or(0) << std::endl;
それ以外はtransform
と同じ挙動になります。
or_else
にはand_then
と同様にラムダ式関数で指定しますが、実装が完全に逆になります。
and_then
とは逆に無効値の場合に関数が呼び出されるため、引数で受け取るようなことはありません。
std::cout << "Name is: " << name.or_else([] { return std::optional<std::string>(""); })->length() << std::endl;
or_else()
の部分は以下のようになります。
- 有効値の場合は、そのまま有効値(つまり
name
)が返ってきます。 - 無効値の場合は、指定したラムダ式関数がコールされて、その戻り値を返します。(
std::optional<std::string>("")
)
どちらの場合も戻り値は有効値になるため、最後には普通にstd::string::length
をコールできます。
この例だとtransform
は使いやすいですが、and_then
とor_else
は少し無理矢理な感じるかもしれません。またtransform
の場合も三項演算子の方が分かりやすいと感じる人もいると思います。
transform
, and_then
, or_else
はどちらかというと、std::optional
型の変数を連続で複数の関数に渡したい時に使うための関数であって、こういうシンプルな例では複雑すぎるかもしれないですね。(その話はまた別の記事にでも書いてみようと思います)
なんだかvalue_or
自体よりほかの説明が長くなってしまいましたが、ともかくvalue_or
は非常に使いやすいので、是非使ってみてください!
Discussion