Example
constexpr auto error(auto has_error) -> std::experimental::expected<bool, int> {
if (has_error) {
return std::experimental::unexpected{42};
} else {
return true;
}
}
int main() {
assert(error(false));
assert(error(false).value());
assert(not error(true).has_value());
assert(42 == error(true).error());
}
Puzzle
- Can you implement a simplified version of
expected
proposal for error handling?
/*TODO - expected/unexpected*/
template<auto Error, class TValue, class TError>
constexpr auto handle_error(TValue value, TError error) -> expected<TValue, TError> {
if (Error) {
return unexpected{error};
} else {
return value;
}
}
int main() {
using namespace boost::ut;
using namespace std::string_literals;
"handle error"_test = [] {
constexpr auto error = true;
constexpr auto no_error = false;
should("be convertible to bool") = [] {
struct {} _;
expect(handle_error<no_error>(_, _) and
not handle_error< error>(_, _));
};
should("contain a value") = [] {
const auto handle_error = ::handle_error<no_error>(42, std::runtime_error{"error"s});
expect(handle_error and
handle_error.has_value() and
handle_error.value() == 42_i
);
};
should("contain an error") = [] {
const auto handle_error = ::handle_error<error>(42, std::runtime_error{"error"s});
expect(not handle_error and
not handle_error.has_value() and
"error"s == handle_error.error().what()
);
};
};
}
Solutions
struct error_tag {};
template <class TValue, class TError>
struct expected {
constexpr expected(TValue val) : value_{std::in_place_index<0>, val} {}
constexpr expected(TError err, error_tag) : value_{std::in_place_index<1>, err} {}
constexpr explicit operator bool() const { return has_value(); }
constexpr auto has_value() const { return value_.index() == 0; }
constexpr auto value() const { return std::get<0>(value_); }
constexpr auto error() const { return std::get<1>(value_); }
private:
std::variant<TValue, TError> value_;
};
template <class TError>
struct unexpected {
constexpr unexpected(TError error) : error_(error) {}
template <class TValue>
constexpr operator expected<TValue, TError>() { return expected<TValue, TError>{error_, error_tag{}}; }
private:
TError error_;
};
template<class TError>
struct unexpected {
unexpected(TError error) :error(error) {}
TError error;
};
template<class TValue, class TError>
struct expected {
expected(TValue value) : value_(value), has_value_(true) {}
expected(unexpected<TError> un) : has_value_(false), error_(un.error) {}
operator bool() const { return has_value_;}
TValue value() const { return value_; }
bool has_value() const { return has_value_; }
TError error() const { return error_.value(); }
private:
TValue value_;
bool has_value_;
std::optional<TError> error_;
};
template <typename TValue, typename TError>
struct expected {
constexpr explicit(false) expected(const TValue& value)
: val_or_err{std::in_place_index<VAL_INDEX>, value} {}
constexpr explicit(true) expected(const TError& error, [[maybe_unused]] const auto is_value)
: val_or_err{std::in_place_index<ERR_INDEX>, error} {}
constexpr operator bool() const { return has_value(); }
constexpr auto has_value() const { return val_or_err.index() == VAL_INDEX; }
constexpr auto value() const { return std::get<VAL_INDEX>(val_or_err); }
constexpr auto error() const { return std::get<ERR_INDEX>(val_or_err); }
static const auto VAL_INDEX = 0;
static const auto ERR_INDEX = 1;
std::variant<TValue, TError> val_or_err;
};
template <typename TError>
struct unexpected {
constexpr explicit(true) unexpected(const TError& e) : error{e} {}
template <typename TValue>
constexpr operator expected<TValue, TError>() const {
return expected<TValue, TError>{error, false};
}
TError error;
};
template<typename E>
struct unexpected
{
unexpected(E e) : error(std::move(e)) { }
E error;
};
template<typename T, typename E>
struct expected : std::optional<T>
{
expected(T t) : std::optional<T>(std::move(t)) { }
expected(unexpected<E> u) : err(std::move(u.error)) {}
E error() const { return err.value(); }
std::optional<E> err;
};