Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert a std::optional to a std::expected?

I'm trying to convert a std::optional into a std::expected using a lambda function.

The code I have written does not compile. The compiler complains that the monadic functions of std::optional must return another std::optional.

Here's my example code, which can be found on Godbolt.

  • godbolt link
#include <string>
#include <optional>
#include <expected>

std::optional<int> function_which_returns_optional_1() {
    return 10;
}

struct ErrorType {

};

int main() {

    std::optional<int> optional_value = function_which_returns_optional_1();

    std::expected<std::string, ErrorType> result =
        optional_value
        .and_then(
            [](int value) -> std::expected<std::string, ErrorType> {
                const auto value_string = std::to_string(value);
                return std::expected(value);
            }
        )
        .or_else(
            []() -> std::expected<std::string, ErrorType> {
                return std::unexpected(ErrorType{});
            }
        );

}

If you are familiar with Rust, this code will probably feel very relevant. Rust has a mechanism for translating std::Result types to std::option, and vice-versa. The C++ equivalent of std::Result is std::expected.

If you have written any Rust code, you will probably instinctively understand why I want to do this and what I am trying to do here.

If you are not familiar with Rust, then here's a couple of pieces of contextual information.

  • I am avoiding the use of exceptions throughout my code, hence the use of std::expected.
  • Some functions in the standard library return std::optional rather than std::expected. (Or vice-versa.) In such cases there may be function calls in the call stack which need to translate between the two types. Typically, an optional will become an expected, because a missing value is an error case. (The reverse of expected to optional makes less sense, becuase this reads as if an error is being hidden or discarded.)

Is there a way to get this code to compile?

Ideally, I would like to avoid the more verbose, although obvious, solution:

if(optional_value.has_value()) {
    const auto actual_value = *optional_value;
    const auto result = std::expected<std::string, ErrorType>(std::to_string(actual_value));
    return result;
}
else {
    const auto error_type = ErrorType{};
    const auto result = std::expected<std::string, ErrorType>(std::unexpected(error_type));
    return result;
}
like image 936
FreelanceConsultant Avatar asked May 14 '26 10:05

FreelanceConsultant


2 Answers

You can use transform, which unlike and_then, does not constrain the invocable to return a specialization of std::optional.

The return value from the invocable will be wrapped by transform into an std::optional, but then you can unwrap that with value.

Since you have an std::unexpected case, you can then use value_or.

auto result = optional_value
              .transform(
                [](int value) -> std::expected<std::string, ErrorType> {
                  return std::to_string(value);
               })
              .value_or(std::unexpected(ErrorType{})); 

demo

like image 150
cigien Avatar answered May 16 '26 00:05

cigien


You could revise your second code snippet to make it much more concise.

std::expected<std::string, ErrorType> optional_to_expected(const std::optional<int>& opt) {
    if (opt) {
        return std::to_string(*opt);
    }
    return std::unexpected(ErrorType());
}
like image 24
xvld Avatar answered May 16 '26 02:05

xvld