Created
January 8, 2025 05:47
-
-
Save BreadFish64/453bc4005b2e28578738ed83851d7720 to your computer and use it in GitHub Desktop.
C++20 implementation of std::expected which passes MSVC's unit tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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