Example
enum class error { runtime_error };
[[nodiscard]] auto foo() -> std::expected<int, error> {
return std::unexpected(error::runtime_error);
}
int main() {
const auto e = foo();
e.and_then([](const auto& e) -> std::expected<int, error> {
std::cout << int(e); // not printed
return {};
}).or_else([](const auto& e) -> std::expected<int, error> {
std::cout << int(e); // prints 0
return {};
});
}
Puzzle
Can you refactor execute routine with Monadic std::expected operations?
enum class error {
trade,
model,
order_id,
};
struct market_data {};
struct trade {};
struct order {};
struct order_with_id {};
std::expected<order_with_id, error> execute(auto& ts, const market_data& md) {
auto trade = ts.parse(md);
if (not trade) {
return std::unexpected(error::trade);
}
auto order = ts.model(*trade);
if (not order) {
return std::unexpected(error::model);
}
if (auto order_with_id = ts.map(*order); order_with_id) {
return *order_with_id;
} else {
return std::unexpected(error::order_id);
}
}
int main() {
using namespace boost::ut;
should("error on empty trade") = [] {
struct {
auto parse(const market_data&) -> std::optional<trade> {
return {};
}
auto model(const trade&) -> std::optional<order> { return {{}}; }
auto map(const order&) -> std::optional<order_with_id> {
return {{}};
}
} fake_ts;
const auto e = execute(fake_ts, {});
expect(not e and error::trade == e.error());
};
should("error on empty model") = [] {
struct {
auto parse(const market_data&) -> std::optional<trade> {
return {{}};
}
auto model(const trade&) -> std::optional<order> { return {}; }
auto map(const order&) -> std::optional<order_with_id> {
return {{}};
}
} fake_ts;
const auto e = execute(fake_ts, {});
expect(not e and error::model == e.error());
};
should("error on order_id") = [] {
struct {
auto parse(const market_data&) -> std::optional<trade> {
return {{}};
}
auto model(const trade&) -> std::optional<order> { return {{}}; }
auto map(const order&) -> std::optional<order_with_id> {
return {};
}
} fake_ts;
const auto e = execute(fake_ts, {});
expect(not e and error::order_id == e.error());
};
should("produce an order") = [] {
struct {
auto parse(const market_data&) -> std::optional<trade> {
return {{}};
}
auto model(const trade&) -> std::optional<order> { return {{}}; }
auto map(const order&) -> std::optional<order_with_id> {
return {{}};
}
} fake_ts;
expect(execute(fake_ts, {}).has_value());
};
}
Solutions
std::expected<order_with_id, error> execute(auto &ts, const market_data &md) {
constexpr auto expected_or = []<class T>(const std::optional<T> &e,
error u) {
return e ? std::expected<T, error>(*e) : std::unexpected(u);
};
return expected_or(ts.parse(md), error::trade)
.and_then([&](const auto &trade) {
return expected_or(ts.model(trade), error::model);
})
.and_then([&](const auto &order) {
return expected_or(ts.map(order), error::order_id);
});
}
std::expected<order_with_id, error> execute(auto& ts, const market_data& md) {
auto as_expected = []<typename T>(const std::optional<T>& o,
error e) -> std::expected<T, error> {
return o ? std::expected<T, error>(*o) : std::unexpected(e);
};
return as_expected(ts.parse(md), error::trade)
.and_then([&](const auto& trade) {
return as_expected(ts.model(trade), error::model);
})
.and_then([&](const auto& order) {
return as_expected(ts.map(order), error::order_id);
});
}
template <class T>
constexpr std::expected<T, error> to_expected(std::optional<T> t, error e) {
if (t) {
return *t;
} else {
return std::unexpected(e);
}
};
std::expected<order_with_id, error> execute(auto& ts, const market_data& md) {
return to_expected(ts.parse(md), error::trade)
.and_then([&](const auto& trade) {
return to_expected(ts.model(trade), error::model);
})
.and_then([&](const auto& order) {
return to_expected(ts.map(order), error::order_id);
});
}
std::expected<order_with_id, error> execute(auto& ts, const market_data& md) {
auto to_expected = []<typename T>(std::optional<T> o,
error e) -> std::expected<T, error> {
return o.has_value() ? std::expected<T, error>{*o} : std::unexpected{e};
};
return to_expected(ts.parse(md), error::trade)
.and_then(
[&](auto v) { return to_expected(ts.model(v), error::model); })
.and_then(
[&](auto v) { return to_expected(ts.map(v), error::order_id); });
}
template <typename T>
constexpr std::expected<T, error> optional_to_expected(
const std::optional<T>&& o, const error e) {
return o.has_value() ? std::expected<T, error>(o.value())
: std::unexpected(e);
}
std::expected<order_with_id, error> execute(auto& ts, const market_data& md) {
return optional_to_expected(ts.parse(md), error::trade)
.and_then([&](const auto& trade) {
return optional_to_expected(ts.model(trade), error::model);
})
.and_then([&](const auto& order) {
return optional_to_expected(ts.map(order), error::order_id);
});
}