include/cvw/base/expected.hpp
Namespaces
| Name |
|---|
| cvw |
Classes
| Name | |
|---|---|
| class | cvw::bad_expected_access<void> Specialization of bad_expected_access for void error type. |
| class | cvw::bad_expected_access Exception thrown when accessing the value of an expected that contains an error. |
| class | cvw::unexpected Wrapper type for constructing expected objects in error state. |
| struct | cvw::unexpect_t Tag type for constructing expected in error state. |
| class | cvw::expected A wrapper that contains either a value or an error. |
| class | cvw::expected<void, E> Specialization of expected for void value type. |
Source code
cpp
#pragma once
#include <exception>
#include <initializer_list>
#include <type_traits>
#include <utility>
namespace cvw {
template <typename E> class bad_expected_access;
template <> class bad_expected_access<void> : public std::exception {
public:
[[nodiscard]] const char* what() const noexcept override {
return "bad_expected_access";
}
};
template <typename E> class bad_expected_access
: public bad_expected_access<void> {
public:
explicit bad_expected_access(E e) : error_(std::move(e)) {}
const E& error() const& noexcept { return error_; }
E& error() & noexcept { return error_; }
const E&& error() const&& noexcept { return std::move(error_); }
E&& error() && noexcept { return std::move(error_); }
private:
E error_;
};
template <typename E> class unexpected {
static_assert(!std::is_void_v<E>, "E must not be void");
static_assert(!std::is_reference_v<E>, "E must not be a reference");
public:
unexpected() = delete;
template <typename Err = E> constexpr explicit unexpected(Err&& e)
requires(!std::is_same_v<std::decay_t<Err>, unexpected> &&
std::is_constructible_v<E, Err>)
: error_(std::forward<Err>(e)) {}
template <typename... Args>
constexpr explicit unexpected(std::in_place_t /*unused*/, Args&&... args)
requires(std::is_constructible_v<E, Args...>)
: error_(std::forward<Args>(args)...) {}
template <typename U, typename... Args>
constexpr explicit unexpected(std::in_place_t /*unused*/,
std::initializer_list<U> il, Args&&... args)
requires(std::is_constructible_v<E, std::initializer_list<U>&, Args...>)
: error_(il, std::forward<Args>(args)...) {}
constexpr const E& error() const& noexcept { return error_; }
constexpr E& error() & noexcept { return error_; }
constexpr const E&& error() const&& noexcept { return std::move(error_); }
constexpr E&& error() && noexcept { return std::move(error_); }
void swap(unexpected& other) noexcept(std::is_nothrow_swappable_v<E>) {
using std::swap;
swap(error_, other.error_);
}
template <typename E2> friend constexpr bool
operator==(const unexpected& a, const unexpected<E2>& b) {
return a.error_ == b.error();
}
private:
E error_;
};
template <typename E> unexpected(E) -> unexpected<E>;
struct unexpect_t {
explicit unexpect_t() = default;
};
inline constexpr unexpect_t unexpect{};
template <typename T, typename E> class expected {
static_assert(!std::is_void_v<E>, "E must not be void");
static_assert(
!std::is_reference_v<T>,
"T must not be a reference (use T* or std::reference_wrapper)");
static_assert(!std::is_reference_v<E>, "E must not be a reference");
union Storage {
T val;
E err;
Storage() {}
~Storage() {}
};
Storage storage_;
bool has_val_;
void destroy() noexcept {
if (has_val_) {
storage_.val.~T();
} else {
storage_.err.~E();
}
}
public:
using value_type = T;
using error_type = E;
using unexpected_type = unexpected<E>;
constexpr expected() noexcept(std::is_nothrow_default_constructible_v<T>)
: has_val_(true) {
::new (&storage_.val) T();
}
expected(const expected& o) : has_val_(o.has_val_) {
if (has_val_) {
::new (&storage_.val) T(o.storage_.val);
} else {
::new (&storage_.err) E(o.storage_.err);
}
}
expected(expected&& o) noexcept(std::is_nothrow_move_constructible_v<T> &&
std::is_nothrow_move_constructible_v<E>)
: has_val_(o.has_val_) {
if (has_val_) {
::new (&storage_.val) T(std::move(o.storage_.val));
} else {
::new (&storage_.err) E(std::move(o.storage_.err));
}
}
template <typename U = T> constexpr expected(U&& v)
requires(!std::is_same_v<std::decay_t<U>, expected> &&
!std::is_same_v<std::decay_t<U>, std::in_place_t> &&
!std::is_same_v<std::decay_t<U>, unexpect_t> &&
std::is_constructible_v<T, U>)
: has_val_(true) {
::new (&storage_.val) T(std::forward<U>(v));
}
template <typename G = E> constexpr expected(const unexpected<G>& u)
requires(std::is_constructible_v<E, const G&>)
: has_val_(false) {
::new (&storage_.err) E(u.error());
}
template <typename G = E> constexpr expected(unexpected<G>&& u)
requires(std::is_constructible_v<E, G>)
: has_val_(false) {
::new (&storage_.err) E(std::move(u.error()));
}
template <typename... Args>
constexpr explicit expected(std::in_place_t /*unused*/, Args&&... args)
requires(std::is_constructible_v<T, Args...>)
: has_val_(true) {
::new (&storage_.val) T(std::forward<Args>(args)...);
}
template <typename... Args>
constexpr explicit expected(unexpect_t /*unused*/, Args&&... args)
requires(std::is_constructible_v<E, Args...>)
: has_val_(false) {
::new (&storage_.err) E(std::forward<Args>(args)...);
}
~expected() { destroy(); }
expected& operator=(const expected& o) {
if (this == &o) {
return *this;
}
if (has_val_ && o.has_val_) {
storage_.val = o.storage_.val;
} else if (!has_val_ && !o.has_val_) {
storage_.err = o.storage_.err;
} else {
destroy();
has_val_ = o.has_val_;
if (has_val_) {
::new (&storage_.val) T(o.storage_.val);
} else {
::new (&storage_.err) E(o.storage_.err);
}
}
return *this;
}
expected&
operator=(expected&& o) noexcept(std::is_nothrow_move_assignable_v<T> &&
std::is_nothrow_move_assignable_v<E> &&
std::is_nothrow_move_constructible_v<T> &&
std::is_nothrow_move_constructible_v<E>) {
if (this == &o) {
return *this;
}
if (has_val_ && o.has_val_) {
storage_.val = std::move(o.storage_.val);
} else if (!has_val_ && !o.has_val_) {
storage_.err = std::move(o.storage_.err);
} else {
destroy();
has_val_ = o.has_val_;
if (has_val_) {
::new (&storage_.val) T(std::move(o.storage_.val));
} else {
::new (&storage_.err) E(std::move(o.storage_.err));
}
}
return *this;
}
template <typename U> expected& operator=(U&& v)
requires(!std::is_same_v<std::decay_t<U>, expected> &&
std::is_constructible_v<T, U> && std::is_assignable_v<T&, U>)
{
if (has_val_) {
storage_.val = std::forward<U>(v);
} else {
destroy();
::new (&storage_.val) T(std::forward<U>(v));
has_val_ = true;
}
return *this;
}
template <typename G> expected& operator=(const unexpected<G>& u) {
if (!has_val_) {
storage_.err = u.error();
} else {
destroy();
::new (&storage_.err) E(u.error());
has_val_ = false;
}
return *this;
}
template <typename G> expected& operator=(unexpected<G>&& u) {
if (!has_val_) {
storage_.err = std::move(u.error());
} else {
destroy();
::new (&storage_.err) E(std::move(u.error()));
has_val_ = false;
}
return *this;
}
[[nodiscard]] constexpr bool has_value() const noexcept { return has_val_; }
constexpr explicit operator bool() const noexcept { return has_val_; }
constexpr T* operator->() noexcept { return &storage_.val; }
constexpr const T* operator->() const noexcept { return &storage_.val; }
constexpr T& operator*() & noexcept { return storage_.val; }
constexpr const T& operator*() const& noexcept { return storage_.val; }
constexpr T&& operator*() && noexcept { return std::move(storage_.val); }
constexpr const T&& operator*() const&& noexcept {
return std::move(storage_.val);
}
constexpr T& value() & {
if (!has_val_) {
throw bad_expected_access<E>(storage_.err);
}
return storage_.val;
}
constexpr const T& value() const& {
if (!has_val_) {
throw bad_expected_access<E>(storage_.err);
}
return storage_.val;
}
constexpr T&& value() && {
if (!has_val_) {
throw bad_expected_access<E>(std::move(storage_.err));
}
return std::move(storage_.val);
}
constexpr const T&& value() const&& {
if (!has_val_) {
throw bad_expected_access<E>(std::move(storage_.err));
}
return std::move(storage_.val);
}
constexpr E& error() & noexcept { return storage_.err; }
constexpr const E& error() const& noexcept { return storage_.err; }
constexpr E&& error() && noexcept { return std::move(storage_.err); }
constexpr const E&& error() const&& noexcept {
return std::move(storage_.err);
}
template <typename U> constexpr T value_or(U&& default_val) const& {
return has_val_ ? storage_.val
: static_cast<T>(std::forward<U>(default_val));
}
template <typename U> constexpr T value_or(U&& default_val) && {
return has_val_ ? std::move(storage_.val)
: static_cast<T>(std::forward<U>(default_val));
}
template <typename U> constexpr E error_or(U&& default_err) const& {
return !has_val_ ? storage_.err
: static_cast<E>(std::forward<U>(default_err));
}
template <typename U> constexpr E error_or(U&& default_err) && {
return !has_val_ ? std::move(storage_.err)
: static_cast<E>(std::forward<U>(default_err));
}
template <typename F> auto and_then(F&& f) & {
using Ret = std::invoke_result_t<F, T&>;
if (has_val_) {
return std::forward<F>(f)(storage_.val);
}
return Ret(unexpect, storage_.err);
}
template <typename F> auto and_then(F&& f) const& {
using Ret = std::invoke_result_t<F, const T&>;
if (has_val_) {
return std::forward<F>(f)(storage_.val);
}
return Ret(unexpect, storage_.err);
}
template <typename F> auto and_then(F&& f) && {
using Ret = std::invoke_result_t<F, T&&>;
if (has_val_) {
return std::forward<F>(f)(std::move(storage_.val));
}
return Ret(unexpect, std::move(storage_.err));
}
template <typename F> auto or_else(F&& f) & {
using Ret = std::invoke_result_t<F, E&>;
if (!has_val_) {
return std::forward<F>(f)(storage_.err);
}
return Ret(storage_.val);
}
template <typename F> auto or_else(F&& f) const& {
using Ret = std::invoke_result_t<F, const E&>;
if (!has_val_) {
return std::forward<F>(f)(storage_.err);
}
return Ret(storage_.val);
}
template <typename F> auto or_else(F&& f) && {
using Ret = std::invoke_result_t<F, E&&>;
if (!has_val_) {
return std::forward<F>(f)(std::move(storage_.err));
}
return Ret(std::move(storage_.val));
}
template <typename F> auto transform(F&& f) & {
using U = std::invoke_result_t<F, T&>;
if (has_val_) {
return expected<U, E>(std::forward<F>(f)(storage_.val));
}
return expected<U, E>(unexpect, storage_.err);
}
template <typename F> auto transform(F&& f) const& {
using U = std::invoke_result_t<F, const T&>;
if (has_val_) {
return expected<U, E>(std::forward<F>(f)(storage_.val));
}
return expected<U, E>(unexpect, storage_.err);
}
template <typename F> auto transform(F&& f) && {
using U = std::invoke_result_t<F, T&&>;
if (has_val_) {
return expected<U, E>(std::forward<F>(f)(std::move(storage_.val)));
}
return expected<U, E>(unexpect, std::move(storage_.err));
}
template <typename F> auto transform_error(F&& f) & {
using G = std::invoke_result_t<F, E&>;
if (!has_val_) {
return expected<T, G>(unexpect, std::forward<F>(f)(storage_.err));
}
return expected<T, G>(storage_.val);
}
template <typename F> auto transform_error(F&& f) const& {
using G = std::invoke_result_t<F, const E&>;
if (!has_val_) {
return expected<T, G>(unexpect, std::forward<F>(f)(storage_.err));
}
return expected<T, G>(storage_.val);
}
template <typename F> auto transform_error(F&& f) && {
using G = std::invoke_result_t<F, E&&>;
if (!has_val_) {
return expected<T, G>(unexpect,
std::forward<F>(f)(std::move(storage_.err)));
}
return expected<T, G>(std::move(storage_.val));
}
template <typename T2, typename E2> friend constexpr bool
operator==(const expected& a, const expected<T2, E2>& b) {
if (a.has_val_ != b.has_value()) {
return false;
}
if (a.has_val_) {
return *a == *b;
}
return a.error() == b.error();
}
template <typename T2>
friend constexpr bool operator==(const expected& a, const T2& v) {
return a.has_val_ && (*a == v);
}
template <typename E2> friend constexpr bool
operator==(const expected& a, const unexpected<E2>& u) {
return !a.has_val_ && (a.error() == u.error());
}
template <typename T2, typename E2> friend constexpr bool
operator!=(const expected& a, const expected<T2, E2>& b) {
return !(a == b);
}
template <typename T2>
friend constexpr bool operator!=(const expected& a, const T2& v) {
return !(a == v);
}
template <typename E2> friend constexpr bool
operator!=(const expected& a, const unexpected<E2>& u) {
return !(a == u);
}
void swap(expected& o) noexcept(std::is_nothrow_move_constructible_v<T> &&
std::is_nothrow_swappable_v<T> &&
std::is_nothrow_move_constructible_v<E> &&
std::is_nothrow_swappable_v<E>) {
using std::swap;
if (has_val_ && o.has_val_) {
swap(storage_.val, o.storage_.val);
} else if (!has_val_ && !o.has_val_) {
swap(storage_.err, o.storage_.err);
} else {
// Swap between value-holding and error-holding expected
if (has_val_) {
// this has value, o has error
T tmp_val(std::move(storage_.val));
storage_.val.~T();
::new (&storage_.err) E(std::move(o.storage_.err));
o.storage_.err.~E();
::new (&o.storage_.val) T(std::move(tmp_val));
// tmp_val is destroyed here
has_val_ = false;
o.has_val_ = true;
} else {
// this has error, o has value
E tmp_err(std::move(storage_.err));
storage_.err.~E();
::new (&storage_.val) T(std::move(o.storage_.val));
o.storage_.val.~T();
::new (&o.storage_.err) E(std::move(tmp_err));
// tmp_err is destroyed here
has_val_ = true;
o.has_val_ = false;
}
}
}
friend void swap(expected& a, expected& b) noexcept(noexcept(a.swap(b))) {
a.swap(b);
}
};
template <typename E> class expected<void, E> {
static_assert(!std::is_void_v<E>, "E must not be void");
union Storage {
E err;
Storage() {}
~Storage() {}
};
Storage storage_;
bool has_val_;
public:
using value_type = void;
using error_type = E;
using unexpected_type = unexpected<E>;
constexpr expected() noexcept : has_val_(true) {}
expected(const expected& o) : has_val_(o.has_val_) {
if (!has_val_) {
::new (&storage_.err) E(o.storage_.err);
}
}
expected(expected&& o) noexcept(std::is_nothrow_move_constructible_v<E>)
: has_val_(o.has_val_) {
if (!has_val_) {
::new (&storage_.err) E(std::move(o.storage_.err));
}
}
template <typename G = E> constexpr expected(const unexpected<G>& u)
: has_val_(false) {
::new (&storage_.err) E(u.error());
}
template <typename G = E> constexpr expected(unexpected<G>&& u)
: has_val_(false) {
::new (&storage_.err) E(std::move(u.error()));
}
constexpr explicit expected(std::in_place_t /*unused*/) noexcept
: has_val_(true) {}
template <typename... Args>
constexpr explicit expected(unexpect_t /*unused*/, Args&&... args)
: has_val_(false) {
::new (&storage_.err) E(std::forward<Args>(args)...);
}
~expected() {
if (!has_val_) {
storage_.err.~E();
}
}
expected& operator=(const expected& o) {
if (has_val_ && o.has_val_) {
} else if (!has_val_ && !o.has_val_) {
storage_.err = o.storage_.err;
} else if (has_val_) {
::new (&storage_.err) E(o.storage_.err);
has_val_ = false;
} else {
storage_.err.~E();
has_val_ = true;
}
return *this;
}
constexpr bool has_value() const noexcept { return has_val_; }
constexpr explicit operator bool() const noexcept { return has_val_; }
constexpr void value() const {
if (!has_val_) {
throw bad_expected_access<E>(storage_.err);
}
}
constexpr E& error() & noexcept { return storage_.err; }
constexpr const E& error() const& noexcept { return storage_.err; }
constexpr E&& error() && noexcept { return std::move(storage_.err); }
constexpr const E&& error() const&& noexcept {
return std::move(storage_.err);
}
template <typename F> auto and_then(F&& f) & {
using Ret = std::invoke_result_t<F>;
if (has_val_) {
return std::forward<F>(f)();
}
return Ret(unexpect, storage_.err);
}
template <typename F> auto and_then(F&& f) && {
using Ret = std::invoke_result_t<F>;
if (has_val_) {
return std::forward<F>(f)();
}
return Ret(unexpect, std::move(storage_.err));
}
template <typename F> auto transform(F&& f) & {
using U = std::invoke_result_t<F>;
if (has_val_) {
return expected<U, E>(std::forward<F>(f)());
}
return expected<U, E>(unexpect, storage_.err);
}
template <typename F> auto transform(F&& f) && {
using U = std::invoke_result_t<F>;
if (has_val_) {
return expected<U, E>(std::forward<F>(f)());
}
return expected<U, E>(unexpect, std::move(storage_.err));
}
};
} // namespace cvwUpdated on 2026-05-17 at 13:22:38 +0000