Skip to content

Instantly share code, notes, and snippets.

@BreadFish64
Created January 8, 2025 05:47
Show Gist options
  • Save BreadFish64/453bc4005b2e28578738ed83851d7720 to your computer and use it in GitHub Desktop.
Save BreadFish64/453bc4005b2e28578738ed83851d7720 to your computer and use it in GitHub Desktop.
C++20 implementation of std::expected which passes MSVC's unit tests
#pragma once
#include <cassert>
#include <concepts>
#include <memory>
#include <type_traits>
#include <utility>
namespace breaded {
#pragma region Forward Declarations
template <class E>
class bad_expected_access;
template <class E>
class unexpected;
template <class T, class E>
class expected;
#pragma endregion
// https://en.cppreference.com/w/cpp/utility/expected/unexpect_t
struct unexpect_t {
constexpr explicit unexpect_t() = default;
};
// https://en.cppreference.com/w/cpp/utility/expected/unexpect_t
constexpr unexpect_t unexpect{};
#pragma region Traits
namespace details {
struct in_place_from_return_t {
constexpr explicit in_place_from_return_t() = default;
};
constexpr in_place_from_return_t in_place_from_return{};
struct unexpect_from_return_t {
constexpr explicit unexpect_from_return_t() = default;
};
constexpr unexpect_from_return_t unexpect_from_return{};
template <class T>
struct IsSpecializationOfUnexpected : std::false_type {};
template <class T, class E>
struct IsSpecializationOfUnexpected<breaded::expected<T, E>> : std::true_type {};
template <class T>
constexpr bool kIsSpecializationOfUnexpected = IsSpecializationOfUnexpected<T>::value;
template <class T>
struct IsSpecializationOfExpected : std::false_type {};
template <class T, class E>
struct IsSpecializationOfExpected<breaded::expected<T, E>> : std::true_type {};
template <class T>
constexpr bool kIsSpecializationOfExpected = IsSpecializationOfExpected<T>::value;
// https://en.cppreference.com/w/cpp/named_req/Destructible
// This isn't quite the same as std::is_destructible_v<T>
// This does not allow arrays or reference types.
template <class T>
concept NamedRequirementDestructible =
std::is_nothrow_destructible_v<T> && !std::is_array_v<T> && !std::is_reference_v<T>;
template <typename T>
concept NotTag = !std::is_same_v<std::remove_cvref_t<T>, std::in_place_t> &&
!std::is_same_v<std::remove_cvref_t<T>, breaded::unexpect_t>;
template <typename E>
concept ValidExpectedErrorType =
std::is_object_v<E> && !std::is_const_v<E> && !std::is_volatile_v<E> && !kIsSpecializationOfExpected<E>;
template <class T>
concept ValidExpectedValueType =
NotTag<T> && !std::is_reference_v<T> && !std::is_function_v<T> && !kIsSpecializationOfUnexpected<T> &&
(std::is_void_v<T> || NamedRequirementDestructible<T>);
} // namespace details
#pragma endregion
#pragma region bad_expected_access
// https://en.cppreference.com/w/cpp/utility/expected/bad_expected_access
template <>
class bad_expected_access<void> : public std::exception {
public:
// https://en.cppreference.com/w/cpp/utility/expected/bad_expected_access#std::bad_expected_access::what
const char* what() const noexcept override { return "Attempted to access the value of an errored expected type"; }
protected:
// https://en.cppreference.com/w/cpp/utility/expected/bad_expected_access#std::bad_expected_access::bad_expected_access
explicit bad_expected_access() = default;
bad_expected_access(const bad_expected_access& other) = default;
bad_expected_access(bad_expected_access&& other) = default;
~bad_expected_access() override = default;
bad_expected_access& operator=(const bad_expected_access& other) = default;
bad_expected_access& operator=(bad_expected_access&& other) = default;
};
// https://en.cppreference.com/w/cpp/utility/expected/bad_expected_access
template <class E>
class bad_expected_access : public breaded::bad_expected_access<void> {
public:
// https://en.cppreference.com/w/cpp/utility/expected/bad_expected_access#std::bad_expected_access::bad_expected_access
explicit bad_expected_access(E e) : error_{std::move(e)} {}
// https://en.cppreference.com/w/cpp/utility/expected/bad_expected_access#std::bad_expected_access::error
[[nodiscard]] const E& error() const& noexcept { return error_; }
// https://en.cppreference.com/w/cpp/utility/expected/bad_expected_access#std::bad_expected_access::error
[[nodiscard]] E& error() & noexcept { return error_; }
// https://en.cppreference.com/w/cpp/utility/expected/bad_expected_access#std::bad_expected_access::error
[[nodiscard]] const E&& error() const&& noexcept { return std::move(error_); }
// https://en.cppreference.com/w/cpp/utility/expected/bad_expected_access#std::bad_expected_access::error
[[nodiscard]] E&& error() && noexcept { return std::move(error_); }
private:
E error_;
};
#pragma endregion
#pragma region Unexpected
// https://en.cppreference.com/w/cpp/utility/expected/unexpected
template <class E>
class unexpected {
static_assert(details::ValidExpectedErrorType<E>);
public:
unexpected() = delete;
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#ctor
constexpr unexpected(const unexpected&) noexcept(std::is_nothrow_copy_constructible_v<E>) = default;
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#ctor
constexpr unexpected(unexpected&&) noexcept(std::is_nothrow_move_constructible_v<E>) = default;
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#ctor
template <class Err = E>
requires(!std::is_same_v<std::remove_cvref_t<Err>, unexpected> &&
!std::is_same_v<std::remove_cvref_t<Err>, std::in_place_t> && std::is_constructible_v<E, Err>)
constexpr explicit unexpected(Err&& e) noexcept(std::is_nothrow_constructible_v<E, Err>)
: error_(std::forward<Err>(e)) {}
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#ctor
template <class... Args>
requires(std::is_constructible_v<E, Args...>)
constexpr explicit unexpected(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v<E, Args...>)
: error_(std::forward<Args>(args)...) {}
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#ctor
template <class U, class... Args>
requires(std::is_constructible_v<E, std::initializer_list<U>&, Args...>)
constexpr explicit unexpected(std::in_place_t, std::initializer_list<U> il, Args&&... args) noexcept(
std::is_nothrow_constructible_v<E, std::initializer_list<U>&, Args...>)
: error_(il, std::forward<Args>(args)...) {}
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#error
[[nodiscard]] constexpr const E& error() const& noexcept { return error_; }
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#error
[[nodiscard]] constexpr E& error() & noexcept { return error_; }
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#error
[[nodiscard]] constexpr const E&& error() const&& noexcept { return std::move(error_); }
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#error
[[nodiscard]] constexpr E&& error() && noexcept { return std::move(error_); }
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#swap
constexpr void swap(unexpected& other) noexcept(std::is_nothrow_swappable_v<E>) {
// Prefer custom swap friend function, and use move-based std::swap as a fallback
// https://en.cppreference.com/w/cpp/language/adl
using std::swap;
swap(error_, other.error_);
}
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#operator.3D.3D.28std::unexpected.29
template <class E2>
[[nodiscard]] friend constexpr bool operator==(const unexpected& x,
const breaded::unexpected<E2>& y) noexcept(noexcept(x.error() ==
y.error())) {
return x.error() == y.error();
}
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#swap.28std::unexpected.29
friend constexpr void swap(unexpected& x, unexpected& y) noexcept(std::is_nothrow_swappable_v<E>) { x.swap(y); }
private:
E error_;
};
// https://en.cppreference.com/w/cpp/utility/expected/unexpected#Deduction_guides
template <class E>
unexpected(E) -> unexpected<E>;
#pragma endregion
namespace details {
template <class NewType, class OldType, class... Args>
constexpr bool kUnionAssignable =
std::is_constructible_v<NewType, Args...> &&
(std::is_nothrow_constructible_v<NewType, Args...> || std::is_nothrow_move_constructible_v<NewType> ||
std::is_nothrow_move_constructible_v<OldType>);
template <class NewType, class OldType, class... Args>
constexpr bool kNothrowUnionAssignable =
kUnionAssignable<NewType, OldType, Args...> && std::is_nothrow_constructible_v<NewType, Args...>;
// This function allows assigning to an inactive union member,
// while preserving the old active member if the assignment throws an exception.
// This is required for std::expected since it can never be value-less
//
// "If an exception is thrown, the old value is retained; *this does not become valueless."
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D#Helper_function_template
template <class NewType, class OldType, class... Args>
constexpr void UnionAssign(NewType& newVal, OldType& oldVal,
Args&&... args) noexcept(kNothrowUnionAssignable<NewType, OldType, Args...>)
requires(kUnionAssignable<NewType, OldType, Args...>)
{
if constexpr (std::is_nothrow_constructible_v<NewType, Args...>) {
std::destroy_at(std::addressof(oldVal));
std::construct_at(std::addressof(newVal), std::forward<Args>(args)...);
} else if constexpr (std::is_nothrow_move_constructible_v<NewType>) {
NewType temp(std::forward<Args>(args)...);
std::destroy_at(std::addressof(oldVal));
std::construct_at(std::addressof(newVal), std::move(temp));
} else if constexpr (std::is_nothrow_move_constructible_v<OldType>) {
OldType temp(std::move(oldVal));
std::destroy_at(std::addressof(oldVal));
try {
std::construct_at(std::addressof(newVal), std::forward<Args>(args)...);
} catch (...) {
std::construct_at(std::addressof(oldVal), std::move(temp));
throw;
}
} else {
static_assert(sizeof(NewType) == 0,
"Sanity check failed. Cannot call without fulfilling one of these conditions");
}
}
template <class T, class U>
constexpr bool kUnionSwappable = std::is_move_constructible_v<T> && std::is_move_constructible_v<U> &&
(std::is_nothrow_move_constructible_v<T> || std::is_nothrow_move_constructible_v<U>);
template <class T, class U>
constexpr bool kNothrowUnionSwappable =
std::is_nothrow_move_constructible_v<T> && std::is_nothrow_move_constructible_v<U>;
// https://en.cppreference.com/w/cpp/utility/expected/swap
template <class T, class U>
constexpr void UnionSwap(T& dstT, U& dstU, T&& srcT, U&& srcU) noexcept(kNothrowUnionSwappable<T, U>)
requires(kUnionSwappable<T, U>)
{
if constexpr (std::is_nothrow_move_constructible_v<T>) {
T tmp(std::move(srcT));
std::destroy_at(std::addressof(srcT));
if constexpr (std::is_nothrow_move_constructible_v<U>) {
std::construct_at(std::addressof(dstU), std::move(srcU));
} else {
try {
std::construct_at(std::addressof(dstU), std::move(srcU));
} catch (...) {
std::construct_at(std::addressof(srcT), std::move(tmp));
throw;
}
}
std::destroy_at(std::addressof(srcU));
std::construct_at(std::addressof(dstT), std::move(tmp));
} else {
static_assert(std::is_nothrow_move_constructible_v<U>,
"Either T or U must be nothrow_move_constructible to enforce strong exception safety");
UnionSwap(dstU, dstT, std::move(srcU), std::move(srcT));
}
}
} // namespace details
#pragma region Expected T
// https://en.cppreference.com/w/cpp/utility/expected
template <class T, class E>
class expected {
static_assert(details::ValidExpectedValueType<T>);
static_assert(details::ValidExpectedErrorType<E>);
static_assert(!std::is_void_v<T>, "Sanity check failed. This should be handled in a specialization");
template <typename Self, typename F>
struct AndThenReturn {
using type = std::remove_cvref_t<std::invoke_result_t<F, decltype(*std::declval<Self>())>>;
};
template <typename Self, typename F>
struct TransformReturn {
using URaw = std::invoke_result_t<F, decltype(*std::declval<Self>())>;
using U = std::remove_cvref_t<URaw>;
using type = breaded::expected<U, E>;
};
template <typename Self, typename F>
struct OrElseReturn {
using type = std::remove_cvref_t<std::invoke_result_t<F, decltype(std::declval<Self>().error())>>;
};
template <typename Self, typename F>
struct TransformErrorReturn {
using GRaw = std::invoke_result_t<F, decltype(std::declval<Self>().error())>;
using G = std::remove_cvref_t<GRaw>;
using type = breaded::expected<T, G>;
};
public:
#pragma region Member types
// https://en.cppreference.com/w/cpp/utility/expected#Member_types
using value_type = T;
using error_type = E;
using unexpected_type = breaded::unexpected<E>;
#pragma endregion
#pragma region Member alias templates
// https://en.cppreference.com/w/cpp/utility/expected#Member_alias_templates
template <class U>
using rebind = breaded::expected<U, error_type>;
#pragma endregion
private:
template <typename U>
static constexpr bool kSameAsThis = std::is_same_v<expected, std::remove_cvref_t<U>>;
static constexpr bool kValueTypeIsBool = std::is_same_v<bool, std::remove_cvref_t<T>>;
template <class U>
static constexpr bool kExpectedConvertibleToValueType =
std::is_constructible_v<T, U&> || std::is_constructible_v<T, U> || std::is_constructible_v<T, const U&> ||
std::is_constructible_v<T, const U> || std::is_convertible_v<U&, T> || std::is_convertible_v<U, T> ||
std::is_convertible_v<const U&, T> || std::is_convertible_v<const U, T>;
template <class U>
static constexpr bool kExpectedConvertibleToErrorType =
std::is_constructible_v<unexpected_type, U&> || std::is_constructible_v<unexpected_type, U> ||
std::is_constructible_v<unexpected_type, const U&> || std::is_constructible_v<unexpected_type, const U>;
public:
#pragma region Member functions
// https://en.cppreference.com/w/cpp/utility/expected#Member_functions
#pragma region constructor
// Default value constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
constexpr expected() noexcept(std::is_nothrow_default_constructible_v<T>)
requires(std::is_default_constructible_v<T>)
: value_(), hasValue_(true) {}
// Non-trivial copy constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
constexpr expected(const expected& other) noexcept(std::is_nothrow_copy_constructible_v<T> &&
std::is_nothrow_copy_constructible_v<E>)
requires(std::is_copy_constructible_v<T> && std::is_copy_constructible_v<E> &&
!(std::is_trivially_copy_constructible_v<T> && std::is_trivially_copy_constructible_v<E>))
{
constructFromExpected(other);
}
// Trivial copy constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
expected(const expected& other)
requires(std::is_copy_constructible_v<T> && std::is_copy_constructible_v<E> &&
(std::is_trivially_copy_constructible_v<T> && std::is_trivially_copy_constructible_v<E>))
= default;
// This constructor is defined as deleted unless is_copy_constructible_v<T> is true
// and is_copy_constructible_v<E> is true
// https://github.com/cplusplus/draft/blob/d29ef68d3c7a1c2317f4670f08bbc8a63828ce35/source/utilities.tex#L7423
expected(const expected&) = delete;
// Non-trivial move constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
constexpr expected(expected&& other) noexcept(std::is_nothrow_move_constructible_v<T> &&
std::is_nothrow_move_constructible_v<E>)
requires(std::is_move_constructible_v<T> && std::is_move_constructible_v<E> &&
!(std::is_trivially_move_constructible_v<T> && std::is_trivially_move_constructible_v<E>))
{
constructFromExpected(std::move(other));
}
// Trivial move constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
expected(expected&& other)
requires(std::is_move_constructible_v<T> && std::is_move_constructible_v<E> &&
(std::is_trivially_move_constructible_v<T> && std::is_trivially_move_constructible_v<E>))
= default;
// Converting copy construct from expected
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class U, class G>
requires(!kSameAsThis<breaded::expected<U, G>> && std::is_constructible_v<T, const U&> &&
(kValueTypeIsBool || !kExpectedConvertibleToValueType<breaded::expected<U, G>>) &&
!kExpectedConvertibleToErrorType<breaded::expected<U, G>>)
constexpr explicit(!std::is_convertible_v<const U&, T> || !std::is_convertible_v<const G&, E>)
expected(const breaded::expected<U, G>& other) noexcept(std::is_nothrow_constructible_v<T, const U&> &&
std::is_nothrow_constructible_v<E, const G&>) {
constructFromExpected(other);
}
// Converting move construct from expected
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class U, class G>
requires(!kSameAsThis<breaded::expected<U, G>> && std::is_constructible_v<T, U> &&
(kValueTypeIsBool || !kExpectedConvertibleToValueType<breaded::expected<U, G>>) &&
!kExpectedConvertibleToErrorType<breaded::expected<U, G>>)
constexpr explicit(!std::is_convertible_v<U, T> || !std::is_convertible_v<G, E>)
expected(breaded::expected<U, G>&& other) noexcept(std::is_nothrow_constructible_v<T, U> &&
std::is_nothrow_constructible_v<E, G>) {
constructFromExpected(std::move(other));
}
// Constructor from value
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class U = T>
requires(details::NotTag<T> && !kSameAsThis<U> && std::is_constructible_v<T, U> &&
!details::kIsSpecializationOfUnexpected<std::remove_cvref_t<U>> &&
(!kValueTypeIsBool || !details::kIsSpecializationOfUnexpected<std::remove_cvref_t<U>>))
constexpr explicit(!std::is_convertible_v<U, T>) expected(U&& v) noexcept(std::is_nothrow_constructible_v<T, U>)
: value_(std::forward<U>(v)), hasValue_(true) {}
// Copy constructor from unexpected
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class G>
requires(std::is_constructible_v<E, const G&>)
constexpr explicit(!std::is_convertible_v<const G&, E>)
expected(const breaded::unexpected<G>& e) noexcept(std::is_nothrow_constructible_v<E, const G&>)
: error_(e.error()), hasValue_(false) {}
// Move constructor from unexpected
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class G>
requires(std::is_constructible_v<E, G>)
constexpr explicit(!std::is_convertible_v<G, E>)
expected(breaded::unexpected<G>&& e) noexcept(std::is_nothrow_constructible_v<E, G>)
: error_(std::move(e).error()), hasValue_(false) {}
// In-place value constructor from args
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class... Args>
requires(std::is_constructible_v<T, Args...>)
constexpr explicit expected(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
: value_(std::forward<Args>(args)...), hasValue_(true) {}
// In-place value constructor from initializer list
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class U, class... Args>
requires(std::is_constructible_v<T, std::initializer_list<U>&, Args...>)
constexpr explicit expected(std::in_place_t, std::initializer_list<U> il, Args&&... args) noexcept(
std::is_nothrow_constructible_v<T, std::initializer_list<U>&, Args...>)
: value_(il, std::forward<Args>(args)...), hasValue_(true) {}
// In-place error constructor from args
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class... Args>
requires(std::is_constructible_v<E, Args...>)
constexpr explicit expected(breaded::unexpect_t,
Args&&... args) noexcept(std::is_nothrow_constructible_v<E, Args...>)
: error_(std::forward<Args>(args)...), hasValue_(false) {}
// In-place error constructor from initializer list
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class U, class... Args>
requires(std::is_constructible_v<E, std::initializer_list<U>&, Args...>)
constexpr explicit expected(breaded::unexpect_t, std::initializer_list<U> il, Args&&... args) noexcept(
std::is_nothrow_constructible_v<E, std::initializer_list<U>&, Args...>)
: error_(il, std::forward<Args>(args)...), hasValue_(false) {}
#pragma endregion
// https://en.cppreference.com/w/cpp/utility/expected/~expected#Main_template_destructor
constexpr ~expected() noexcept { destroy(); }
// https://en.cppreference.com/w/cpp/utility/expected/~expected#Main_template_destructor
~expected()
requires(std::is_trivially_destructible_v<T> && std::is_trivially_destructible_v<E>)
= default;
#pragma region Assignment
// Non-trivial copy assignment
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
constexpr expected& operator=(const expected& other) noexcept(std::is_nothrow_copy_assignable_v<T> &&
std::is_nothrow_copy_assignable_v<E> &&
details::kNothrowUnionAssignable<T, E, const T&> &&
details::kNothrowUnionAssignable<E, T, const E&>)
requires(std::is_copy_assignable_v<T> && std::is_copy_assignable_v<E> &&
details::kUnionAssignable<T, E, const T&> && details::kUnionAssignable<E, T, const E&> &&
!(std::is_trivially_copy_assignable_v<T> && std::is_trivially_copy_assignable_v<E>))
{
assignFromExpected(other);
return *this;
}
// Trivial copy assignment
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
expected& operator=(const expected& other)
requires(std::is_copy_assignable_v<T> && std::is_copy_assignable_v<E> &&
details::kUnionAssignable<T, E, const T&> && details::kUnionAssignable<E, T, const E&> &&
(std::is_trivially_copy_assignable_v<T> && std::is_trivially_copy_assignable_v<E>))
= default;
// This operator is defined as deleted unless is_copy_constructible_v<T> is true and is_copy_assignable_v<T> is true
// and is_copy_assignable_v<E> is true and is_copy_constructible_v<E> is true and
// is_nothrow_move_constructible_v<T> || is_nothrow_move_constructible_v<E> is true
// https://github.com/cplusplus/draft/blob/d29ef68d3c7a1c2317f4670f08bbc8a63828ce35/source/utilities.tex#L7796
expected& operator=(const expected&) = delete;
// Non-trivial move assignment
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
constexpr expected& operator=(expected&& other) noexcept(std::is_nothrow_move_assignable_v<T> &&
std::is_nothrow_move_assignable_v<E> &&
details::kNothrowUnionAssignable<T, E, T> &&
details::kNothrowUnionAssignable<E, T, E>)
requires(std::is_move_assignable_v<T> && std::is_move_assignable_v<E> && details::kUnionAssignable<T, E, T> &&
details::kUnionAssignable<E, T, E> &&
!(std::is_trivially_move_assignable_v<T> && std::is_trivially_move_assignable_v<E>))
{
assignFromExpected(std::move(other));
return *this;
}
// Trivial move assignment
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
expected& operator=(expected&& other)
requires(std::is_move_assignable_v<T> && std::is_move_assignable_v<E> && details::kUnionAssignable<T, E, T> &&
details::kUnionAssignable<E, T, E> &&
(std::is_trivially_move_assignable_v<T> && std::is_trivially_move_assignable_v<E>))
= default;
// Assignment from value
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
template <class U = T>
constexpr expected& operator=(U&& v) noexcept(std::is_nothrow_assignable_v<T, U> &&
details::kNothrowUnionAssignable<T, E, U>)
requires(!kSameAsThis<U> && !details::kIsSpecializationOfExpected<U> && std::is_constructible_v<T, U> &&
std::is_assignable_v<T&, U> && details::kUnionAssignable<T, E, U>)
{
if (hasValue_) {
value_ = std::forward<U>(v);
} else {
details::UnionAssign(value_, error_, std::forward<U>(v));
hasValue_ = true;
}
return *this;
}
// Copy assignment from unexpected
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
template <class G>
constexpr expected& operator=(const breaded::unexpected<G>& other) noexcept(
std::is_nothrow_assignable_v<E, const G&> && details::kNothrowUnionAssignable<E, T, const G&>)
requires(std::is_assignable_v<E, const G&> && details::kUnionAssignable<E, T, const G&>)
{
if (hasValue_) {
details::UnionAssign(error_, value_, other.error());
hasValue_ = false;
} else {
error_ = other.error();
}
return *this;
}
// Move assignment from unexpected
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
template <class G>
constexpr expected& operator=(breaded::unexpected<G>&& other) noexcept(std::is_nothrow_assignable_v<E, G> &&
details::kNothrowUnionAssignable<E, T, G>)
requires(std::is_assignable_v<E, G> && details::kUnionAssignable<E, T, G>)
{
if (hasValue_) {
details::UnionAssign(error_, value_, std::move(other).error());
hasValue_ = false;
} else {
error_ = std::move(other).error();
}
return *this;
}
#pragma endregion
#pragma region Observers
// https://en.cppreference.com/w/cpp/utility/expected/operator*
constexpr const T* operator->() const noexcept {
assert(hasValue_ && "Attempted to call expected::operator-> when has_value() == false");
return std::addressof(value_);
}
// https://en.cppreference.com/w/cpp/utility/expected/operator*
constexpr T* operator->() noexcept {
assert(hasValue_ && "Attempted to call expected::operator-> when has_value() == false");
return std::addressof(value_);
}
// https://en.cppreference.com/w/cpp/utility/expected/operator*
[[nodiscard]] constexpr const T& operator*() const& noexcept {
assert(hasValue_ && "Attempted to call expected::operator* when has_value() == false");
return value_;
}
// https://en.cppreference.com/w/cpp/utility/expected/operator*
[[nodiscard]] constexpr T& operator*() & noexcept {
assert(hasValue_ && "Attempted to call expected::operator* when has_value() == false");
return value_;
}
// https://en.cppreference.com/w/cpp/utility/expected/operator*
[[nodiscard]] constexpr const T&& operator*() const&& noexcept {
assert(hasValue_ && "Attempted to call expected::operator* when has_value() == false");
return std::move(value_);
}
// https://en.cppreference.com/w/cpp/utility/expected/operator*
[[nodiscard]] constexpr T&& operator*() && noexcept {
assert(hasValue_ && "Attempted to call expected::operator* when has_value() == false");
return std::move(value_);
}
// https://en.cppreference.com/w/cpp/utility/expected/operator_bool
[[nodiscard]] constexpr explicit operator bool() const noexcept { return hasValue_; }
// https://en.cppreference.com/w/cpp/utility/expected/operator_bool
[[nodiscard]] constexpr bool has_value() const noexcept { return hasValue_; }
// https://en.cppreference.com/w/cpp/utility/expected/value
constexpr T& value() & noexcept(false) {
if (!hasValue_) {
throw breaded::bad_expected_access(std::as_const(error_));
}
return value_;
}
// https://en.cppreference.com/w/cpp/utility/expected/value
constexpr const T& value() const& noexcept(false) {
if (!hasValue_) {
throw breaded::bad_expected_access(std::as_const(error_));
}
return value_;
}
// https://en.cppreference.com/w/cpp/utility/expected/value
constexpr T&& value() && noexcept(false) {
if (!hasValue_) {
throw breaded::bad_expected_access(std::move(error_));
}
return std::move(value_);
}
// https://en.cppreference.com/w/cpp/utility/expected/value
constexpr const T&& value() const&& noexcept(false) {
if (!hasValue_) {
throw breaded::bad_expected_access(std::move(error_));
}
return std::move(value_);
}
// https://en.cppreference.com/w/cpp/utility/expected/error
[[nodiscard]] constexpr E& error() & noexcept {
// The behavior is undefined if this->has_value() is true.
assert(!hasValue_ && "Attempted to call expected::error when has_value() == true");
return error_;
}
// https://en.cppreference.com/w/cpp/utility/expected/error
[[nodiscard]] constexpr const E& error() const& noexcept {
// The behavior is undefined if this->has_value() is true.
assert(!hasValue_ && "Attempted to call expected::error when has_value() == true");
return error_;
}
// https://en.cppreference.com/w/cpp/utility/expected/error
[[nodiscard]] constexpr E&& error() && noexcept {
// The behavior is undefined if this->has_value() is true.
assert(!hasValue_ && "Attempted to call expected::error when has_value() == true");
return std::move(error_);
}
// https://en.cppreference.com/w/cpp/utility/expected/error
[[nodiscard]] constexpr const E&& error() const&& noexcept {
// The behavior is undefined if this->has_value() is true.
assert(!hasValue_ && "Attempted to call expected::error when has_value() == true");
return std::move(error_);
}
// https://en.cppreference.com/w/cpp/utility/expected/value_or
template <class U>
requires(std::is_copy_constructible_v<T> && std::is_convertible_v<U, T>)
[[nodiscard]] constexpr T value_or(U&& default_value) const& noexcept(std::is_nothrow_copy_constructible_v<T> &&
std::is_nothrow_convertible_v<U, T>) {
if (hasValue_) {
return value_;
}
return std::forward<U>(default_value);
}
// https://en.cppreference.com/w/cpp/utility/expected/value_or
template <class U>
requires(std::is_move_constructible_v<T> && std::is_convertible_v<U, T>)
[[nodiscard]] constexpr T value_or(U&& default_value) && noexcept(std::is_nothrow_move_constructible_v<T> &&
std::is_nothrow_convertible_v<U, T>) {
if (hasValue_) {
return std::move(value_);
}
return std::forward<U>(default_value);
}
// https://en.cppreference.com/w/cpp/utility/expected/error_or
template <class G = E>
requires(std::is_copy_constructible_v<E> && std::is_convertible_v<G, E>)
[[nodiscard]] constexpr E error_or(G&& default_value) const& noexcept(std::is_nothrow_copy_constructible_v<E> &&
std::is_nothrow_convertible_v<G, E>) {
if (!hasValue_) {
return error_;
}
return std::forward<G>(default_value);
}
// https://en.cppreference.com/w/cpp/utility/expected/error_or
template <class G = E>
requires(std::is_move_constructible_v<E> && std::is_convertible_v<G, E>)
[[nodiscard]] constexpr E error_or(G&& default_value) && noexcept(std::is_nothrow_move_constructible_v<E> &&
std::is_nothrow_convertible_v<G, E>) {
if (!hasValue_) {
return std::move(error_);
}
return std::forward<G>(default_value);
}
#pragma endregion
#pragma region Monadic operations
// https://en.cppreference.com/w/cpp/utility/expected/and_then
template <class F>
constexpr AndThenReturn<expected&, F>::type and_then(F&& f) &
requires(std::is_constructible_v<E, E&>)
{
return andThenImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/and_then
template <class F>
constexpr AndThenReturn<const expected&, F>::type and_then(F&& f) const&
requires(std::is_constructible_v<E, const E&>)
{
return andThenImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/and_then
template <class F>
constexpr AndThenReturn<expected&&, F>::type and_then(F&& f) &&
requires(std::is_constructible_v<E, E &&>)
{
return andThenImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/and_then
template <class F>
constexpr AndThenReturn<const expected&&, F>::type and_then(F&& f) const&&
requires(std::is_constructible_v<E, const E &&>)
{
return andThenImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform
template <class F>
constexpr TransformReturn<expected&, F>::type transform(F&& f) &
requires(std::is_constructible_v<E, E&>)
{
return transformImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform
template <class F>
constexpr TransformReturn<const expected&, F>::type transform(F&& f) const&
requires(std::is_constructible_v<E, const E&>)
{
return transformImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform
template <class F>
constexpr TransformReturn<expected&&, F>::type transform(F&& f) &&
requires(std::is_constructible_v<E, E &&>)
{
return transformImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform
template <class F>
constexpr TransformReturn<const expected&&, F>::type transform(F&& f) const&&
requires(std::is_constructible_v<E, const E &&>)
{
return transformImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/or_else
template <class F>
constexpr OrElseReturn<expected&, F>::type or_else(F&& f) &
requires(std::is_constructible_v<T, T&>)
{
return orElseImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/or_else
template <class F>
constexpr OrElseReturn<const expected&, F>::type or_else(F&& f) const&
requires(std::is_constructible_v<T, const T&>)
{
return orElseImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/or_else
template <class F>
constexpr OrElseReturn<expected&&, F>::type or_else(F&& f) &&
requires(std::is_constructible_v<T, T &&>)
{
return orElseImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/or_else
template <class F>
constexpr OrElseReturn<const expected&&, F>::type or_else(F&& f) const&&
requires(std::is_constructible_v<T, const T &&>)
{
return orElseImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform_error
template <class F>
constexpr TransformErrorReturn<expected&, F>::type transform_error(F&& f) &
requires(std::is_constructible_v<T, T&>)
{
return transformErrorImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform_error
template <class F>
constexpr TransformErrorReturn<const expected&, F>::type transform_error(F&& f) const&
requires(std::is_constructible_v<T, const T&>)
{
return transformErrorImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform_error
template <class F>
constexpr TransformErrorReturn<expected&&, F>::type transform_error(F&& f) &&
requires(std::is_constructible_v<T, T &&>)
{
return transformErrorImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform_error
template <class F>
constexpr TransformErrorReturn<const expected&&, F>::type transform_error(F&& f) const&&
requires(std::is_constructible_v<T, const T &&>)
{
return transformErrorImpl(std::move(*this), std::forward<F>(f));
}
#pragma endregion
#pragma region Modifiers
// https://en.cppreference.com/w/cpp/utility/expected/emplace
template <class... Args>
constexpr T& emplace(Args&&... args) noexcept
requires(std::is_nothrow_constructible_v<T, Args...>)
{
destroy();
std::construct_at(std::addressof(value_), std::forward<Args>(args)...);
hasValue_ = true;
return value_;
}
// https://en.cppreference.com/w/cpp/utility/expected/emplace
template <class U, class... Args>
constexpr T& emplace(std::initializer_list<U> il, Args&&... args) noexcept
requires(std::is_nothrow_constructible_v<T, std::initializer_list<U>&, Args...>)
{
destroy();
std::construct_at(std::addressof(value_), il, std::forward<Args>(args)...);
hasValue_ = true;
return value_;
}
// https://en.cppreference.com/w/cpp/utility/expected/swap
constexpr void swap(expected& other) noexcept(std::is_nothrow_swappable_v<T> && std::is_nothrow_swappable_v<E> &&
details::kNothrowUnionSwappable<T, E>)
requires(std::is_swappable_v<T> && std::is_swappable_v<E> && details::kUnionSwappable<T, E>)
{
// Prefer custom swap friend function, and use move-based std::swap as a fallback
// https://en.cppreference.com/w/cpp/language/adl
using std::swap;
if (hasValue_) {
if (other.hasValue_) {
swap(value_, other.value_);
} else {
details::UnionSwap(error_, other.value_, std::move(other.error_), std::move(value_));
hasValue_ = false;
other.hasValue_ = true;
}
} else {
if (other.hasValue_) {
other.swap(*this);
} else {
swap(error_, other.error_);
}
}
}
#pragma endregion Modifiers
#pragma endregion Member functions
#pragma region Non-member functions
// https://en.cppreference.com/w/cpp/utility/expected/operator_cmp
template <class T2, class E2>
requires(!std::is_void_v<T2>)
[[nodiscard]] friend constexpr bool operator==(const expected& lhs, const breaded::expected<T2, E2>& rhs) noexcept(
noexcept(*lhs == *rhs) && noexcept(lhs.error() == rhs.error())) {
if (lhs.has_value()) {
if (rhs.has_value()) {
return *lhs == *rhs;
} else {
return false;
}
} else {
if (rhs.has_value()) {
return false;
} else {
return lhs.error() == rhs.error();
}
}
}
// https://en.cppreference.com/w/cpp/utility/expected/operator_cmp
template <class T2>
[[nodiscard]] friend constexpr bool operator==(const expected& x, const T2& val) noexcept(noexcept(*x == val)) {
if (x.hasValue_) {
return x.value_ == val;
} else {
return false;
}
}
// https://en.cppreference.com/w/cpp/utility/expected/operator_cmp
template <class E2>
[[nodiscard]] friend constexpr bool operator==(const expected& x,
const breaded::unexpected<E2>& e) noexcept(noexcept(x.error() ==
e.error())) {
if (x.hasValue_) {
return false;
} else {
return x.error_ == e.error();
}
}
// https://en.cppreference.com/w/cpp/utility/expected/swap2
friend constexpr void swap(expected& lhs, expected& rhs) noexcept(noexcept(lhs.swap(rhs)))
requires(std::is_swappable_v<T> && std::is_swappable_v<E> && details::kUnionSwappable<T, E>)
{
lhs.swap(rhs);
}
#pragma endregion
// ⚠️ Implementation detail.
// A constructor to initialize via RVO in the monadic function implementation
// This works even for non-moveable types
template <class F, class... Args>
requires(std::is_invocable_v<F, Args...>)
constexpr explicit expected(details::in_place_from_return_t, F&& f, Args&&... args)
: value_(std::invoke(std::forward<F>(f), std::forward<Args>(args)...)), hasValue_(true) {}
// ⚠️ Implementation detail.
// A constructor to initialize via RVO in the monadic function implementation
// This works even for non-moveable types
template <class F, class... Args>
requires(std::is_invocable_v<F, Args...>)
constexpr explicit expected(details::unexpect_from_return_t, F&& f, Args&&... args)
: error_(std::invoke(std::forward<F>(f), std::forward<Args>(args)...)), hasValue_(false) {}
private:
template <class Other>
constexpr void constructFromExpected(Other&& other) {
if (other.has_value()) {
std::construct_at(std::addressof(value_), *std::forward<Other>(other));
} else {
std::construct_at(std::addressof(error_), std::forward<Other>(other).error());
}
hasValue_ = other.has_value();
}
template <class Other>
constexpr void assignFromExpected(Other&& other) {
if (other.has_value()) {
if (hasValue_) {
// If this->has_value() equals other.has_value(), assigns the value contained in other.
value_ = *std::forward<Other>(other);
} else {
details::UnionAssign(value_, error_, *std::forward<Other>(other));
}
} else {
if (hasValue_) {
details::UnionAssign(error_, value_, std::forward<Other>(other).error());
} else {
// If this->has_value() equals other.has_value(), assigns the value contained in other.
error_ = std::forward<Other>(other).error();
}
}
// If no exception was thrown, after assignment, has_value() is equal to other.has_value().
hasValue_ = other.has_value();
}
constexpr void destroy() noexcept {
if (hasValue_) {
std::destroy_at(std::addressof(value_));
} else {
std::destroy_at(std::addressof(error_));
}
}
template <class Self, class F>
constexpr static AndThenReturn<Self, F>::type andThenImpl(Self&& self, F&& f) {
if (self.hasValue_) {
return std::invoke(std::forward<F>(f), *std::forward<Self>(self));
} else {
return AndThenReturn<Self, F>::type(breaded::unexpect, std::forward<Self>(self).error());
}
}
template <class Self, class F>
constexpr static TransformReturn<Self, F>::type transformImpl(Self&& self, F&& f) {
if (self.hasValue_) {
return TransformReturn<Self, F>::type(details::in_place_from_return, std::forward<F>(f),
*std::forward<Self>(self));
} else {
return TransformReturn<Self, F>::type(breaded::unexpect, std::forward<Self>(self).error());
}
}
template <class Self, class F>
constexpr static OrElseReturn<Self, F>::type orElseImpl(Self&& self, F&& f) {
if (self.hasValue_) {
return OrElseReturn<Self, F>::type(*std::forward<Self>(self));
} else {
return std::invoke(std::forward<F>(f), std::forward<Self>(self).error());
}
}
template <class Self, class F>
constexpr static TransformErrorReturn<Self, F>::type transformErrorImpl(Self&& self, F&& f) {
if (self.hasValue_) {
return TransformErrorReturn<Self, F>::type(*std::forward<Self>(self));
} else {
return TransformErrorReturn<Self, F>::type(details::unexpect_from_return, std::forward<F>(f),
std::forward<Self>(self).error());
}
}
union {
T value_;
E error_;
};
bool hasValue_;
};
#pragma endregion
#pragma region Expected void
template <class T, class E>
requires(std::is_void_v<T>)
class expected<T, E> {
// partial specialization of expected for void types
// https://github.com/cplusplus/draft/blob/d29ef68d3c7a1c2317f4670f08bbc8a63828ce35/source/utilities.tex#L6909
static_assert(details::ValidExpectedErrorType<E>);
template <typename Self, typename F>
struct AndThenReturn {
using type = std::remove_cvref_t<std::invoke_result_t<F>>;
};
template <typename Self, typename F>
struct TransformReturn {
using URaw = std::invoke_result_t<F>;
using U = std::remove_cvref_t<URaw>;
using type = breaded::expected<U, E>;
};
template <typename Self, typename F>
struct OrElseReturn {
using type = std::remove_cvref_t<std::invoke_result_t<F, decltype(std::declval<Self>().error())>>;
};
template <typename Self, typename F>
struct TransformErrorReturn {
using GRaw = std::invoke_result_t<F, decltype(std::declval<Self>().error())>;
using G = std::remove_cvref_t<GRaw>;
using type = breaded::expected<T, G>;
};
public:
#pragma region Member types
using value_type = T;
using error_type = E;
using unexpected_type = breaded::unexpected<E>;
#pragma endregion
#pragma region Member alias templates
// Member alias templates
template <class U>
using rebind = breaded::expected<U, error_type>;
#pragma endregion
private:
template <class U>
static constexpr bool kExpectedConvertibleToErrorType =
std::is_constructible_v<unexpected_type, U&> || std::is_constructible_v<unexpected_type, U> ||
std::is_constructible_v<unexpected_type, const U&> || std::is_constructible_v<unexpected_type, const U>;
public:
// Default empty constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
constexpr expected() noexcept : hasValue_(true) {}
// Non-trivial copy constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
constexpr expected(const expected& other) noexcept(std::is_nothrow_copy_constructible_v<E>)
requires(std::is_copy_constructible_v<E> && !std::is_trivially_copy_constructible_v<E>)
{
constructFromExpected(other);
}
// Trivial copy constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
expected(const expected& other)
requires(std::is_copy_constructible_v<E> && std::is_trivially_copy_constructible_v<E>)
= default;
// This constructor is defined as deleted unless is_copy_constructible_v<E> is true
// https://github.com/cplusplus/draft/blob/d29ef68d3c7a1c2317f4670f08bbc8a63828ce35/source/utilities.tex#L3373
expected(const expected&) = delete;
// Non-trivial move constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
constexpr expected(expected&& other) noexcept(std::is_nothrow_move_constructible_v<E>)
requires(std::is_move_constructible_v<E> && !std::is_trivially_move_constructible_v<E>)
{
constructFromExpected(std::move(other));
}
// Trivial move constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
expected(expected&& other)
requires(std::is_move_constructible_v<E> && std::is_trivially_move_constructible_v<E>)
= default;
// Converting copy constructor from expected
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class U, class G>
requires(!std::is_same_v<expected, breaded::expected<U, G>> && std::is_void_v<U> &&
std::is_constructible_v<E, const G&> && !kExpectedConvertibleToErrorType<breaded::expected<U, G>>)
constexpr explicit(!std::is_convertible_v<const G&, E>)
expected(const breaded::expected<U, G>& other) noexcept(std::is_nothrow_constructible_v<E, const G&>) {
constructFromExpected(other);
}
// Converting move constructor from expected
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class U, class G>
requires(!std::is_same_v<expected, breaded::expected<U, G>> && std::is_void_v<U> &&
std::is_constructible_v<E, G> && !kExpectedConvertibleToErrorType<breaded::expected<U, G>>)
constexpr explicit(!std::is_convertible_v<G, E>)
expected(breaded::expected<U, G>&& other) noexcept(std::is_nothrow_constructible_v<E, G>) {
constructFromExpected(std::move(other));
}
// Copy constructor from unexpected
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class G>
requires(std::is_constructible_v<E, const G&>)
constexpr explicit(!std::is_convertible_v<const G&, E>)
expected(const breaded::unexpected<G>& e) noexcept(std::is_nothrow_constructible_v<E, const G&>)
: error_(e.error()), hasValue_(false) {}
// Move constructor from unexpected
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class G>
requires(std::is_constructible_v<E, G>)
constexpr explicit(!std::is_convertible_v<G, E>)
expected(breaded::unexpected<G>&& e) noexcept(std::is_nothrow_constructible_v<E, G>)
: error_(std::move(e).error()), hasValue_(false) {}
// In-place empty constructor
// https://en.cppreference.com/w/cpp/utility/expected/expected
constexpr explicit expected(std::in_place_t) noexcept : hasValue_(true) {}
// In-place error constructor from args
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class... Args>
requires(std::is_constructible_v<E, Args...>)
constexpr explicit expected(breaded::unexpect_t,
Args&&... args) noexcept(std::is_nothrow_constructible_v<E, Args...>)
: error_(std::forward<Args>(args)...), hasValue_(false) {}
// In-place error constructor from initializer list
// https://en.cppreference.com/w/cpp/utility/expected/expected
template <class U, class... Args>
requires(std::is_constructible_v<E, std::initializer_list<U>&, Args...>)
constexpr explicit expected(breaded::unexpect_t, std::initializer_list<U> il, Args&&... args) noexcept(
std::is_nothrow_constructible_v<E, std::initializer_list<U>&, Args...>)
: error_(il, std::forward<Args>(args)...), hasValue_(false) {}
// Non-trivial destructor
// https://en.cppreference.com/w/cpp/utility/expected/~expected#void_partial_specialization_destructor
constexpr ~expected() noexcept { destroy(); }
// Trivial destructor
// https://en.cppreference.com/w/cpp/utility/expected/~expected#void_partial_specialization_destructor
~expected()
requires(std::is_trivially_destructible_v<E>)
= default;
#pragma region Assignment
// Non-trivial copy assignment
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
constexpr expected& operator=(const expected& other) noexcept(std::is_nothrow_copy_assignable_v<E> &&
std::is_nothrow_copy_constructible_v<E>)
requires(std::is_copy_assignable_v<E> && std::is_copy_constructible_v<E> &&
!std::is_trivially_copy_assignable_v<E>)
{
assignFromExpected(other);
return *this;
}
// Trivial copy assignment
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
expected& operator=(const expected& other)
requires(std::is_copy_assignable_v<E> && std::is_copy_constructible_v<E> &&
std::is_trivially_copy_assignable_v<E>)
= default;
// This operator is defined as deleted unless is_copy_assignable_v<E> is true and is_copy_constructible_v<E> is true
// https://github.com/cplusplus/draft/blob/d29ef68d3c7a1c2317f4670f08bbc8a63828ce35/source/utilities.tex#L9007
expected& operator=(const expected&) = delete;
// Non-trivial move assignment
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
constexpr expected& operator=(expected&& other) noexcept(std::is_nothrow_move_assignable_v<E> &&
std::is_nothrow_move_constructible_v<E>)
requires(std::is_move_assignable_v<E> && std::is_move_constructible_v<E> &&
!std::is_trivially_move_assignable_v<E>)
{
assignFromExpected(std::move(other));
return *this;
}
// Trivial move assignment
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
expected& operator=(expected&& other)
requires(std::is_move_assignable_v<E> && std::is_move_constructible_v<E> &&
std::is_trivially_move_assignable_v<E>)
= default;
// Copy assignment from unexpected
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
template <class G>
constexpr expected& operator=(const breaded::unexpected<G>& other) noexcept(
std::is_nothrow_constructible_v<E, const G&> && std::is_nothrow_assignable_v<E, const G&>)
requires(std::is_constructible_v<E, const G&> && std::is_assignable_v<E, const G&>)
{
if (hasValue_) {
std::construct_at(std::addressof(error_), other.error());
hasValue_ = false;
} else {
error_ = other.error();
}
return *this;
}
// Move assignment from unexpected
// https://en.cppreference.com/w/cpp/utility/expected/operator%3D
template <class G>
constexpr expected& operator=(breaded::unexpected<G>&& other) noexcept(std::is_nothrow_constructible_v<E, G> &&
std::is_nothrow_assignable_v<E, G>)
requires(std::is_constructible_v<E, G> && std::is_assignable_v<E, G>)
{
if (hasValue_) {
std::construct_at(std::addressof(error_), std::move(other).error());
hasValue_ = false;
} else {
error_ = std::move(other).error();
}
return *this;
}
#pragma endregion
#pragma region Observers
// https://en.cppreference.com/w/cpp/utility/expected/operator*
constexpr void operator*() const noexcept {
assert(hasValue_ && "Attempted to call expected::operator* when has_value() == false");
}
// https://en.cppreference.com/w/cpp/utility/expected/operator_bool
[[nodiscard]] constexpr explicit operator bool() const noexcept { return hasValue_; }
// https://en.cppreference.com/w/cpp/utility/expected/operator_bool
[[nodiscard]] constexpr bool has_value() const noexcept { return hasValue_; }
// https://en.cppreference.com/w/cpp/utility/expected/value
constexpr void value() const& noexcept(false) {
if (!hasValue_) {
throw breaded::bad_expected_access(std::as_const(error_));
}
}
// https://en.cppreference.com/w/cpp/utility/expected/value
constexpr void value() && noexcept(false) {
if (!hasValue_) {
throw breaded::bad_expected_access(std::move(error_));
}
}
// https://en.cppreference.com/w/cpp/utility/expected/error
[[nodiscard]] constexpr E& error() & noexcept {
// The behavior is undefined if this->has_value() is true.
assert(!hasValue_ && "Attempted to call expected::error when has_value() == true");
return error_;
}
// https://en.cppreference.com/w/cpp/utility/expected/error
[[nodiscard]] constexpr const E& error() const& noexcept {
// The behavior is undefined if this->has_value() is true.
assert(!hasValue_ && "Attempted to call expected::error when has_value() == true");
return error_;
}
// https://en.cppreference.com/w/cpp/utility/expected/error
[[nodiscard]] constexpr E&& error() && noexcept {
// The behavior is undefined if this->has_value() is true.
assert(!hasValue_ && "Attempted to call expected::error when has_value() == true");
return std::move(error_);
}
// https://en.cppreference.com/w/cpp/utility/expected/error
[[nodiscard]] constexpr const E&& error() const&& noexcept {
// The behavior is undefined if this->has_value() is true.
assert(!hasValue_ && "Attempted to call expected::error when has_value() == true");
return std::move(error_);
}
// https://en.cppreference.com/w/cpp/utility/expected/error_or
template <class G = E>
requires(std::is_copy_constructible_v<E> && std::is_convertible_v<G, E>)
[[nodiscard]] constexpr E error_or(G&& default_value) const& noexcept(std::is_nothrow_copy_constructible_v<E> &&
std::is_nothrow_convertible_v<G, E>) {
if (!hasValue_) {
return error_;
}
return std::forward<G>(default_value);
}
// https://en.cppreference.com/w/cpp/utility/expected/error_or
template <class G = E>
requires(std::is_move_constructible_v<E> && std::is_convertible_v<G, E>)
[[nodiscard]] constexpr E error_or(G&& default_value) && noexcept(std::is_nothrow_move_constructible_v<E> &&
std::is_nothrow_convertible_v<G, E>) {
if (!hasValue_) {
return std::move(error_);
}
return std::forward<G>(default_value);
}
#pragma endregion
#pragma region Monadic operations
// https://en.cppreference.com/w/cpp/utility/expected/and_then
template <class F>
constexpr AndThenReturn<expected&, F>::type and_then(F&& f) &
requires(std::is_constructible_v<E, E&>)
{
return andThenImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/and_then
template <class F>
constexpr AndThenReturn<const expected&, F>::type and_then(F&& f) const&
requires(std::is_constructible_v<E, const E&>)
{
return andThenImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/and_then
template <class F>
constexpr AndThenReturn<expected&&, F>::type and_then(F&& f) &&
requires(std::is_constructible_v<E, E &&>)
{
return andThenImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/and_then
template <class F>
constexpr AndThenReturn<const expected&&, F>::type and_then(F&& f) const&&
requires(std::is_constructible_v<E, const E &&>)
{
return andThenImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform
template <class F>
constexpr TransformReturn<expected&, F>::type transform(F&& f) &
requires(std::is_constructible_v<E, E&>)
{
return transformImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform
template <class F>
constexpr TransformReturn<const expected&, F>::type transform(F&& f) const&
requires(std::is_constructible_v<E, const E&>)
{
return transformImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform
template <class F>
constexpr TransformReturn<expected&&, F>::type transform(F&& f) &&
requires(std::is_constructible_v<E, E &&>)
{
return transformImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform
template <class F>
constexpr TransformReturn<const expected&&, F>::type transform(F&& f) const&&
requires(std::is_constructible_v<E, const E &&>)
{
return transformImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/or_else
template <class F>
constexpr OrElseReturn<expected&, F>::type or_else(F&& f) & {
return orElseImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/or_else
template <class F>
constexpr OrElseReturn<const expected&, F>::type or_else(F&& f) const& {
return orElseImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/or_else
template <class F>
constexpr OrElseReturn<expected&&, F>::type or_else(F&& f) && {
return orElseImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/or_else
template <class F>
constexpr OrElseReturn<const expected&&, F>::type or_else(F&& f) const&& {
return orElseImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform_error
template <class F>
constexpr TransformErrorReturn<expected&, F>::type transform_error(F&& f) & {
return transformErrorImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform_error
template <class F>
constexpr TransformErrorReturn<const expected&, F>::type transform_error(F&& f) const& {
return transformErrorImpl(*this, std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform_error
template <class F>
constexpr TransformErrorReturn<expected&&, F>::type transform_error(F&& f) && {
return transformErrorImpl(std::move(*this), std::forward<F>(f));
}
// https://en.cppreference.com/w/cpp/utility/expected/transform_error
template <class F>
constexpr TransformErrorReturn<const expected&&, F>::type transform_error(F&& f) const&& {
return transformErrorImpl(std::move(*this), std::forward<F>(f));
}
#pragma endregion
#pragma region Modifiers
// https://en.cppreference.com/w/cpp/utility/expected/emplace
constexpr void emplace() noexcept {
destroy();
hasValue_ = true;
}
// https://en.cppreference.com/w/cpp/utility/expected/swap
constexpr void swap(expected& other) noexcept(std::is_nothrow_swappable_v<E> &&
std::is_nothrow_move_constructible_v<E>)
requires(std::is_swappable_v<E> && std::is_move_constructible_v<E>)
{
// Prefer custom swap friend function, and use move-based std::swap as a fallback
// https://en.cppreference.com/w/cpp/language/adl
using std::swap;
if (hasValue_) {
if (!other.hasValue_) {
std::construct_at(std::addressof(error_), std::move(other.error_));
std::destroy_at(std::addressof(other.error_));
hasValue_ = false;
other.hasValue_ = true;
}
} else {
if (other.hasValue_) {
other.swap(*this);
} else {
swap(error_, other.error_);
}
}
}
#pragma endregion
#pragma region Non-member functions
// https://en.cppreference.com/w/cpp/utility/expected/operator_cmp
template <class T2, class E2>
requires(std::is_void_v<T2>)
[[nodiscard]] friend constexpr bool operator==(const expected& lhs, const breaded::expected<T2, E2>& rhs) noexcept(
noexcept(lhs.error() == rhs.error())) {
if (lhs.hasValue_) {
return rhs.hasValue_;
} else {
if (rhs.hasValue_) {
return false;
} else {
return lhs.error_ == rhs.error_;
}
}
}
// https://en.cppreference.com/w/cpp/utility/expected/operator_cmp
template <class E2>
[[nodiscard]] friend constexpr bool operator==(const expected& x,
const breaded::unexpected<E2>& e) noexcept(noexcept(x.error() ==
e.error())) {
if (x.hasValue_) {
return false;
} else {
return x.error_ == e.error();
}
}
// https://en.cppreference.com/w/cpp/utility/expected/operator_cmp
[[nodiscard]] friend constexpr void swap(expected& lhs, expected& rhs) noexcept(noexcept(lhs.swap(rhs)))
requires(std::is_swappable_v<E> && std::is_move_constructible_v<E>)
{
lhs.swap(rhs);
}
#pragma endregion
// ⚠️ Implementation detail.
// A constructor to initialize via RVO in the monadic function implementation
// This works even for non-moveable types
template <class F, class... Args>
requires(std::is_invocable_v<F, Args...>)
constexpr explicit expected(details::in_place_from_return_t, F&& f, Args&&... args) : hasValue_(true) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
// ⚠️ Implementation detail.
// A constructor to initialize via RVO in the monadic function implementation
// This works even for non-moveable types
template <class F, class... Args>
requires(std::is_invocable_v<F, Args...>)
constexpr explicit expected(details::unexpect_from_return_t, F&& f, Args&&... args)
: error_(std::invoke(std::forward<F>(f), std::forward<Args>(args)...)), hasValue_(false) {}
private:
template <class Other>
constexpr void constructFromExpected(Other&& other) {
if (!other.has_value()) {
std::construct_at(std::addressof(error_), std::forward<Other>(other).error());
}
hasValue_ = other.has_value();
}
template <class Other>
constexpr void assignFromExpected(Other&& other) {
if (other.has_value()) {
std::destroy_at(std::addressof(error_));
} else {
if (hasValue_) {
std::construct_at(std::addressof(error_), std::forward<Other>(other).error());
} else {
// If this->has_value() equals other.has_value(), assigns the value contained in other.
error_ = std::forward<Other>(other).error();
}
}
// If no exception was thrown, after assignment, has_value() is equal to other.has_value().
hasValue_ = other.has_value();
}
constexpr void destroy() noexcept {
if (!hasValue_) {
std::destroy_at(std::addressof(error_));
}
}
template <class Self, class F>
constexpr static AndThenReturn<Self, F>::type andThenImpl(Self&& self, F&& f) {
if (self.hasValue_) {
return std::invoke(std::forward<F>(f));
} else {
return AndThenReturn<Self, F>::type(breaded::unexpect, std::forward<Self>(self).error());
}
}
template <class Self, class F>
constexpr static TransformReturn<Self, F>::type transformImpl(Self&& self, F&& f) {
if (self.hasValue_) {
return TransformReturn<Self, F>::type(details::in_place_from_return, std::forward<F>(f));
} else {
return TransformReturn<Self, F>::type(breaded::unexpect, std::forward<Self>(self).error());
}
}
template <class Self, class F>
constexpr static OrElseReturn<Self, F>::type orElseImpl(Self&& self, F&& f) {
if (self.hasValue_) {
return OrElseReturn<Self, F>::type();
} else {
return std::invoke(std::forward<F>(f), std::forward<Self>(self).error());
}
}
template <class Self, class F>
constexpr static TransformErrorReturn<Self, F>::type transformErrorImpl(Self&& self, F&& f) {
if (self.hasValue_) {
return TransformErrorReturn<Self, F>::type();
} else {
return TransformErrorReturn<Self, F>::type(details::unexpect_from_return, std::forward<F>(f),
std::forward<Self>(self).error());
}
}
union {
E error_;
};
bool hasValue_;
};
#pragma endregion
}; // namespace breaded
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment