base/include/base/expected/expected.hpp¶
Provides a C++17 implementation of std::expected (C++23). More...
Namespaces¶
| Name |
|---|
| cf |
Classes¶
| Name | |
|---|---|
| class | cf::bad_expected_access< void > Specialization of bad_expected_access for void error type. |
| class | cf::bad_expected_access Exception thrown when accessing the value of an expected that contains an error. |
| class | cf::unexpected Wrapper type for constructing expected objects in error state. |
| struct | cf::unexpect_t Tag type for constructing expected in error state. |
| class | cf::expected A wrapper that contains either a value or an error. |
| class | cf::expected< void, E > Specialization of expected for void value type. |
Detailed Description¶
Provides a C++17 implementation of std::expected (C++23).
Author: Charliechen114514
Version: 0.1
Since: 0.1
Date: 2026-02-22
Offers a type-safe error handling mechanism that uses values instead of exceptions. Compatible with compilers that do not yet support C++23.
Source code¶
#pragma once
#include <exception>
#include <initializer_list>
#include <type_traits>
#include <utility>
namespace cf {
template <typename E> class bad_expected_access;
template <> class bad_expected_access<void> : public std::exception {
public:
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,
typename = std::enable_if_t<!std::is_same_v<std::decay_t<Err>, unexpected> &&
std::is_constructible_v<E, Err>>>
constexpr explicit unexpected(Err&& e) : error_(std::forward<Err>(e)) {}
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<E, Args...>>>
constexpr explicit unexpected(std::in_place_t, Args&&... args)
: error_(std::forward<Args>(args)...) {}
template <
typename U, typename... Args,
typename = std::enable_if_t<std::is_constructible_v<E, std::initializer_list<U>&, Args...>>>
constexpr explicit unexpected(std::in_place_t, std::initializer_list<U> il, Args&&... 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,
typename = std::enable_if_t<!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>>>
constexpr expected(U&& v) : has_val_(true) {
::new (&storage_.val) T(std::forward<U>(v));
}
template <typename G = E, typename = std::enable_if_t<std::is_constructible_v<E, const G&>>>
constexpr expected(const unexpected<G>& u) : has_val_(false) {
::new (&storage_.err) E(u.error());
}
template <typename G = E, typename = std::enable_if_t<std::is_constructible_v<E, G>>>
constexpr expected(unexpected<G>&& u) : has_val_(false) {
::new (&storage_.err) E(std::move(u.error()));
}
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
constexpr explicit expected(std::in_place_t, Args&&... args) : has_val_(true) {
::new (&storage_.val) T(std::forward<Args>(args)...);
}
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<E, Args...>>>
constexpr explicit expected(unexpect_t, Args&&... 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>
std::enable_if_t<!std::is_same_v<std::decay_t<U>, expected> && std::is_constructible_v<T, U> &&
std::is_assignable_v<T&, U>,
expected&>
operator=(U&& v) {
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;
}
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);
else
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);
else
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));
else
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);
else
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);
else
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));
else
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));
else
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));
else
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)));
else
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));
else
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));
else
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)));
else
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) noexcept : has_val_(true) {}
template <typename... Args> constexpr explicit expected(unexpect_t, 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)();
else
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)();
else
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)());
else
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)());
else
return expected<U, E>(unexpect, std::move(storage_.err));
}
};
} // namespace cf
Updated on 2026-03-09 at 10:14:01 +0000