c++ - How to convert a std::optional to a std::expected? - Stack Overflow

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

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;
}

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;
}
Share Improve this question edited Mar 7 at 16:22 user2138149 asked Mar 7 at 10:12 user2138149user2138149 17.7k30 gold badges150 silver badges296 bronze badges 4
  • 1 Doc says: f - a suitable function or Callable object that returns an std::optional and you are returning std::expected. – Marek R Commented Mar 7 at 12:16
  • @MarekR I might be missing the point you are making. I do want to return a std::expected. – user2138149 Commented Mar 7 at 12:23
  • You might wrap your "verbose" way into a function... – Jarod42 Commented Mar 7 at 12:44
  • @Eljay OP already explicitly states that "the monadic functions must return std::optional. This is not a question about why it doesn't work. – Weijun Zhou Commented Mar 7 at 12:54
Add a comment  | 

4 Answers 4

Reset to default 8

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

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());
}

I would write it this way (with an additional overloads for optional<T>& and optional<T>&& in real life):

template <class T, class E, class E2 = remove_cvref_t<E>>
constexpr auto ok_or(optional<T> const& v, E&& e) -> expected<T, E2> {
    return v ? expected<T, E2>(in_place, *v)
             : expected<T, E2>(unexpected, (E&&)e);
}

This is basically generalizing xvld's answer, the name comes from Rust's function (where the name admittedly makes more sense because the "value" case of Rust's expectedResult — is named Ok).

It has a few benefits:

  • we can turn any optional into an expected with suitable additional error type. Sure, in your case you need an extra step — ok_or(optional_value, ErrorType{}).transform(to_string) — but those are two separate steps so that seems reasonable to express.
  • we're directly constructing the expected instead of going through the unexpected path, which is more efficient.

Solution from cigien fixes your code and keeps monadic style of code. IMHO this is a bit boilerplatey and looks bad.

On other hand, solution from xvld looks better, but is not very flexible.

So I would select middle ground solution and provide a helper function:

template <typename T>
concept optional_like = requires(T x) {
    { x ? 1 : 0 };
    { *x };
};

template <typename ErrorType, optional_like T>
auto as_expected(T&& x, ErrorType&& error = {})
{
    using Error_t = std::remove_cvref_t<ErrorType>;
    using Value_t = std::remove_cvref_t<decltype(*x)>;
    using Expected_t = std::expected<Value_t, Error_t>;

    if (x) {
        return Expected_t { *std::forward<T>(x) };
    }
    return Expected_t { std::unexpected<Error_t> { std::forward<ErrorType>(error) } };
}

Then it can be used like this:

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

    // use 1
    std::expected<std::string, ErrorType> result = as_expected<ErrorType>(optional_value.transform([](auto value) {
        return std::to_string(value);
    }));

    // use 2
    std::expected<std::string, ErrorType> result2 = as_expected<ErrorType>(optional_value).transform([](auto value) {
        return std::to_string(value);
    });

    // use 3
    std::expected<std::string, ErrorType> result3 = as_expected(optional_value, ErrorType{}).transform([](auto value) {
        return std::to_string(value);
    });
}

https://godbolt./z/rToW34sjG

Note: This function will also work for pointer values.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744936827a4602076.html

相关推荐

  • c++ - How to convert a std::optional to a std::expected? - Stack Overflow

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

    1天前
    60

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信