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 thanstd::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, anoptional
will become anexpected
, because a missing value is an error case. (The reverse ofexpected
tooptional
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 thanstd::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, anoptional
will become anexpected
, because a missing value is an error case. (The reverse ofexpected
tooptional
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
|
4 Answers
Reset to default 8You 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 expected
— Result
— is named Ok
).
It has a few benefits:
- we can turn any
optional
into anexpected
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 theunexpected
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
f - a suitable function or Callable object that returns an std::optional
and you are returningstd::expected
. – Marek R Commented Mar 7 at 12:16std::expected
. – user2138149 Commented Mar 7 at 12:23std::optional
. This is not a question about why it doesn't work. – Weijun Zhou Commented Mar 7 at 12:54