| /* | |
| * MIT License | |
| * | |
| * Copyright (c) 2024 zeus-cpp | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| * of this software and associated documentation files (the "Software"), to deal | |
| * in the Software without restriction, including without limitation the rights | |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| * copies of the Software, and to permit persons to whom the Software is | |
| * furnished to do so, subject to the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included in all | |
| * copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| * SOFTWARE. | |
| */ | |
| /// \file iceberg/compat/expected.h | |
| /// \brief A C++20 backport of C++23 `std::expected`, used as the storage type | |
| /// behind `iceberg::Result`. | |
| /// | |
| /// Purpose: the library is developed and built as C++23. Compatibility-mode | |
| /// packages additionally support C++20 consumers. `iceberg::Result<T>` (see | |
| /// iceberg/result.h) uses the same `iceberg::expected` type in both package | |
| /// modes rather than selecting a storage type per translation unit. The API mirrors | |
| /// `std::expected` (including the monadic `and_then`, `or_else`, `transform` | |
| /// and `transform_error`), so code can be written as if against the standard | |
| /// type. | |
| /// | |
| /// Upstream: https://github.com/zeus-cpp/expected (MIT), | |
| /// include/zeus/expected.hpp at tag v1.4.0 | |
| /// (f9dde3b062f1b3a5d1a9f2888e4b6f6d2303557b). | |
| /// | |
| /// Local changes (search for "iceberg:" for the non-mechanical ones): | |
| /// * Namespace `zeus` and its versioned inline ABI namespace replaced by plain | |
| /// `iceberg`, so symbol names stay stable across upstream updates; macros | |
| /// renamed from `ZEUS_EXPECTED_*` to `ICEBERG_EXPECTED_*`, and the upstream | |
| /// version macros removed. | |
| /// * Public classes annotated with `ICEBERG_EXPORT`, except the primary | |
| /// `expected` template, which uses `ICEBERG_TEMPLATE_CLASS_EXPORT` (empty on | |
| /// MSVC, where exporting a class template breaks the Windows build). | |
| /// * `[[nodiscard]]` added to `unexpected` and `expected`. | |
| /// * The default constructor no longer requires `T` to be default | |
| /// constructible, so `expected<T, E>` can hold types with no (or a private) | |
| /// default constructor, which MSVC otherwise tried to instantiate. | |
| /// * The default package retains construction from `std::unexpected<G>` when | |
| /// available. Compatibility packages omit those constructors in all language | |
| /// modes so the class definition remains the same for library and consumer. | |
| /// * Reformatted with clang-format. | |
| /// | |
| /// History: | |
| /// - apache/iceberg-cpp#40 first vendored zeus-cpp/expected (v1.2.0) while the | |
| /// project targeted C++20. | |
| /// - apache/iceberg-cpp#139 raised the project to C++23 and removed the header | |
| /// in favor of `std::expected`, which made C++23 a requirement for users of | |
| /// the public headers too. | |
| /// - apache/iceberg-cpp#936 vendored it again, updated to v1.4.0, so the public | |
| /// headers work in C++20 again. | |
| /// | |
| /// Once C++23 can be required of all consumers, this header can be dropped and | |
| /// `iceberg::Result` aliased back to `std::expected`. | |
| #pragma once | |
| #include <exception> | |
| #include <functional> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <version> | |
| #include "iceberg/compat/compiler.h" | |
| #include "iceberg/iceberg_export.h" | |
| #if !ICEBERG_CXX20_COMPAT && defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L | |
| # include <expected> | |
| #endif | |
| // NOLINTBEGIN | |
| #if defined(_MSVC_LANG) | |
| # define ICEBERG_EXPECTED_CPLUSPLUS _MSVC_LANG | |
| #else | |
| # define ICEBERG_EXPECTED_CPLUSPLUS __cplusplus | |
| #endif | |
| #if ICEBERG_EXPECTED_CPLUSPLUS < 201'703L | |
| static_assert(false, "This expected variant requires C++17"); | |
| #endif | |
| #if ICEBERG_EXPECTED_CPLUSPLUS >= 202'002L | |
| # define ICEBERG_EXPECTED_CONSTEXPR_DTOR constexpr | |
| #else | |
| # define ICEBERG_EXPECTED_CONSTEXPR_DTOR | |
| #endif | |
| // Detect exception support | |
| #if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) | |
| # define ICEBERG_EXPECTED_THROW(e) (throw(e)) | |
| #else | |
| # define ICEBERG_EXPECTED_THROW(e) ((void)(e), std::terminate()) | |
| #endif | |
| namespace iceberg { | |
| namespace expected_detail { | |
| template <class T, class... Args> | |
| constexpr T* construct_at(T* p, Args&&... args) noexcept( | |
| noexcept(::new (static_cast<void*>(p)) T(std::forward<Args>(args)...))) { | |
| #if ICEBERG_EXPECTED_CPLUSPLUS >= 202'002L | |
| return std::construct_at(p, std::forward<Args>(args)...); | |
| #else | |
| return ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...); | |
| #endif | |
| } | |
| template <class T> | |
| struct remove_cvref { | |
| using type = std::remove_cv_t<std::remove_reference_t<T>>; | |
| }; | |
| template <class T> | |
| using remove_cvref_t = typename remove_cvref<T>::type; | |
| template <class T, template <class...> class Template> | |
| inline constexpr bool is_specialization_v = | |
| false; // true if and only if T is a specialization of Template | |
| template <template <class...> class Template, class... Types> | |
| inline constexpr bool is_specialization_v<Template<Types...>, Template> = true; | |
| template <class T, class U> | |
| using is_void_or = std::conditional_t<std::is_void_v<T>, std::true_type, U>; | |
| template <class T, class U> | |
| inline constexpr bool is_void_or_v = is_void_or<T, U>::value; | |
| template <class T> | |
| inline constexpr bool is_trivially_destructible_or_void_v = | |
| is_void_or_v<T, std::is_trivially_destructible<T>>; | |
| template <class T> | |
| inline constexpr bool is_trivially_copy_constructible_or_void_v = | |
| is_void_or_v<T, std::is_trivially_copy_constructible<T>>; | |
| template <class T> | |
| inline constexpr bool is_trivially_move_constructible_or_void_v = | |
| is_void_or_v<T, std::is_trivially_move_constructible<T>>; | |
| template <class T> | |
| inline constexpr bool is_trivially_copy_assignable_or_void_v = | |
| is_void_or_v<T, std::is_trivially_copy_assignable<T>>; | |
| template <class T> | |
| inline constexpr bool is_trivially_move_assignable_or_void_v = | |
| is_void_or_v<T, std::is_trivially_move_assignable<T>>; | |
| template <class T> | |
| inline constexpr bool is_nothrow_copy_constructible_or_void_v = | |
| is_void_or_v<T, std::is_nothrow_copy_constructible<T>>; | |
| template <class T> | |
| inline constexpr bool is_nothrow_move_constructible_or_void_v = | |
| is_void_or_v<T, std::is_nothrow_move_constructible<T>>; | |
| template <class T> | |
| inline constexpr bool is_default_constructible_or_void_v = | |
| is_void_or_v<T, std::is_default_constructible<T>>; | |
| template <class T> | |
| inline constexpr bool is_copy_constructible_or_void_v = | |
| is_void_or_v<T, std::is_copy_constructible<T>>; | |
| template <class T> | |
| inline constexpr bool is_move_constructible_or_void_v = | |
| is_void_or_v<T, std::is_move_constructible<T>>; | |
| template <class T> | |
| inline constexpr bool is_copy_assignable_or_void_v = | |
| is_void_or_v<T, std::is_copy_assignable<T>>; | |
| template <class T> | |
| inline constexpr bool is_move_assignable_or_void_v = | |
| is_void_or_v<T, std::is_move_assignable<T>>; | |
| template <class From, class To> | |
| inline constexpr bool is_nothrow_convertible_v = | |
| noexcept(static_cast<To>(std::declval<From>())); | |
| template <class Lhs, class Rhs, class = void> | |
| struct is_equality_result_convertible_to_bool : std::false_type {}; | |
| template <class Lhs, class Rhs> | |
| struct is_equality_result_convertible_to_bool< | |
| Lhs, Rhs, | |
| std::void_t<decltype(std::declval<const Lhs&>() == std::declval<const Rhs&>())>> | |
| : std::is_convertible< | |
| decltype(std::declval<const Lhs&>() == std::declval<const Rhs&>()), bool> {}; | |
| template <class Lhs, class Rhs> | |
| inline constexpr bool is_equality_result_convertible_to_bool_v = | |
| is_equality_result_convertible_to_bool<Lhs, Rhs>::value; | |
| constexpr bool implicitly_convert_to_bool(bool value) noexcept { return value; } | |
| template <class Lhs, class Rhs, class = void> | |
| struct is_nothrow_equality_result_convertible_to_bool : std::false_type {}; | |
| template <class Lhs, class Rhs> | |
| struct is_nothrow_equality_result_convertible_to_bool< | |
| Lhs, Rhs, | |
| std::void_t<decltype(implicitly_convert_to_bool(std::declval<const Lhs&>() == | |
| std::declval<const Rhs&>()))>> | |
| : std::bool_constant<noexcept(implicitly_convert_to_bool( | |
| std::declval<const Lhs&>() == std::declval<const Rhs&>()))> {}; | |
| template <class Lhs, class Rhs> | |
| inline constexpr bool is_nothrow_equality_result_convertible_to_bool_v = | |
| is_nothrow_equality_result_convertible_to_bool<Lhs, Rhs>::value; | |
| } // namespace expected_detail | |
| template <class E> | |
| class unexpected; | |
| namespace expected_detail { | |
| template <class E> | |
| struct is_error_type_valid : std::true_type { | |
| static_assert(std::is_object_v<E>, "E must be an object type"); | |
| static_assert(!std::is_array_v<E>, "E must not be an array"); | |
| static_assert(!std::is_const_v<E>, "E must not be const"); | |
| static_assert(!std::is_volatile_v<E>, "E must not be volatile"); | |
| static_assert(!expected_detail::is_specialization_v<std::remove_cv_t<E>, unexpected>, | |
| "E must not be unexpected"); | |
| }; | |
| template <class T> | |
| inline constexpr bool is_error_type_valid_v = is_error_type_valid<T>::value; | |
| } // namespace expected_detail | |
| template <class E> | |
| class ICEBERG_EXPORT [[nodiscard]] unexpected { | |
| public: | |
| static_assert(expected_detail::is_error_type_valid_v<E>); | |
| unexpected() = delete; | |
| ~unexpected() = default; | |
| unexpected(const unexpected&) = default; | |
| unexpected(unexpected&&) = default; | |
| template <class... Args, | |
| typename std::enable_if_t<std::is_constructible_v<E, Args...>>* = nullptr> | |
| constexpr explicit unexpected(std::in_place_t, Args&&... args) noexcept( | |
| std::is_nothrow_constructible_v<E, Args...>) | |
| : m_val(std::forward<Args>(args)...) {} | |
| template <class U, class... Args, | |
| typename std::enable_if_t<std::is_constructible_v< | |
| E, std::initializer_list<U>&, Args...>>* = nullptr> | |
| constexpr explicit unexpected( | |
| std::in_place_t, std::initializer_list<U> l, | |
| Args&&... args) noexcept(std::is_nothrow_constructible_v<E, | |
| std::initializer_list<U>&, | |
| Args...>) | |
| : m_val(l, std::forward<Args>(args)...) {} | |
| template <class Err = E, | |
| typename std::enable_if_t<std::is_constructible_v<E, Err>>* = nullptr> | |
| constexpr explicit unexpected(Err&& e) noexcept(std::is_nothrow_constructible_v<E, Err>) | |
| : m_val(std::forward<Err>(e)) {} | |
| unexpected& operator=(const unexpected&) = default; | |
| unexpected& operator=(unexpected&&) = default; | |
| constexpr const E& error() const& noexcept { return m_val; } | |
| constexpr E& error() & noexcept { return m_val; } | |
| constexpr E&& error() && noexcept { return std::move(m_val); } | |
| constexpr const E&& error() const&& noexcept { return std::move(m_val); } | |
| constexpr void swap(unexpected& other) noexcept(std::is_nothrow_swappable_v<E>) { | |
| static_assert(std::is_swappable_v<E>, "E must be swappable"); | |
| using std::swap; | |
| swap(m_val, other.m_val); | |
| } | |
| friend constexpr void swap(unexpected& x, unexpected& y) noexcept(noexcept(x.swap(y))) { | |
| x.swap(y); | |
| } | |
| template <class E2> | |
| friend constexpr bool operator==( | |
| const unexpected& lhs, | |
| const unexpected<E2>& rhs) noexcept(noexcept(lhs.error() == rhs.error())) { | |
| return lhs.error() == rhs.error(); | |
| } | |
| template <class E2> | |
| friend constexpr bool operator!=( | |
| const unexpected& lhs, | |
| const unexpected<E2>& rhs) noexcept(noexcept(lhs.error() != rhs.error())) { | |
| return lhs.error() != rhs.error(); | |
| } | |
| private: | |
| E m_val; | |
| }; | |
| // deduction guide | |
| template <class E> | |
| unexpected(E) -> unexpected<E>; | |
| struct ICEBERG_EXPORT unexpect_t { | |
| explicit unexpect_t() = default; | |
| }; | |
| inline constexpr unexpect_t unexpect{}; | |
| template <class E> | |
| class bad_expected_access; | |
| template <> | |
| class ICEBERG_EXPORT bad_expected_access<void> : public std::exception { | |
| public: | |
| [[nodiscard]] const char* what() const noexcept override { | |
| return "Bad expected access"; | |
| } | |
| protected: | |
| // LWG-4031 | |
| bad_expected_access() noexcept = default; | |
| bad_expected_access(const bad_expected_access&) noexcept = default; | |
| bad_expected_access(bad_expected_access&&) noexcept = default; | |
| bad_expected_access& operator=(const bad_expected_access&) noexcept = default; | |
| bad_expected_access& operator=(bad_expected_access&&) noexcept = default; | |
| ~bad_expected_access() override = default; | |
| }; | |
| template <class E> | |
| class ICEBERG_EXPORT bad_expected_access : public bad_expected_access<void> { | |
| public: | |
| explicit bad_expected_access(E e) noexcept(std::is_nothrow_move_constructible_v<E>) | |
| : m_val(std::move(e)) {} | |
| const E& error() const& noexcept { return m_val; } | |
| E& error() & noexcept { return m_val; } | |
| const E&& error() const&& noexcept { return std::move(m_val); } | |
| E&& error() && noexcept { return std::move(m_val); } | |
| private: | |
| E m_val; | |
| }; | |
| template <class T, class E> | |
| class expected; | |
| namespace expected_detail { | |
| template <class T> | |
| struct is_value_type_valid : std::true_type { | |
| static_assert(!std::is_reference_v<T>, "T must not be a reference"); | |
| static_assert(!std::is_function_v<T>, "T must not be a function"); | |
| static_assert(!std::is_array_v<T>, | |
| "T must not be an array to meet the requirements of Cpp17Destructible " | |
| "when T is not cv-void"); | |
| static_assert(!std::is_same_v<T, std::remove_cv_t<std::in_place_t>>, | |
| "T must not be in_place_t"); | |
| static_assert(!std::is_same_v<T, std::remove_cv_t<unexpect_t>>, | |
| "T must not be unexpect_t"); | |
| static_assert(!expected_detail::is_specialization_v<std::remove_cv_t<T>, unexpected>, | |
| "T must not be unexpected"); | |
| }; | |
| template <class T> | |
| inline constexpr bool is_value_type_valid_v = is_value_type_valid<T>::value; | |
| template <class T, class E, class U> | |
| using enable_forward_t = std::enable_if_t< | |
| std::is_constructible_v<T, U> && | |
| !std::is_same_v<expected_detail::remove_cvref_t<U>, std::in_place_t> && | |
| !std::is_same_v<expected<T, E>, expected_detail::remove_cvref_t<U>> && | |
| !expected_detail::is_specialization_v<expected_detail::remove_cvref_t<U>, | |
| unexpected> && | |
| (!std::is_same_v<std::remove_cv_t<T>, bool> || // LWG-3836 | |
| !expected_detail::is_specialization_v<expected_detail::remove_cvref_t<U>, | |
| expected>) && | |
| !std::is_same_v<expected_detail::remove_cvref_t<U>, unexpect_t> // LWG-4222 | |
| >; | |
| template <class T, class E, class U, class G, class UF, class GF> | |
| using enable_from_other_expected_t = std::enable_if_t< | |
| std::is_constructible_v<T, UF> && std::is_constructible_v<E, GF> && | |
| std::disjunction_v<std::is_same<std::remove_cv_t<T>, bool>, // LWG-3836 | |
| std::negation<std::disjunction< | |
| std::is_constructible<T, expected<U, G>&>, | |
| std::is_constructible<T, expected<U, G>>, | |
| std::is_constructible<T, const expected<U, G>&>, | |
| std::is_constructible<T, const expected<U, G>>, | |
| std::is_convertible<expected<U, G>&, T>, | |
| std::is_convertible<expected<U, G>&&, T>, | |
| std::is_convertible<const expected<U, G>&, T>, | |
| std::is_convertible<const expected<U, G>&&, T>, | |
| std::is_constructible<unexpected<E>, expected<U, G>&>, | |
| std::is_constructible<unexpected<E>, expected<U, G>>, | |
| std::is_constructible<unexpected<E>, const expected<U, G>&>, | |
| std::is_constructible<unexpected<E>, const expected<U, G>>>>>>; | |
| template <class E, class U, class G, class GF> | |
| using enable_from_other_void_expected_t = | |
| std::enable_if_t<std::is_void_v<U> && std::is_constructible_v<E, GF> && | |
| std::negation_v<std::disjunction< | |
| std::is_constructible<unexpected<E>, expected<U, G>&>, | |
| std::is_constructible<unexpected<E>, expected<U, G>>, | |
| std::is_constructible<unexpected<E>, const expected<U, G>&>, | |
| std::is_constructible<unexpected<E>, const expected<U, G>>>>>; | |
| } // namespace expected_detail | |
| namespace expected_detail { | |
| // Constructing the active alternative inside storage_base ensures its | |
| // destructor only ever observes a fully initialized value or error. | |
| struct construct_from_expected_t { | |
| explicit construct_from_expected_t() = default; | |
| }; | |
| inline constexpr construct_from_expected_t construct_from_expected{}; | |
| template <class Rhs> | |
| constexpr bool expected_source_has_value(const Rhs& rhs) noexcept { | |
| if constexpr (expected_detail::is_specialization_v<expected_detail::remove_cvref_t<Rhs>, | |
| expected>) { | |
| return rhs.has_value(); | |
| } else { | |
| return rhs.m_has_val; | |
| } | |
| } | |
| template <class Rhs> | |
| constexpr decltype(auto) expected_source_value(Rhs&& rhs) noexcept { | |
| if constexpr (expected_detail::is_specialization_v<expected_detail::remove_cvref_t<Rhs>, | |
| expected>) { | |
| return *std::forward<Rhs>(rhs); | |
| } else { | |
| if constexpr (std::is_lvalue_reference_v<Rhs>) { | |
| return (rhs.m_val); | |
| } else { | |
| return std::move(rhs.m_val); | |
| } | |
| } | |
| } | |
| template <class Rhs> | |
| constexpr decltype(auto) expected_source_error(Rhs&& rhs) noexcept { | |
| if constexpr (expected_detail::is_specialization_v<expected_detail::remove_cvref_t<Rhs>, | |
| expected>) { | |
| return std::forward<Rhs>(rhs).error(); | |
| } else { | |
| if constexpr (std::is_lvalue_reference_v<Rhs>) { | |
| return (rhs.m_unexpect); | |
| } else { | |
| return std::move(rhs.m_unexpect); | |
| } | |
| } | |
| } | |
| struct construct_with_invoke_result_t { | |
| explicit construct_with_invoke_result_t() = default; | |
| }; | |
| inline constexpr construct_with_invoke_result_t construct_with_invoke_result{}; | |
| template <class T> | |
| struct [[nodiscard]] ReinitGuard { | |
| static_assert(std::is_nothrow_move_constructible_v<T>); | |
| ReinitGuard(ReinitGuard const&) = delete; | |
| ReinitGuard& operator=(ReinitGuard const&) = delete; | |
| constexpr ReinitGuard(T* target, T* tmp) noexcept : _target(target), _tmp(tmp) {} | |
| ICEBERG_EXPECTED_CONSTEXPR_DTOR ~ReinitGuard() noexcept { | |
| if (_target) { | |
| expected_detail::construct_at(_target, std::move(*_tmp)); | |
| } | |
| } | |
| T* _target; | |
| T* _tmp; | |
| }; | |
| template <class First, class Second, class... Args> | |
| constexpr void reinit_expected(First& new_val, Second& old_val, Args&&... args) noexcept( | |
| std::is_nothrow_constructible_v<First, Args...>) { | |
| if constexpr (std::is_nothrow_constructible_v<First, Args...>) { | |
| if constexpr (!std::is_trivially_destructible_v<Second>) { | |
| old_val.~Second(); | |
| } | |
| expected_detail::construct_at(std::addressof(new_val), std::forward<Args>(args)...); | |
| } else if constexpr (std::is_nothrow_move_constructible_v<First>) { | |
| First tmp(std::forward<Args>(args)...); | |
| if constexpr (!std::is_trivially_destructible_v<Second>) { | |
| old_val.~Second(); | |
| } | |
| expected_detail::construct_at(std::addressof(new_val), std::move(tmp)); | |
| } else { | |
| Second tmp(std::move(old_val)); | |
| if constexpr (!std::is_trivially_destructible_v<Second>) { | |
| old_val.~Second(); | |
| } | |
| expected_detail::ReinitGuard<Second> guard{std::addressof(old_val), | |
| std::addressof(tmp)}; | |
| expected_detail::construct_at(std::addressof(new_val), std::forward<Args>(args)...); | |
| guard._target = nullptr; | |
| } | |
| } | |
| // Implements the storage of the values, and ensures that the destructor is | |
| // trivial if it can be. | |
| // | |
| // This specialization is for when `T` and `E` are trivially destructible | |
| template <class T, class E, | |
| bool = is_trivially_destructible_or_void_v<T> && | |
| std::is_trivially_destructible_v<E>> | |
| struct storage_base { | |
| storage_base(storage_base const&) = default; | |
| storage_base(storage_base&&) = default; | |
| storage_base& operator=(storage_base const&) = default; | |
| storage_base& operator=(storage_base&&) = default; | |
| // iceberg: constrained so MSVC never instantiates T{} for a T without an | |
| // accessible default constructor. | |
| template <class U = T, std::enable_if_t<std::is_default_constructible_v<U>, int> = 0> | |
| constexpr storage_base() noexcept(std::is_nothrow_default_constructible_v<U>) | |
| : m_val(), m_has_val(true) {} | |
| template <class Rhs> | |
| constexpr storage_base(construct_from_expected_t, Rhs&& rhs) noexcept( | |
| std::is_nothrow_constructible_v< | |
| T, decltype(expected_detail::expected_source_value(std::declval<Rhs>()))> && | |
| std::is_nothrow_constructible_v< | |
| E, decltype(expected_detail::expected_source_error(std::declval<Rhs>()))>) | |
| : m_dummy(), m_has_val(expected_detail::expected_source_has_value(rhs)) { | |
| if (m_has_val) { | |
| expected_detail::construct_at( | |
| std::addressof(m_val), | |
| expected_detail::expected_source_value(std::forward<Rhs>(rhs))); | |
| } else { | |
| expected_detail::construct_at( | |
| std::addressof(m_unexpect), | |
| expected_detail::expected_source_error(std::forward<Rhs>(rhs))); | |
| } | |
| } | |
| template <class... Args, | |
| std::enable_if_t<std::is_constructible_v<T, Args&&...>>* = nullptr> | |
| constexpr explicit storage_base(std::in_place_t, Args&&... args) noexcept( | |
| noexcept(T(std::forward<Args>(args)...))) | |
| : m_val(std::forward<Args>(args)...), m_has_val(true) {} | |
| template <class U, class... Args, | |
| std::enable_if_t<std::is_constructible_v<T, std::initializer_list<U>&, | |
| Args&&...>>* = nullptr> | |
| constexpr explicit storage_base( | |
| std::in_place_t, std::initializer_list<U> il, | |
| Args&&... args) noexcept(noexcept(T(il, std::forward<Args>(args)...))) | |
| : m_val(il, std::forward<Args>(args)...), m_has_val(true) {} | |
| template <class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, Args&&...>>* = nullptr> | |
| constexpr explicit storage_base(unexpect_t, Args&&... args) noexcept( | |
| noexcept(E(std::forward<Args>(args)...))) | |
| : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {} | |
| template <class U, class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, std::initializer_list<U>&, | |
| Args&&...>>* = nullptr> | |
| constexpr explicit storage_base( | |
| unexpect_t, std::initializer_list<U> il, | |
| Args&&... args) noexcept(noexcept(E(il, std::forward<Args>(args)...))) | |
| : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {} | |
| // helper ctor for transform() | |
| template <class Fn, class... Args> | |
| constexpr explicit storage_base( | |
| construct_with_invoke_result_t, Fn&& func, | |
| Args&&... args) noexcept(noexcept(static_cast<T>(std::invoke(std::forward<Fn>(func), | |
| std::forward<Args>( | |
| args)...)))) | |
| : m_val(std::invoke(std::forward<Fn>(func), std::forward<Args>(args)...)), | |
| m_has_val(true) {} | |
| // helper ctor for transform_error() | |
| template <class Fn, class... Args> | |
| constexpr explicit storage_base( | |
| construct_with_invoke_result_t, unexpect_t, Fn&& func, | |
| Args&&... args) noexcept(noexcept(static_cast<E>(std::invoke(std::forward<Fn>(func), | |
| std::forward<Args>( | |
| args)...)))) | |
| : m_unexpect(std::invoke(std::forward<Fn>(func), std::forward<Args>(args)...)), | |
| m_has_val(false) {} | |
| ~storage_base() = default; | |
| union { | |
| T m_val; | |
| E m_unexpect; | |
| char m_dummy; | |
| }; | |
| bool m_has_val; | |
| }; | |
| template <class T, class E> | |
| struct storage_base<T, E, false> { | |
| storage_base(storage_base const&) = default; | |
| storage_base(storage_base&&) = default; | |
| storage_base& operator=(storage_base const&) = default; | |
| storage_base& operator=(storage_base&&) = default; | |
| // iceberg: constrained so MSVC never instantiates T{} for a T without an | |
| // accessible default constructor. | |
| template <class U = T, std::enable_if_t<std::is_default_constructible_v<U>, int> = 0> | |
| constexpr storage_base() noexcept(std::is_nothrow_default_constructible_v<U>) | |
| : m_val(), m_has_val(true) {} | |
| template <class Rhs> | |
| constexpr storage_base(construct_from_expected_t, Rhs&& rhs) noexcept( | |
| std::is_nothrow_constructible_v< | |
| T, decltype(expected_detail::expected_source_value(std::declval<Rhs>()))> && | |
| std::is_nothrow_constructible_v< | |
| E, decltype(expected_detail::expected_source_error(std::declval<Rhs>()))>) | |
| : m_dummy(), m_has_val(expected_detail::expected_source_has_value(rhs)) { | |
| if (m_has_val) { | |
| expected_detail::construct_at( | |
| std::addressof(m_val), | |
| expected_detail::expected_source_value(std::forward<Rhs>(rhs))); | |
| } else { | |
| expected_detail::construct_at( | |
| std::addressof(m_unexpect), | |
| expected_detail::expected_source_error(std::forward<Rhs>(rhs))); | |
| } | |
| } | |
| template <class... Args, | |
| std::enable_if_t<std::is_constructible_v<T, Args&&...>>* = nullptr> | |
| constexpr explicit storage_base(std::in_place_t, Args&&... args) noexcept( | |
| noexcept(T(std::forward<Args>(args)...))) | |
| : m_val(std::forward<Args>(args)...), m_has_val(true) {} | |
| template <class U, class... Args, | |
| std::enable_if_t<std::is_constructible_v<T, std::initializer_list<U>&, | |
| Args&&...>>* = nullptr> | |
| constexpr explicit storage_base( | |
| std::in_place_t, std::initializer_list<U> il, | |
| Args&&... args) noexcept(noexcept(T(il, std::forward<Args>(args)...))) | |
| : m_val(il, std::forward<Args>(args)...), m_has_val(true) {} | |
| template <class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, Args&&...>>* = nullptr> | |
| constexpr explicit storage_base(unexpect_t, Args&&... args) noexcept( | |
| noexcept(E(std::forward<Args>(args)...))) | |
| : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {} | |
| template <class U, class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, std::initializer_list<U>&, | |
| Args&&...>>* = nullptr> | |
| constexpr explicit storage_base( | |
| unexpect_t, std::initializer_list<U> il, | |
| Args&&... args) noexcept(noexcept(E(il, std::forward<Args>(args)...))) | |
| : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {} | |
| // helper ctor for transform() | |
| template <class Fn, class... Args> | |
| constexpr explicit storage_base( | |
| construct_with_invoke_result_t, Fn&& func, | |
| Args&&... args) noexcept(noexcept(static_cast<T>(std::invoke(std::forward<Fn>(func), | |
| std::forward<Args>( | |
| args)...)))) | |
| : m_val(std::invoke(std::forward<Fn>(func), std::forward<Args>(args)...)), | |
| m_has_val(true) {} | |
| // helper ctor for transform_error() | |
| template <class Fn, class... Args> | |
| constexpr explicit storage_base( | |
| construct_with_invoke_result_t, unexpect_t, Fn&& func, | |
| Args&&... args) noexcept(noexcept(static_cast<E>(std::invoke(std::forward<Fn>(func), | |
| std::forward<Args>( | |
| args)...)))) | |
| : m_unexpect(std::invoke(std::forward<Fn>(func), std::forward<Args>(args)...)), | |
| m_has_val(false) {} | |
| // Keep the exception specification implicit; an explicit noexcept trips | |
| // Clang's LLVM-59854 during constexpr destruction. | |
| ICEBERG_EXPECTED_CONSTEXPR_DTOR ~storage_base() { | |
| if (m_has_val) { | |
| if constexpr (!std::is_trivially_destructible_v<T>) { | |
| m_val.~T(); | |
| } | |
| } else { | |
| if constexpr (!std::is_trivially_destructible_v<E>) { | |
| m_unexpect.~E(); | |
| } | |
| } | |
| } | |
| union { | |
| T m_val; | |
| E m_unexpect; | |
| char m_dummy; | |
| }; | |
| bool m_has_val; | |
| }; | |
| // `T` is `void`, `E` is trivially-destructible | |
| template <class E> | |
| struct storage_base<void, E, true> { | |
| storage_base(storage_base const&) = default; | |
| storage_base(storage_base&&) = default; | |
| storage_base& operator=(storage_base const&) = default; | |
| storage_base& operator=(storage_base&&) = default; | |
| constexpr storage_base() noexcept : m_has_val(true) {} | |
| template <class Rhs> | |
| constexpr storage_base(construct_from_expected_t, Rhs&& rhs) noexcept( | |
| std::is_nothrow_constructible_v< | |
| E, decltype(expected_detail::expected_source_error(std::declval<Rhs>()))>) | |
| : m_val(), m_has_val(expected_detail::expected_source_has_value(rhs)) { | |
| if (!m_has_val) { | |
| expected_detail::construct_at( | |
| std::addressof(m_unexpect), | |
| expected_detail::expected_source_error(std::forward<Rhs>(rhs))); | |
| } | |
| } | |
| constexpr explicit storage_base(std::in_place_t) noexcept : m_has_val(true) {} | |
| template <class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, Args&&...>>* = nullptr> | |
| constexpr explicit storage_base(unexpect_t, Args&&... args) | |
| : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {} | |
| template <class U, class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, std::initializer_list<U>&, | |
| Args&&...>>* = nullptr> | |
| constexpr explicit storage_base(unexpect_t, std::initializer_list<U> il, Args&&... args) | |
| : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {} | |
| // helper ctor for transform_error() | |
| template <class Fn, class... Args> | |
| constexpr explicit storage_base( | |
| construct_with_invoke_result_t, unexpect_t, Fn&& func, | |
| Args&&... args) noexcept(noexcept(static_cast<E>(std::invoke(std::forward<Fn>(func), | |
| std::forward<Args>( | |
| args)...)))) | |
| : m_unexpect(std::invoke(std::forward<Fn>(func), std::forward<Args>(args)...)), | |
| m_has_val(false) {} | |
| ~storage_base() = default; | |
| struct dummy {}; | |
| union { | |
| E m_unexpect; | |
| dummy m_val; | |
| }; | |
| bool m_has_val; | |
| }; | |
| // `T` is `void`, `E` is not trivially-destructible | |
| template <class E> | |
| struct storage_base<void, E, false> { | |
| storage_base(storage_base const&) = default; | |
| storage_base(storage_base&&) = default; | |
| storage_base& operator=(storage_base const&) = default; | |
| storage_base& operator=(storage_base&&) = default; | |
| constexpr storage_base() noexcept : m_has_val(true) {} | |
| template <class Rhs> | |
| constexpr storage_base(construct_from_expected_t, Rhs&& rhs) noexcept( | |
| std::is_nothrow_constructible_v< | |
| E, decltype(expected_detail::expected_source_error(std::declval<Rhs>()))>) | |
| : m_val(), m_has_val(expected_detail::expected_source_has_value(rhs)) { | |
| if (!m_has_val) { | |
| expected_detail::construct_at( | |
| std::addressof(m_unexpect), | |
| expected_detail::expected_source_error(std::forward<Rhs>(rhs))); | |
| } | |
| } | |
| constexpr explicit storage_base(std::in_place_t) noexcept : m_has_val(true) {} | |
| template <class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, Args&&...>>* = nullptr> | |
| constexpr explicit storage_base(unexpect_t, Args&&... args) | |
| : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {} | |
| template <class U, class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, std::initializer_list<U>&, | |
| Args&&...>>* = nullptr> | |
| constexpr explicit storage_base(unexpect_t, std::initializer_list<U> il, Args&&... args) | |
| : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {} | |
| // helper ctor for transform_error() | |
| template <class Fn, class... Args> | |
| constexpr explicit storage_base( | |
| construct_with_invoke_result_t, unexpect_t, Fn&& func, | |
| Args&&... args) noexcept(noexcept(static_cast<E>(std::invoke(std::forward<Fn>(func), | |
| std::forward<Args>( | |
| args)...)))) | |
| : m_unexpect(std::invoke(std::forward<Fn>(func), std::forward<Args>(args)...)), | |
| m_has_val(false) {} | |
| // Keep the exception specification implicit; an explicit noexcept trips | |
| // Clang's LLVM-59854 during constexpr destruction. | |
| ICEBERG_EXPECTED_CONSTEXPR_DTOR ~storage_base() { | |
| if (!m_has_val) { | |
| m_unexpect.~E(); | |
| } | |
| } | |
| struct dummy {}; | |
| union { | |
| E m_unexpect; | |
| dummy m_val; | |
| }; | |
| bool m_has_val; | |
| }; | |
| // This base class provides some handy member functions which can be used in | |
| // further derived classes | |
| template <class T, class E> | |
| struct operations_base : storage_base<T, E> { | |
| using storage_base<T, E>::storage_base; | |
| constexpr T& get() & noexcept { return this->m_val; } | |
| constexpr const T& get() const& noexcept { return this->m_val; } | |
| constexpr T&& get() && noexcept(std::is_nothrow_move_constructible_v<T>) { | |
| return std::move(this->m_val); | |
| } | |
| constexpr const T&& get() const&& noexcept(std::is_nothrow_move_constructible_v<T>) { | |
| return std::move(this->m_val); | |
| } | |
| constexpr E& geterr() & noexcept { return this->m_unexpect; } | |
| constexpr const E& geterr() const& noexcept { return this->m_unexpect; } | |
| constexpr E&& geterr() && noexcept(std::is_nothrow_move_constructible_v<E>) { | |
| return std::move(this->m_unexpect); | |
| } | |
| constexpr const E&& geterr() const&& noexcept(std::is_nothrow_move_constructible_v<E>) { | |
| return std::move(this->m_unexpect); | |
| } | |
| }; | |
| // This base class provides some handy member functions which can be used in | |
| // further derived classes | |
| template <class E> | |
| struct operations_base<void, E> : storage_base<void, E> { | |
| using storage_base<void, E>::storage_base; | |
| constexpr E& geterr() & noexcept { return this->m_unexpect; } | |
| constexpr const E& geterr() const& noexcept { return this->m_unexpect; } | |
| constexpr E&& geterr() && noexcept(std::is_nothrow_move_constructible_v<E>) { | |
| return std::move(this->m_unexpect); | |
| } | |
| constexpr const E&& geterr() const&& noexcept(std::is_nothrow_move_constructible_v<E>) { | |
| return std::move(this->m_unexpect); | |
| } | |
| }; | |
| // This class manages conditionally having a trivial copy constructor | |
| // This specialization is for when T and E are trivially copy constructible | |
| template <class T, class E, | |
| bool Enabled = | |
| is_copy_constructible_or_void_v<T> && std::is_copy_constructible_v<E>, | |
| bool Trivially = is_trivially_copy_constructible_or_void_v<T> && | |
| std::is_trivially_copy_constructible_v<E>> | |
| struct copy_ctor_base : operations_base<T, E> { | |
| using operations_base<T, E>::operations_base; | |
| }; | |
| // This specialization is for when T or E are not copy constructible | |
| template <class T, class E> | |
| struct copy_ctor_base<T, E, false, false> : operations_base<T, E> { | |
| using operations_base<T, E>::operations_base; | |
| ~copy_ctor_base() = default; | |
| copy_ctor_base() = default; | |
| copy_ctor_base(const copy_ctor_base& rhs) = delete; | |
| copy_ctor_base(copy_ctor_base&& rhs) = default; | |
| copy_ctor_base& operator=(const copy_ctor_base& rhs) = default; | |
| copy_ctor_base& operator=(copy_ctor_base&& rhs) = default; | |
| }; | |
| // This specialization is for when T or E are not trivially copy constructible | |
| template <class T, class E> | |
| struct copy_ctor_base<T, E, true, false> : operations_base<T, E> { | |
| using operations_base<T, E>::operations_base; | |
| ~copy_ctor_base() = default; | |
| copy_ctor_base() = default; | |
| constexpr copy_ctor_base(const copy_ctor_base& rhs) noexcept( | |
| is_nothrow_copy_constructible_or_void_v<T> && | |
| std::is_nothrow_copy_constructible_v<E>) | |
| : operations_base<T, E>(construct_from_expected, rhs) {} | |
| copy_ctor_base(copy_ctor_base&& rhs) = default; | |
| copy_ctor_base& operator=(const copy_ctor_base& rhs) = default; | |
| copy_ctor_base& operator=(copy_ctor_base&& rhs) = default; | |
| }; | |
| // This class manages conditionally having a trivial move constructor | |
| template <class T, class E, | |
| bool Enabled = | |
| is_move_constructible_or_void_v<T> && std::is_move_constructible_v<E>, | |
| bool Trivially = is_trivially_move_constructible_or_void_v<T> && | |
| std::is_trivially_move_constructible_v<E>> | |
| struct move_ctor_base : copy_ctor_base<T, E> { | |
| using copy_ctor_base<T, E>::copy_ctor_base; | |
| }; | |
| // This specialization is for when T or E are not move constructible | |
| template <class T, class E> | |
| struct move_ctor_base<T, E, false, false> : copy_ctor_base<T, E> { | |
| using copy_ctor_base<T, E>::copy_ctor_base; | |
| ~move_ctor_base() = default; | |
| move_ctor_base() = default; | |
| move_ctor_base(const move_ctor_base& rhs) = default; | |
| move_ctor_base(move_ctor_base&& rhs) = delete; | |
| move_ctor_base& operator=(const move_ctor_base& rhs) = default; | |
| move_ctor_base& operator=(move_ctor_base&& rhs) = default; | |
| }; | |
| // This specialization is for when T or E are not trivially move constructible | |
| template <class T, class E> | |
| struct move_ctor_base<T, E, true, false> : copy_ctor_base<T, E> { | |
| using copy_ctor_base<T, E>::copy_ctor_base; | |
| ~move_ctor_base() = default; | |
| move_ctor_base() = default; | |
| move_ctor_base(const move_ctor_base& rhs) = default; | |
| constexpr move_ctor_base(move_ctor_base&& rhs) noexcept( | |
| is_nothrow_move_constructible_or_void_v<T> && | |
| std::is_nothrow_move_constructible_v<E>) | |
| : copy_ctor_base<T, E>(construct_from_expected, std::move(rhs)) {} | |
| move_ctor_base& operator=(const move_ctor_base& rhs) = default; | |
| move_ctor_base& operator=(move_ctor_base&& rhs) = default; | |
| }; | |
| template <class T, class E> | |
| inline constexpr bool is_expected_copy_assignable_v = | |
| is_copy_assignable_or_void_v<T> && is_copy_constructible_or_void_v<T> && | |
| std::is_copy_assignable_v<E> && std::is_copy_constructible_v<E> && | |
| (is_nothrow_move_constructible_or_void_v<T> || | |
| std::is_nothrow_move_constructible_v<E>); | |
| // LWG-4026 | |
| template <class T, class E> | |
| inline constexpr bool is_expected_trivially_copy_assignable_v = | |
| is_trivially_copy_constructible_or_void_v<T> && | |
| is_trivially_copy_assignable_or_void_v<T> && is_trivially_destructible_or_void_v<T> && | |
| std::is_trivially_copy_constructible_v<E> && std::is_trivially_copy_assignable_v<E> && | |
| std::is_trivially_destructible_v<E>; | |
| // This class manages conditionally having a (possibly trivial) copy assignment operator | |
| template <class T, class E, bool Enabled = is_expected_copy_assignable_v<T, E>, | |
| bool Trivially = is_expected_trivially_copy_assignable_v<T, E>> | |
| struct copy_assign_base : move_ctor_base<T, E> { | |
| using move_ctor_base<T, E>::move_ctor_base; | |
| ~copy_assign_base() = default; | |
| copy_assign_base() = default; | |
| copy_assign_base(const copy_assign_base& rhs) = default; | |
| copy_assign_base(copy_assign_base&& rhs) = default; | |
| copy_assign_base& operator=(const copy_assign_base& rhs) = default; | |
| copy_assign_base& operator=(copy_assign_base&& rhs) = default; | |
| }; | |
| template <class T, class E> | |
| struct copy_assign_base<T, E, false, false> : move_ctor_base<T, E> { | |
| using move_ctor_base<T, E>::move_ctor_base; | |
| ~copy_assign_base() = default; | |
| copy_assign_base() = default; | |
| copy_assign_base(const copy_assign_base& rhs) = default; | |
| copy_assign_base(copy_assign_base&& rhs) = default; | |
| copy_assign_base& operator=(const copy_assign_base& rhs) = delete; | |
| copy_assign_base& operator=(copy_assign_base&& rhs) = default; | |
| }; | |
| template <class T, class E> | |
| struct copy_assign_base<T, E, true, false> : move_ctor_base<T, E> { | |
| using move_ctor_base<T, E>::move_ctor_base; | |
| ~copy_assign_base() = default; | |
| copy_assign_base() = default; | |
| copy_assign_base(const copy_assign_base& rhs) = default; | |
| copy_assign_base(copy_assign_base&& rhs) = default; | |
| constexpr copy_assign_base& operator=(const copy_assign_base& rhs) noexcept( | |
| std::is_nothrow_copy_constructible_v<T> && std::is_nothrow_copy_assignable_v<T> && | |
| std::is_nothrow_copy_constructible_v<E> && std::is_nothrow_copy_assignable_v<E>) { | |
| if (this->m_has_val && rhs.m_has_val) { | |
| this->m_val = rhs.m_val; | |
| } else if (this->m_has_val) { | |
| expected_detail::reinit_expected(this->m_unexpect, this->m_val, rhs.m_unexpect); | |
| } else if (rhs.m_has_val) { | |
| expected_detail::reinit_expected(this->m_val, this->m_unexpect, rhs.m_val); | |
| } else { | |
| this->m_unexpect = rhs.m_unexpect; | |
| } | |
| this->m_has_val = rhs.m_has_val; | |
| return *this; | |
| } | |
| copy_assign_base& operator=(copy_assign_base&& rhs) = default; | |
| }; | |
| template <class E> | |
| struct copy_assign_base<void, E, true, false> : move_ctor_base<void, E> { | |
| using move_ctor_base<void, E>::move_ctor_base; | |
| ~copy_assign_base() = default; | |
| copy_assign_base() = default; | |
| copy_assign_base(const copy_assign_base& rhs) = default; | |
| copy_assign_base(copy_assign_base&& rhs) = default; | |
| constexpr copy_assign_base& operator=(const copy_assign_base& rhs) noexcept( | |
| std::is_nothrow_copy_constructible_v<E> && std::is_nothrow_copy_assignable_v<E>) { | |
| if (this->m_has_val && rhs.m_has_val) { | |
| // no-op | |
| } else if (this->m_has_val) { | |
| expected_detail::construct_at(std::addressof(this->m_unexpect), rhs.m_unexpect); | |
| this->m_has_val = false; | |
| } else if (rhs.m_has_val) { | |
| this->m_unexpect.~E(); | |
| this->m_has_val = true; | |
| } else { | |
| this->m_unexpect = rhs.m_unexpect; | |
| } | |
| return *this; | |
| } | |
| copy_assign_base& operator=(copy_assign_base&& rhs) = default; | |
| }; | |
| template <class T, class E> | |
| inline constexpr bool is_expected_move_assignable_v = | |
| is_move_assignable_or_void_v<T> && is_move_constructible_or_void_v<T> && | |
| std::is_move_assignable_v<E> && std::is_move_constructible_v<E> && | |
| (is_nothrow_move_constructible_or_void_v<T> || | |
| std::is_nothrow_move_constructible_v<E>); | |
| // LWG-4026 | |
| template <class T, class E> | |
| inline constexpr bool is_expected_trivially_move_assignable_v = | |
| is_trivially_move_constructible_or_void_v<T> && | |
| is_trivially_move_assignable_or_void_v<T> && is_trivially_destructible_or_void_v<T> && | |
| std::is_trivially_move_constructible_v<E> && std::is_trivially_move_assignable_v<E> && | |
| std::is_trivially_destructible_v<E>; | |
| // This class manages conditionally having a (possibly trivial) move assignment operator | |
| template <class T, class E, bool Enabled = is_expected_move_assignable_v<T, E>, | |
| bool Trivially = is_expected_trivially_move_assignable_v<T, E>> | |
| struct move_assign_base : copy_assign_base<T, E> { | |
| using copy_assign_base<T, E>::copy_assign_base; | |
| ~move_assign_base() = default; | |
| move_assign_base() = default; | |
| move_assign_base(const move_assign_base& rhs) = default; | |
| move_assign_base(move_assign_base&& rhs) = default; | |
| move_assign_base& operator=(const move_assign_base& rhs) = default; | |
| move_assign_base& operator=(move_assign_base&& rhs) = default; | |
| }; | |
| template <class T, class E> | |
| struct move_assign_base<T, E, false, false> : copy_assign_base<T, E> { | |
| using copy_assign_base<T, E>::copy_assign_base; | |
| ~move_assign_base() = default; | |
| move_assign_base() = default; | |
| move_assign_base(const move_assign_base& rhs) = default; | |
| move_assign_base(move_assign_base&& rhs) = default; | |
| move_assign_base& operator=(const move_assign_base& rhs) = default; | |
| move_assign_base& operator=(move_assign_base&& rhs) = delete; | |
| }; | |
| template <class T, class E> | |
| struct move_assign_base<T, E, true, false> : copy_assign_base<T, E> { | |
| using copy_assign_base<T, E>::copy_assign_base; | |
| ~move_assign_base() = default; | |
| move_assign_base() = default; | |
| move_assign_base(const move_assign_base& rhs) = default; | |
| move_assign_base(move_assign_base&& rhs) = default; | |
| move_assign_base& operator=(const move_assign_base& rhs) = default; | |
| constexpr move_assign_base& operator=(move_assign_base&& rhs) noexcept( | |
| std::is_nothrow_move_constructible_v<T> && std::is_nothrow_move_assignable_v<T> && | |
| std::is_nothrow_move_constructible_v<E> && std::is_nothrow_move_assignable_v<E>) { | |
| if (this->m_has_val && rhs.m_has_val) { | |
| this->m_val = std::move(rhs.m_val); | |
| } else if (this->m_has_val) { | |
| expected_detail::reinit_expected(this->m_unexpect, this->m_val, | |
| std::move(rhs.m_unexpect)); | |
| } else if (rhs.m_has_val) { | |
| expected_detail::reinit_expected(this->m_val, this->m_unexpect, | |
| std::move(rhs.m_val)); | |
| } else { | |
| this->m_unexpect = std::move(rhs.m_unexpect); | |
| } | |
| this->m_has_val = rhs.m_has_val; | |
| return *this; | |
| } | |
| }; | |
| template <class E> | |
| struct move_assign_base<void, E, true, false> : copy_assign_base<void, E> { | |
| using copy_assign_base<void, E>::copy_assign_base; | |
| ~move_assign_base() = default; | |
| move_assign_base() = default; | |
| move_assign_base(const move_assign_base& rhs) = default; | |
| move_assign_base(move_assign_base&& rhs) = default; | |
| move_assign_base& operator=(const move_assign_base& rhs) = default; | |
| constexpr move_assign_base& operator=(move_assign_base&& rhs) noexcept( | |
| std::is_nothrow_move_constructible_v<E> && std::is_nothrow_move_assignable_v<E>) { | |
| if (this->m_has_val && rhs.m_has_val) { | |
| // no-op | |
| } else if (this->m_has_val) { | |
| expected_detail::construct_at(std::addressof(this->m_unexpect), | |
| std::move(rhs.m_unexpect)); | |
| this->m_has_val = false; | |
| } else if (rhs.m_has_val) { | |
| this->m_unexpect.~E(); | |
| this->m_has_val = true; | |
| } else { | |
| this->m_unexpect = std::move(rhs.m_unexpect); | |
| } | |
| return *this; | |
| } | |
| }; | |
| // This is needed to be able to construct the default_ctor_base which | |
| // follows, while still conditionally deleting the default constructor. | |
| struct default_constructor_tag { | |
| explicit default_constructor_tag() = default; | |
| }; | |
| // default_ctor_base will ensure that expected has a deleted default | |
| // consturctor if T is not default constructible. | |
| // This specialization is for when T is default constructible | |
| template <class T, class E, bool Enable = is_default_constructible_or_void_v<T>> | |
| struct default_ctor_base { | |
| ~default_ctor_base() = default; | |
| default_ctor_base() = default; | |
| default_ctor_base(default_ctor_base const&) = default; | |
| default_ctor_base(default_ctor_base&&) = default; | |
| default_ctor_base& operator=(default_ctor_base const&) = default; | |
| default_ctor_base& operator=(default_ctor_base&&) = default; | |
| constexpr explicit default_ctor_base(default_constructor_tag) {} | |
| }; | |
| // This specialization is for when T is not default constructible | |
| template <class T, class E> | |
| struct default_ctor_base<T, E, false> { | |
| ~default_ctor_base() = default; | |
| default_ctor_base() = delete; | |
| default_ctor_base(default_ctor_base const&) = default; | |
| default_ctor_base(default_ctor_base&&) = default; | |
| default_ctor_base& operator=(default_ctor_base const&) = default; | |
| default_ctor_base& operator=(default_ctor_base&&) = default; | |
| constexpr explicit default_ctor_base(default_constructor_tag) {} | |
| }; | |
| } // namespace expected_detail | |
| /// An `expected<T, E>` object is an object that contains the storage for | |
| /// another object and manages the lifetime of this contained object `T`. | |
| /// Alternatively it could contain the storage for another unexpected object | |
| /// `E`. The contained object may not be initialized after the expected object | |
| /// has been initialized, and may not be destroyed before the expected object | |
| /// has been destroyed. The initialization state of the contained object is | |
| /// tracked by the expected object. | |
| template <class T, class E> | |
| class ICEBERG_TEMPLATE_CLASS_EXPORT [[nodiscard]] expected | |
| : private expected_detail::move_assign_base<T, E>, | |
| private expected_detail::default_ctor_base<T, E> { | |
| static_assert(expected_detail::is_value_type_valid_v<T>); | |
| static_assert(expected_detail::is_error_type_valid_v<E>); | |
| constexpr T* valptr() noexcept { return std::addressof(this->m_val); } | |
| constexpr const T* valptr() const noexcept { return std::addressof(this->m_val); } | |
| constexpr E* errptr() noexcept { return std::addressof(this->m_unexpect); } | |
| constexpr const E* errptr() const noexcept { return std::addressof(this->m_unexpect); } | |
| constexpr T& val() noexcept { return this->m_val; } | |
| constexpr E& err() noexcept { return this->m_unexpect; } | |
| constexpr const T& val() const noexcept { return this->m_val; } | |
| constexpr const E& err() const noexcept { return this->m_unexpect; } | |
| using impl_base = expected_detail::move_assign_base<T, E>; | |
| using ctor_base = expected_detail::default_ctor_base<T, E>; | |
| public: | |
| typedef T value_type; | |
| typedef E error_type; | |
| typedef unexpected<E> unexpected_type; | |
| template <class U> | |
| using rebind = expected<U, error_type>; | |
| // default constructors | |
| constexpr expected() = default; | |
| constexpr expected(const expected& rhs) = default; | |
| constexpr expected(expected&& rhs) = default; | |
| // constructors for expected<U, G> | |
| // implicit const reference | |
| template <class U, class G, | |
| std::enable_if_t<!std::is_same_v<expected<U, G>, expected>>* = nullptr, | |
| std::enable_if_t<std::is_convertible_v<const U&, T> && | |
| std::is_convertible_v<const G&, E>>* = nullptr, | |
| expected_detail::enable_from_other_expected_t<T, E, U, G, const U&, | |
| const G&>* = nullptr> | |
| constexpr expected(const expected<U, G>& rhs) noexcept( | |
| std::is_nothrow_constructible_v<T, const U&> && | |
| std::is_nothrow_constructible_v<E, const G&>) | |
| : impl_base(expected_detail::construct_from_expected, rhs), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // explicit const reference | |
| template <class U, class G, | |
| std::enable_if_t<!std::is_same_v<expected<U, G>, expected>>* = nullptr, | |
| std::enable_if_t<!(std::is_convertible_v<const U&, T> && | |
| std::is_convertible_v<const G&, E>)>* = nullptr, | |
| expected_detail::enable_from_other_expected_t<T, E, U, G, const U&, | |
| const G&>* = nullptr> | |
| constexpr explicit expected(const expected<U, G>& rhs) noexcept( | |
| std::is_nothrow_constructible_v<T, const U&> && | |
| std::is_nothrow_constructible_v<E, const G&>) | |
| : impl_base(expected_detail::construct_from_expected, rhs), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // implicit rvalue | |
| template <class U, class G, | |
| std::enable_if_t<!std::is_same_v<expected<U, G>, expected>>* = nullptr, | |
| std::enable_if_t<std::is_convertible_v<U, T> && | |
| std::is_convertible_v<G, E>>* = nullptr, | |
| expected_detail::enable_from_other_expected_t<T, E, U, G, U, G>* = nullptr> | |
| constexpr expected(expected<U, G>&& rhs) noexcept( | |
| std::is_nothrow_constructible_v<T, U> && std::is_nothrow_constructible_v<E, G>) | |
| : impl_base(expected_detail::construct_from_expected, std::move(rhs)), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // explicit rvalue | |
| template <class U, class G, | |
| std::enable_if_t<!std::is_same_v<expected<U, G>, expected>>* = nullptr, | |
| std::enable_if_t<!(std::is_convertible_v<U, T> && | |
| std::is_convertible_v<G, E>)>* = nullptr, | |
| expected_detail::enable_from_other_expected_t<T, E, U, G, U, G>* = nullptr> | |
| constexpr explicit expected(expected<U, G>&& rhs) noexcept( | |
| std::is_nothrow_constructible_v<T, U> && std::is_nothrow_constructible_v<E, G>) | |
| : impl_base(expected_detail::construct_from_expected, std::move(rhs)), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // template <class U = T> | |
| // expected(U &&) | |
| // implicit | |
| template <class U = std::remove_cv_t<T>, | |
| std::enable_if_t<std::is_convertible_v<U, T>>* = nullptr, | |
| expected_detail::enable_forward_t<T, E, U>* = nullptr> | |
| constexpr expected(U&& v) noexcept(std::is_nothrow_constructible_v<T, U>) | |
| : expected(std::in_place, std::forward<U>(v)) {} | |
| // explicit | |
| template <class U = std::remove_cv_t<T>, | |
| std::enable_if_t<!std::is_convertible_v<U, T>>* = nullptr, | |
| expected_detail::enable_forward_t<T, E, U>* = nullptr> | |
| constexpr explicit expected(U&& v) noexcept(std::is_nothrow_constructible_v<T, U>) | |
| : expected(std::in_place, std::forward<U>(v)) {} | |
| #if !ICEBERG_CXX20_COMPAT && defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L | |
| // iceberg: library code built as C++23 still spells some errors std::unexpected. | |
| template <class G, std::enable_if_t<std::is_constructible_v<E, const G&>>* = nullptr> | |
| constexpr explicit(!std::is_convertible_v<const G&, E>) expected( | |
| const std::unexpected<G>& e) noexcept(std::is_nothrow_constructible_v<E, const G&>) | |
| : expected(unexpect, e.error()) {} | |
| template <class G, std::enable_if_t<std::is_constructible_v<E, G>>* = nullptr> | |
| constexpr explicit(!std::is_convertible_v<G, E>) | |
| expected(std::unexpected<G>&& e) noexcept(std::is_nothrow_constructible_v<E, G>) | |
| : expected(unexpect, std::move(e.error())) {} | |
| #endif | |
| // constructors for unexpected<G> | |
| // explicit const unexpected<G> & | |
| template <class G, std::enable_if_t<!std::is_convertible_v<const G&, E>>* = nullptr, | |
| std::enable_if_t<std::is_constructible_v<E, const G&>>* = nullptr> | |
| explicit constexpr expected(const unexpected<G>& e) noexcept( | |
| std::is_nothrow_constructible_v<E, const G&>) | |
| : impl_base(unexpect, e.error()), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // implicit const unexpected<G> & | |
| template <class G, std::enable_if_t<std::is_convertible_v<const G&, E>>* = nullptr, | |
| std::enable_if_t<std::is_constructible_v<E, const G&>>* = nullptr> | |
| constexpr expected(unexpected<G> const& e) noexcept( | |
| std::is_nothrow_constructible_v<E, const G&>) | |
| : impl_base(unexpect, e.error()), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // explicit unexpected<G> && | |
| template <class G, std::enable_if_t<!std::is_convertible_v<G, E>>* = nullptr, | |
| std::enable_if_t<std::is_constructible_v<E, G>>* = nullptr> | |
| explicit constexpr expected(unexpected<G>&& e) noexcept( | |
| std::is_nothrow_constructible_v<E, G>) | |
| : impl_base(unexpect, std::move(e.error())), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // implicit unexpected<G> && | |
| template <class G, std::enable_if_t<std::is_convertible_v<G, E>>* = nullptr, | |
| std::enable_if_t<std::is_constructible_v<E, G>>* = nullptr> | |
| constexpr expected(unexpected<G>&& e) noexcept(std::is_nothrow_constructible_v<E, G>) | |
| : impl_base(unexpect, std::move(e.error())), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // template<class... Args> | |
| // expected(std::in_place_t, Args &&...) | |
| template <class... Args, | |
| std::enable_if_t<std::is_constructible_v<T, Args...>>* = nullptr> | |
| constexpr explicit expected(std::in_place_t, Args&&... args) noexcept( | |
| std::is_nothrow_constructible_v<T, Args...>) | |
| : impl_base(std::in_place, std::forward<Args>(args)...), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // template<class U, class... Args> | |
| // expected(std::in_place_t, std::initializer_list<U>, Args &&...) | |
| template <class U, class... Args, | |
| std::enable_if_t<std::is_constructible_v<T, std::initializer_list<U>&, | |
| Args...>>* = nullptr> | |
| 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...>) | |
| : impl_base(std::in_place, il, std::forward<Args>(args)...), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // helper ctor for transform() | |
| template <class Fn, class... Args> | |
| constexpr explicit expected( | |
| expected_detail::construct_with_invoke_result_t tag, Fn&& func, | |
| Args&&... args) noexcept(noexcept(static_cast<T>(std::invoke(std::forward<Fn>(func), | |
| std::forward<Args>( | |
| args)...)))) | |
| : impl_base(tag, std::forward<Fn>(func), std::forward<Args>(args)...), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // template<class... Args> | |
| // expected(unexpect_t, Args &&...) | |
| template <class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, Args...>>* = nullptr> | |
| constexpr explicit expected(unexpect_t, Args&&... args) noexcept( | |
| std::is_nothrow_constructible_v<E, Args...>) | |
| : impl_base(unexpect, std::forward<Args>(args)...), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // template<class U, class... Args> | |
| // expected(unexpect_t, std::initializer_list<U>, Args &&...) | |
| template <class U, class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, std::initializer_list<U>&, | |
| Args...>>* = nullptr> | |
| constexpr explicit expected( | |
| unexpect_t, std::initializer_list<U> il, | |
| Args&&... args) noexcept(std::is_nothrow_constructible_v<E, | |
| std::initializer_list<U>&, | |
| Args...>) | |
| : impl_base(unexpect, il, std::forward<Args>(args)...), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // helper ctor for transform_error() | |
| template <class Fn, class... Args> | |
| constexpr explicit expected( | |
| expected_detail::construct_with_invoke_result_t tag1, unexpect_t tag2, Fn&& func, | |
| Args&&... args) noexcept(noexcept(static_cast<E>(std::invoke(std::forward<Fn>(func), | |
| std::forward<Args>( | |
| args)...)))) | |
| : impl_base(tag1, tag2, std::forward<Fn>(func), std::forward<Args>(args)...), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // default assignment operators | |
| constexpr expected& operator=(const expected& rhs) = default; | |
| constexpr expected& operator=(expected&& rhs) = default; | |
| // assignments | |
| template < | |
| class U = std::remove_cv_t<T>, | |
| std::enable_if_t<!std::is_same_v<expected, expected_detail::remove_cvref_t<U>> && | |
| !expected_detail::is_specialization_v< | |
| expected_detail::remove_cvref_t<U>, unexpected> && | |
| std::is_constructible_v<T, U> && std::is_assignable_v<T&, U> && | |
| (std::is_nothrow_constructible_v<T, U> || | |
| std::is_nothrow_move_constructible_v<T> || | |
| std::is_nothrow_move_constructible_v<E>)>* = nullptr> | |
| constexpr expected& operator=(U&& v) noexcept(std::is_nothrow_constructible_v<T, U> && | |
| std::is_nothrow_assignable_v<T&, U>) { | |
| if (has_value()) { | |
| val() = std::forward<U>(v); | |
| } else { | |
| expected_detail::reinit_expected(val(), err(), std::forward<U>(v)); | |
| this->m_has_val = true; | |
| } | |
| return *this; | |
| } | |
| template < | |
| class G, class GF = const G&, | |
| std::enable_if_t<std::is_constructible_v<E, GF> && std::is_assignable_v<E&, GF> && | |
| (std::is_nothrow_constructible_v<E, GF> || | |
| std::is_nothrow_move_constructible_v<T> || | |
| std::is_nothrow_move_constructible_v<E>)>* = nullptr> | |
| constexpr expected& operator=(const unexpected<G>& rhs) noexcept( | |
| std::is_nothrow_constructible_v<E, GF> && std::is_nothrow_assignable_v<E&, GF>) { | |
| if (!has_value()) { | |
| err() = std::forward<GF>(rhs.error()); | |
| } else { | |
| expected_detail::reinit_expected(err(), val(), std::forward<GF>(rhs.error())); | |
| this->m_has_val = false; | |
| } | |
| return *this; | |
| } | |
| template < | |
| class G, class GF = G, | |
| std::enable_if_t<std::is_constructible_v<E, GF> && std::is_assignable_v<E&, GF> && | |
| (std::is_nothrow_constructible_v<E, GF> || | |
| std::is_nothrow_move_constructible_v<T> || | |
| std::is_nothrow_move_constructible_v<E>)>* = nullptr> | |
| constexpr expected& operator=(unexpected<G>&& rhs) noexcept( | |
| std::is_nothrow_constructible_v<E, GF> && std::is_nothrow_assignable_v<E&, GF>) { | |
| if (!has_value()) { | |
| err() = std::forward<GF>(rhs.error()); | |
| } else { | |
| expected_detail::reinit_expected(err(), val(), std::forward<GF>(rhs.error())); | |
| this->m_has_val = false; | |
| } | |
| return *this; | |
| } | |
| template <class... Args, | |
| typename std::enable_if_t<std::is_nothrow_constructible_v<T, Args&&...>, | |
| bool> = false> | |
| constexpr T& emplace(Args&&... args) { | |
| if (has_value()) { | |
| if constexpr (!std::is_trivially_destructible_v<T>) { | |
| val().~T(); | |
| } | |
| } else { | |
| if constexpr (!std::is_trivially_destructible_v<E>) { | |
| err().~E(); | |
| } | |
| this->m_has_val = true; | |
| } | |
| return *expected_detail::construct_at(valptr(), std::forward<Args>(args)...); | |
| } | |
| template <class U, class... Args, | |
| std::enable_if_t<std::is_nothrow_constructible_v<T, std::initializer_list<U>&, | |
| Args&&...>>* = nullptr> | |
| constexpr T& emplace(std::initializer_list<U> il, Args&&... args) { | |
| if (has_value()) { | |
| if constexpr (!std::is_trivially_destructible_v<T>) { | |
| val().~T(); | |
| } | |
| } else { | |
| if constexpr (!std::is_trivially_destructible_v<E>) { | |
| err().~E(); | |
| } | |
| this->m_has_val = true; | |
| } | |
| return *expected_detail::construct_at(valptr(), il, std::forward<Args>(args)...); | |
| } | |
| template <class OT = T, class OE = E> | |
| constexpr std::enable_if_t<std::is_swappable_v<OT> && std::is_swappable_v<OE> && | |
| std::is_move_constructible_v<OT> && | |
| std::is_move_constructible_v<OE> && | |
| (std::is_nothrow_move_constructible_v<OT> || | |
| std::is_nothrow_move_constructible_v<OE>)> | |
| swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v<T> && | |
| std::is_nothrow_swappable_v<T> && | |
| std::is_nothrow_move_constructible_v<E> && | |
| std::is_nothrow_swappable_v<E>) { | |
| using std::swap; | |
| if (this->m_has_val && rhs.m_has_val) { | |
| swap(this->m_val, rhs.m_val); // ADL | |
| } else if (this->m_has_val) { | |
| if constexpr (std::is_nothrow_move_constructible_v<E>) { | |
| E tmp(std::move(rhs.error())); | |
| if constexpr (!std::is_trivially_destructible_v<E>) { | |
| rhs.error().~E(); | |
| } | |
| if constexpr (std::is_nothrow_move_constructible_v<T>) { | |
| expected_detail::construct_at(std::addressof(rhs.m_val), | |
| std::move(this->m_val)); | |
| } else { | |
| expected_detail::ReinitGuard<E> guard{std::addressof(rhs.error()), | |
| std::addressof(tmp)}; | |
| expected_detail::construct_at(std::addressof(rhs.m_val), | |
| std::move(this->m_val)); | |
| guard._target = nullptr; | |
| } | |
| if constexpr (!std::is_trivially_destructible_v<T>) { | |
| this->m_val.~T(); | |
| } | |
| expected_detail::construct_at(std::addressof(this->error()), std::move(tmp)); | |
| } else { | |
| T tmp(std::move(this->m_val)); | |
| if constexpr (!std::is_trivially_destructible_v<T>) { | |
| this->m_val.~T(); | |
| } | |
| expected_detail::ReinitGuard<T> guard{std::addressof(this->m_val), | |
| std::addressof(tmp)}; | |
| expected_detail::construct_at(std::addressof(this->error()), | |
| std::move(rhs.error())); | |
| guard._target = nullptr; | |
| if constexpr (!std::is_trivially_destructible_v<E>) { | |
| rhs.error().~E(); | |
| } | |
| expected_detail::construct_at(std::addressof(rhs.m_val), std::move(tmp)); | |
| } | |
| this->m_has_val = false; | |
| rhs.m_has_val = true; | |
| } else if (rhs.m_has_val) { | |
| rhs.swap(*this); | |
| } else { | |
| swap(this->error(), rhs.error()); // ADL | |
| } | |
| } | |
| constexpr const T* operator->() const noexcept { return valptr(); } | |
| constexpr T* operator->() noexcept { return valptr(); } | |
| constexpr const T& operator*() const& noexcept { return val(); } | |
| constexpr T& operator*() & noexcept { return val(); } | |
| constexpr const T&& operator*() const&& noexcept { return std::move(val()); } | |
| constexpr T&& operator*() && noexcept { return std::move(val()); } | |
| constexpr bool has_value() const noexcept { return this->m_has_val; } | |
| constexpr explicit operator bool() const noexcept { return this->m_has_val; } | |
| constexpr const T& value() const& { | |
| static_assert(std::is_copy_constructible_v<E>, | |
| "E must be copy constructible, by LWG-3843"); | |
| if (!has_value()) ICEBERG_EXPECTED_THROW(bad_expected_access(error())); | |
| return val(); | |
| } | |
| constexpr T& value() & { | |
| static_assert(std::is_copy_constructible_v<E>, | |
| "E must be copy constructible, by LWG-3843"); | |
| if (!has_value()) ICEBERG_EXPECTED_THROW(bad_expected_access(std::as_const(error()))); | |
| return val(); | |
| } | |
| constexpr const T&& value() const&& { | |
| static_assert(std::is_copy_constructible_v<E>, | |
| "E must be copy constructible, by LWG-3843"); | |
| static_assert(std::is_constructible_v<E, decltype(std::move(error()))>, | |
| "E must be constructible from const E&&, by LWG-3843"); | |
| if (!has_value()) ICEBERG_EXPECTED_THROW(bad_expected_access(std::move(error()))); | |
| return std::move(val()); | |
| } | |
| constexpr T&& value() && { | |
| static_assert(std::is_copy_constructible_v<E>, | |
| "E must be copy constructible, by LWG-3843"); | |
| static_assert(std::is_constructible_v<E, decltype(std::move(error()))>, | |
| "E must be constructible from E&&, by LWG-3843"); | |
| if (!has_value()) ICEBERG_EXPECTED_THROW(bad_expected_access(std::move(error()))); | |
| return std::move(val()); | |
| } | |
| constexpr const E& error() const& noexcept { return err(); } | |
| constexpr E& error() & noexcept { return err(); } | |
| constexpr const E&& error() const&& noexcept { return std::move(err()); } | |
| constexpr E&& error() && noexcept { return std::move(err()); } | |
| template <class U = std::remove_cv_t<T>> | |
| constexpr T value_or(U&& v) const& noexcept( | |
| std::is_nothrow_copy_constructible_v<T> && | |
| expected_detail::is_nothrow_convertible_v<U, T>) { | |
| static_assert(std::is_copy_constructible_v<T>, "T must be copy-constructible"); | |
| static_assert(std::is_convertible_v<U, T>, "is_convertible_v<U, T> must be true"); | |
| if (this->m_has_val) { | |
| return this->m_val; | |
| } else { | |
| return static_cast<T>(std::forward<U>(v)); | |
| } | |
| } | |
| template <class U = std::remove_cv_t<T>> | |
| constexpr T value_or(U&& v) && noexcept( | |
| std::is_nothrow_move_constructible_v<T> && | |
| expected_detail::is_nothrow_convertible_v<U, T>) { | |
| static_assert(std::is_move_constructible_v<T>, "T must be move-constructible"); | |
| static_assert(std::is_convertible_v<U, T>, "is_convertible_v<U, T> must be true"); | |
| if (this->m_has_val) { | |
| return std::move(this->m_val); | |
| } else { | |
| return static_cast<T>(std::forward<U>(v)); | |
| } | |
| } | |
| template <class G = E> | |
| constexpr E error_or(G&& v) const& noexcept( | |
| std::is_nothrow_copy_constructible_v<E> && | |
| expected_detail::is_nothrow_convertible_v<G, E>) { | |
| static_assert(std::is_copy_constructible_v<E>, "E must be copy-constructible"); | |
| static_assert(std::is_convertible_v<G, E>, "is_convertible_v<G, E> must be true"); | |
| if (this->m_has_val) { | |
| return static_cast<E>(std::forward<G>(v)); | |
| } else { | |
| return this->m_unexpect; | |
| } | |
| } | |
| template <class G = E> | |
| constexpr E error_or(G&& v) && noexcept( | |
| std::is_nothrow_move_constructible_v<E> && | |
| expected_detail::is_nothrow_convertible_v<G, E>) { | |
| static_assert(std::is_move_constructible_v<E>, "E must be move-constructible"); | |
| static_assert(std::is_convertible_v<G, E>, "is_convertible_v<G, E> must be true"); | |
| if (this->m_has_val) { | |
| return static_cast<E>(std::forward<G>(v)); | |
| } else { | |
| return std::move(this->m_unexpect); | |
| } | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, GE&>>* = nullptr> | |
| constexpr auto and_then(F&& f) & { | |
| using U = | |
| expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype((this->m_val))>>; | |
| static_assert(expected_detail::is_specialization_v<U, expected>, | |
| "U (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename U::error_type, E>, | |
| "The error type must be the same after calling the F"); | |
| if (has_value()) | |
| return std::invoke(std::forward<F>(f), this->m_val); | |
| else | |
| return U(unexpect, error()); | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, const GE&>>* = nullptr> | |
| constexpr auto and_then(F&& f) const& { | |
| using U = | |
| expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype((this->m_val))>>; | |
| static_assert(expected_detail::is_specialization_v<U, expected>, | |
| "U (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename U::error_type, E>, | |
| "The error type must be the same after calling the F"); | |
| if (has_value()) | |
| return std::invoke(std::forward<F>(f), this->m_val); | |
| else | |
| return U(unexpect, error()); | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, GE&&>>* = nullptr> | |
| constexpr auto and_then(F&& f) && { | |
| using U = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(this->m_val))>>; | |
| static_assert(expected_detail::is_specialization_v<U, expected>, | |
| "U (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename U::error_type, E>, | |
| "The error type must be the same after calling the F"); | |
| if (has_value()) | |
| return std::invoke(std::forward<F>(f), std::move(this->m_val)); | |
| else | |
| return U(unexpect, std::move(error())); | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, const GE>>* = nullptr> | |
| constexpr auto and_then(F&& f) const&& { | |
| using U = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(this->m_val))>>; | |
| static_assert(expected_detail::is_specialization_v<U, expected>, | |
| "U (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename U::error_type, E>, | |
| "The error type must be the same after calling the F"); | |
| if (has_value()) | |
| return std::invoke(std::forward<F>(f), std::move(this->m_val)); | |
| else | |
| return U(unexpect, std::move(error())); | |
| } | |
| template <class F, class UT = T, | |
| std::enable_if_t<std::is_constructible_v<UT, UT&>>* = nullptr> | |
| constexpr auto or_else(F&& f) & { | |
| using G = expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype(error())>>; | |
| static_assert(expected_detail::is_specialization_v<G, expected>, | |
| "G (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename G::value_type, T>, | |
| "The value type must be the same after calling the F"); | |
| if (has_value()) | |
| return G(std::in_place, this->m_val); | |
| else | |
| return std::invoke(std::forward<F>(f), error()); | |
| } | |
| template <class F, class UT = T, | |
| std::enable_if_t<std::is_constructible_v<UT, const UT&>>* = nullptr> | |
| constexpr auto or_else(F&& f) const& { | |
| using G = expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype(error())>>; | |
| static_assert(expected_detail::is_specialization_v<G, expected>, | |
| "G (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename G::value_type, T>, | |
| "The value type must be the same after calling the F"); | |
| if (has_value()) | |
| return G(std::in_place, this->m_val); | |
| else | |
| return std::invoke(std::forward<F>(f), error()); | |
| } | |
| template <class F, class UT = T, | |
| std::enable_if_t<std::is_constructible_v<UT, UT&&>>* = nullptr> | |
| constexpr auto or_else(F&& f) && { | |
| using G = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(error()))>>; | |
| static_assert(expected_detail::is_specialization_v<G, expected>, | |
| "G (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename G::value_type, T>, | |
| "The value type must be the same after calling the F"); | |
| if (has_value()) | |
| return G(std::in_place, std::move(this->m_val)); | |
| else | |
| return std::invoke(std::forward<F>(f), std::move(error())); | |
| } | |
| template <class F, class UT = T, | |
| std::enable_if_t<std::is_constructible_v<UT, const UT>>* = nullptr> | |
| constexpr auto or_else(F&& f) const&& { | |
| using G = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(error()))>>; | |
| static_assert(expected_detail::is_specialization_v<G, expected>, | |
| "G (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename G::value_type, T>, | |
| "The value type must be the same after calling the F"); | |
| if (has_value()) | |
| return G(std::in_place, std::move(this->m_val)); | |
| else | |
| return std::invoke(std::forward<F>(f), std::move(error())); | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, GE&>>* = nullptr> | |
| constexpr auto transform(F&& f) & { | |
| using U = | |
| expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype((this->m_val))>>; | |
| static_assert(expected_detail::is_value_type_valid_v<U>, | |
| "U must be a valid type for expected<U, E>"); | |
| // FIXME another constraint needed here | |
| if (!has_value()) { | |
| return expected<U, E>(unexpect, error()); | |
| } else { | |
| if constexpr (std::is_void_v<U>) { | |
| std::invoke(std::forward<F>(f), this->m_val); | |
| return expected<U, E>{}; | |
| } else { | |
| return expected<U, E>(expected_detail::construct_with_invoke_result_t{}, | |
| std::forward<F>(f), this->m_val); | |
| } | |
| } | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, const GE&>>* = nullptr> | |
| constexpr auto transform(F&& f) const& { | |
| using U = | |
| expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype((this->m_val))>>; | |
| static_assert(expected_detail::is_value_type_valid_v<U>, | |
| "U must be a valid type for expected<U, E>"); | |
| // FIXME another constraint needed here | |
| if (!has_value()) { | |
| return expected<U, E>(unexpect, error()); | |
| } else { | |
| if constexpr (std::is_void_v<U>) { | |
| std::invoke(std::forward<F>(f), this->m_val); | |
| return expected<U, E>{}; | |
| } else { | |
| return expected<U, E>(expected_detail::construct_with_invoke_result_t{}, | |
| std::forward<F>(f), this->m_val); | |
| } | |
| } | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, GE&&>>* = nullptr> | |
| constexpr auto transform(F&& f) && { | |
| using U = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(this->m_val))>>; | |
| static_assert(expected_detail::is_value_type_valid_v<U>, | |
| "U must be a valid type for expected<U, E>"); | |
| // FIXME another constraint needed here | |
| if (!has_value()) { | |
| return expected<U, E>(unexpect, std::move(error())); | |
| } else { | |
| if constexpr (std::is_void_v<U>) { | |
| std::invoke(std::forward<F>(f), std::move(this->m_val)); | |
| return expected<U, E>{}; | |
| } else { | |
| return expected<U, E>(expected_detail::construct_with_invoke_result_t{}, | |
| std::forward<F>(f), std::move(this->m_val)); | |
| } | |
| } | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, const GE>>* = nullptr> | |
| constexpr auto transform(F&& f) const&& { | |
| using U = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(this->m_val))>>; | |
| static_assert(expected_detail::is_value_type_valid_v<U>, | |
| "U must be a valid type for expected<U, E>"); | |
| // FIXME another constraint needed here | |
| if (!has_value()) { | |
| return expected<U, E>(unexpect, std::move(error())); | |
| } else { | |
| if constexpr (std::is_void_v<U>) { | |
| std::invoke(std::forward<F>(f), std::move(this->m_val)); | |
| return expected<U, E>{}; | |
| } else { | |
| return expected<U, E>(expected_detail::construct_with_invoke_result_t{}, | |
| std::forward<F>(f), std::move(this->m_val)); | |
| } | |
| } | |
| } | |
| template <class F, class UT = T, | |
| std::enable_if_t<std::is_constructible_v<UT, UT&>>* = nullptr> | |
| constexpr auto transform_error(F&& f) & { | |
| using G = expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype(error())>>; | |
| static_assert(expected_detail::is_error_type_valid_v<G>, | |
| "G must be a valid type for expected<T, G>"); | |
| // FIXME another constraint needed here | |
| if (has_value()) { | |
| return expected<T, G>(std::in_place, this->m_val); | |
| } else { | |
| return expected<T, G>(expected_detail::construct_with_invoke_result_t{}, unexpect, | |
| std::forward<F>(f), error()); | |
| } | |
| } | |
| template <class F, class UT = T, | |
| std::enable_if_t<std::is_constructible_v<UT, const UT&>>* = nullptr> | |
| constexpr auto transform_error(F&& f) const& { | |
| using G = expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype(error())>>; | |
| static_assert(expected_detail::is_error_type_valid_v<G>, | |
| "G must be a valid type for expected<T, G>"); | |
| // FIXME another constraint needed here | |
| if (has_value()) { | |
| return expected<T, G>(std::in_place, this->m_val); | |
| } else { | |
| return expected<T, G>(expected_detail::construct_with_invoke_result_t{}, unexpect, | |
| std::forward<F>(f), error()); | |
| } | |
| } | |
| template <class F, class UT = T, | |
| std::enable_if_t<std::is_constructible_v<UT, UT&&>>* = nullptr> | |
| constexpr auto transform_error(F&& f) && { | |
| using G = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(error()))>>; | |
| static_assert(expected_detail::is_error_type_valid_v<G>, | |
| "G must be a valid type for expected<T, G>"); | |
| // FIXME another constraint needed here | |
| if (has_value()) { | |
| return expected<T, G>(std::in_place, std::move(this->m_val)); | |
| } else { | |
| return expected<T, G>(expected_detail::construct_with_invoke_result_t{}, unexpect, | |
| std::forward<F>(f), std::move(error())); | |
| } | |
| } | |
| template <class F, class UT = T, | |
| std::enable_if_t<std::is_constructible_v<UT, const UT>>* = nullptr> | |
| constexpr auto transform_error(F&& f) const&& { | |
| using G = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(error()))>>; | |
| static_assert(expected_detail::is_error_type_valid_v<G>, | |
| "G must be a valid type for expected<T, G>"); | |
| // FIXME another constraint needed here | |
| if (has_value()) { | |
| return expected<T, G>(std::in_place, std::move(this->m_val)); | |
| } else { | |
| return expected<T, G>(expected_detail::construct_with_invoke_result_t{}, unexpect, | |
| std::forward<F>(f), std::move(error())); | |
| } | |
| } | |
| template <class T2, class E2> | |
| [[nodiscard]] friend constexpr std::enable_if_t< | |
| !std::is_void_v<T2> && | |
| expected_detail::is_equality_result_convertible_to_bool_v<T, T2> && | |
| expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, | |
| bool> | |
| operator==(const expected& x, const expected<T2, E2>& y) noexcept( | |
| expected_detail::is_nothrow_equality_result_convertible_to_bool_v<T, T2> && | |
| expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>) { | |
| if (x.has_value() != y.has_value()) { | |
| return false; | |
| } else if (x.has_value()) { | |
| return *x == *y; | |
| } else { | |
| return x.error() == y.error(); | |
| } | |
| } | |
| #if ICEBERG_EXPECTED_CPLUSPLUS < 202'002L | |
| template <class T2, class E2> | |
| [[nodiscard]] friend constexpr std::enable_if_t< | |
| !std::is_void_v<T2> && | |
| expected_detail::is_equality_result_convertible_to_bool_v<T, T2> && | |
| expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, | |
| bool> operator!=(const expected& x, | |
| const expected<T2, E2>& y) noexcept(noexcept(x == y)) { | |
| return !(x == y); | |
| } | |
| #endif | |
| template <class T2> | |
| [[nodiscard]] friend constexpr std::enable_if_t< | |
| !expected_detail::is_specialization_v<T2, iceberg::expected> && | |
| expected_detail::is_equality_result_convertible_to_bool_v<T, T2>, | |
| bool> | |
| operator==(const expected& x, const T2& v) noexcept( | |
| expected_detail::is_nothrow_equality_result_convertible_to_bool_v<T, T2>) { | |
| if (x.has_value()) { | |
| return *x == v; | |
| } else { | |
| return false; | |
| } | |
| } | |
| #if ICEBERG_EXPECTED_CPLUSPLUS < 202'002L | |
| template <class T2> | |
| [[nodiscard]] friend constexpr std::enable_if_t< | |
| !expected_detail::is_specialization_v<T2, iceberg::expected> && | |
| expected_detail::is_equality_result_convertible_to_bool_v<T, T2>, | |
| bool> operator!=(const expected& x, const T2& v) noexcept(noexcept(x == v)) { | |
| return !(x == v); | |
| } | |
| #endif | |
| template <class E2> | |
| [[nodiscard]] friend constexpr std::enable_if_t< | |
| expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> | |
| operator==(const expected& x, const unexpected<E2>& e) noexcept( | |
| expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>) { | |
| if (x.has_value()) { | |
| return false; | |
| } else { | |
| return x.error() == e.error(); | |
| } | |
| } | |
| #if ICEBERG_EXPECTED_CPLUSPLUS < 202'002L | |
| template <class E2> | |
| [[nodiscard]] friend constexpr std::enable_if_t< | |
| expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> | |
| operator!=(const expected& x, const unexpected<E2>& e) noexcept(noexcept(x == e)) { | |
| return !(x == e); | |
| } | |
| #endif | |
| }; | |
| // standalone swap for non-void value type | |
| template <class T, class E, | |
| std::enable_if_t<!std::is_void_v<T> && std::is_move_constructible_v<T> && | |
| std::is_swappable_v<T> && std::is_move_constructible_v<E> && | |
| std::is_swappable_v<E> && | |
| (std::is_nothrow_move_constructible_v<T> || | |
| std::is_nothrow_move_constructible_v<E>)>* = nullptr> | |
| constexpr void swap(expected<T, E>& lhs, | |
| expected<T, E>& rhs) noexcept(noexcept(lhs.swap(rhs))) { | |
| lhs.swap(rhs); | |
| } | |
| template <class E> | |
| class ICEBERG_EXPORT [[nodiscard]] | |
| expected<void, E> : private expected_detail::move_assign_base<void, E>, | |
| private expected_detail::default_ctor_base<void, E> { | |
| static_assert(expected_detail::is_error_type_valid_v<E>); | |
| using T = void; | |
| constexpr E* errptr() noexcept { return std::addressof(this->m_unexpect); } | |
| constexpr const E* errptr() const noexcept { return std::addressof(this->m_unexpect); } | |
| constexpr void val() noexcept {} | |
| constexpr E& err() noexcept { return this->m_unexpect; } | |
| constexpr void val() const noexcept {} | |
| constexpr const E& err() const noexcept { return this->m_unexpect; } | |
| using impl_base = expected_detail::move_assign_base<T, E>; | |
| using ctor_base = expected_detail::default_ctor_base<T, E>; | |
| public: | |
| typedef T value_type; | |
| typedef E error_type; | |
| typedef unexpected<E> unexpected_type; | |
| template <class U> | |
| using rebind = expected<U, error_type>; | |
| constexpr expected() = default; | |
| constexpr expected(const expected& rhs) = default; | |
| constexpr expected(expected&& rhs) = default; | |
| template < | |
| class U, class G, | |
| std::enable_if_t<!std::is_same_v<expected<U, G>, expected>>* = nullptr, | |
| std::enable_if_t<std::is_convertible_v<const G&, E>>* = nullptr, | |
| expected_detail::enable_from_other_void_expected_t<E, U, G, const G&>* = nullptr> | |
| constexpr expected(const expected<U, G>& rhs) noexcept( | |
| std::is_nothrow_constructible_v<E, const G&>) | |
| : impl_base(expected_detail::construct_from_expected, rhs), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| template < | |
| class U, class G, | |
| std::enable_if_t<!std::is_same_v<expected<U, G>, expected>>* = nullptr, | |
| std::enable_if_t<!std::is_convertible_v<const G&, E>>* = nullptr, | |
| expected_detail::enable_from_other_void_expected_t<E, U, G, const G&>* = nullptr> | |
| constexpr explicit expected(const expected<U, G>& rhs) noexcept( | |
| std::is_nothrow_constructible_v<E, const G&>) | |
| : impl_base(expected_detail::construct_from_expected, rhs), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| template <class U, class G, | |
| std::enable_if_t<!std::is_same_v<expected<U, G>, expected>>* = nullptr, | |
| std::enable_if_t<std::is_convertible_v<G, E>>* = nullptr, | |
| expected_detail::enable_from_other_void_expected_t<E, U, G, G>* = nullptr> | |
| constexpr expected(expected<U, G>&& rhs) noexcept(std::is_nothrow_constructible_v<E, G>) | |
| : impl_base(expected_detail::construct_from_expected, std::move(rhs)), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| template <class U, class G, | |
| std::enable_if_t<!std::is_same_v<expected<U, G>, expected>>* = nullptr, | |
| std::enable_if_t<!std::is_convertible_v<G, E>>* = nullptr, | |
| expected_detail::enable_from_other_void_expected_t<E, U, G, G>* = nullptr> | |
| constexpr explicit expected(expected<U, G>&& rhs) noexcept( | |
| std::is_nothrow_constructible_v<E, G>) | |
| : impl_base(expected_detail::construct_from_expected, std::move(rhs)), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| #if !ICEBERG_CXX20_COMPAT && defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L | |
| // iceberg: library code built as C++23 still spells some errors std::unexpected. | |
| template <class G, std::enable_if_t<std::is_constructible_v<E, const G&>>* = nullptr> | |
| constexpr explicit(!std::is_convertible_v<const G&, E>) expected( | |
| const std::unexpected<G>& e) noexcept(std::is_nothrow_constructible_v<E, const G&>) | |
| : expected(unexpect, e.error()) {} | |
| template <class G, std::enable_if_t<std::is_constructible_v<E, G>>* = nullptr> | |
| constexpr explicit(!std::is_convertible_v<G, E>) | |
| expected(std::unexpected<G>&& e) noexcept(std::is_nothrow_constructible_v<E, G>) | |
| : expected(unexpect, std::move(e.error())) {} | |
| #endif | |
| // constructors for unexpected<G> | |
| // explicit const unexpected<G> & | |
| template <class G, std::enable_if_t<!std::is_convertible_v<const G&, E>>* = nullptr, | |
| std::enable_if_t<std::is_constructible_v<E, const G&>>* = nullptr> | |
| explicit constexpr expected(const unexpected<G>& e) noexcept( | |
| std::is_nothrow_constructible_v<E, const G&>) | |
| : impl_base(unexpect, e.error()), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // implicit const unexpected<G> & | |
| template <class G, std::enable_if_t<std::is_convertible_v<const G&, E>>* = nullptr, | |
| std::enable_if_t<std::is_constructible_v<E, const G&>>* = nullptr> | |
| constexpr expected(unexpected<G> const& e) noexcept( | |
| std::is_nothrow_constructible_v<E, const G&>) | |
| : impl_base(unexpect, e.error()), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // explicit unexpected<G> && | |
| template <class G, std::enable_if_t<!std::is_convertible_v<G, E>>* = nullptr, | |
| std::enable_if_t<std::is_constructible_v<E, G>>* = nullptr> | |
| explicit constexpr expected(unexpected<G>&& e) noexcept( | |
| std::is_nothrow_constructible_v<E, G>) | |
| : impl_base(unexpect, std::move(e.error())), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // implicit unexpected<G> && | |
| template <class G, std::enable_if_t<std::is_convertible_v<G, E>>* = nullptr, | |
| std::enable_if_t<std::is_constructible_v<E, G>>* = nullptr> | |
| constexpr expected(unexpected<G>&& e) noexcept(std::is_nothrow_constructible_v<E, G>) | |
| : impl_base(unexpect, std::move(e.error())), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // expected(std::in_place_t) | |
| constexpr explicit expected(std::in_place_t) noexcept | |
| : impl_base(std::in_place), ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // template<class... Args> | |
| // expected(unexpect_t, Args &&...) | |
| template <class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, Args...>>* = nullptr> | |
| constexpr explicit expected(unexpect_t, Args&&... args) noexcept( | |
| std::is_nothrow_constructible_v<E, Args...>) | |
| : impl_base(unexpect, std::forward<Args>(args)...), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // template<class U, class... Args> | |
| // expected(unexpect_t, std::initializer_list<U>, Args &&...) | |
| template <class U, class... Args, | |
| std::enable_if_t<std::is_constructible_v<E, std::initializer_list<U>&, | |
| Args...>>* = nullptr> | |
| constexpr explicit expected( | |
| unexpect_t, std::initializer_list<U> il, | |
| Args&&... args) noexcept(std::is_nothrow_constructible_v<E, | |
| std::initializer_list<U>&, | |
| Args...>) | |
| : impl_base(unexpect, il, std::forward<Args>(args)...), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| // helper ctor for transform_error() | |
| template <class Fn, class... Args> | |
| constexpr explicit expected( | |
| expected_detail::construct_with_invoke_result_t tag1, unexpect_t tag2, Fn&& func, | |
| Args&&... args) noexcept(noexcept(static_cast<E>(std::invoke(std::forward<Fn>(func), | |
| std::forward<Args>( | |
| args)...)))) | |
| : impl_base(tag1, tag2, std::forward<Fn>(func), std::forward<Args>(args)...), | |
| ctor_base(expected_detail::default_constructor_tag{}) {} | |
| expected& operator=(const expected& rhs) = default; | |
| expected& operator=(expected&& rhs) = default; | |
| template <class G, class GF = const G&, | |
| std::enable_if_t<std::is_constructible_v<E, GF> && | |
| std::is_assignable_v<E&, GF>>* = nullptr> | |
| constexpr expected& operator=(const unexpected<G>& rhs) noexcept( | |
| std::is_nothrow_constructible_v<E, GF> && std::is_nothrow_assignable_v<E&, GF>) { | |
| if (has_value()) { | |
| expected_detail::construct_at(errptr(), std::forward<GF>(rhs.error())); | |
| this->m_has_val = false; | |
| } else { | |
| error() = std::forward<GF>(rhs.error()); | |
| } | |
| return *this; | |
| } | |
| template <class G, class GF = G, | |
| std::enable_if_t<std::is_constructible_v<E, GF> && | |
| std::is_assignable_v<E&, GF>>* = nullptr> | |
| constexpr expected& operator=(unexpected<G>&& rhs) noexcept( | |
| std::is_nothrow_constructible_v<E, GF> && std::is_nothrow_assignable_v<E&, GF>) { | |
| if (has_value()) { | |
| expected_detail::construct_at(errptr(), std::forward<GF>(rhs.error())); | |
| this->m_has_val = false; | |
| } else { | |
| error() = std::forward<GF>(rhs.error()); | |
| } | |
| return *this; | |
| } | |
| constexpr void emplace() { | |
| if (!has_value()) { | |
| if constexpr (!std::is_trivially_destructible_v<E>) { | |
| err().~E(); | |
| } | |
| this->m_has_val = true; | |
| } | |
| } | |
| template <class OE = E> | |
| constexpr std::enable_if_t<std::is_swappable_v<OE> && std::is_move_constructible_v<OE>> | |
| swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v<E> && | |
| std::is_nothrow_swappable_v<E>) { | |
| using std::swap; | |
| if (this->m_has_val && rhs.m_has_val) { | |
| // do nothing | |
| } else if (this->m_has_val) { | |
| expected_detail::construct_at(std::addressof(error()), std::move(rhs.error())); | |
| if constexpr (!std::is_trivially_destructible_v<E>) { | |
| rhs.error().~E(); | |
| } | |
| this->m_has_val = false; | |
| rhs.m_has_val = true; | |
| } else if (rhs.m_has_val) { | |
| expected_detail::construct_at(std::addressof(rhs.error()), std::move(error())); | |
| if constexpr (!std::is_trivially_destructible_v<E>) { | |
| error().~E(); | |
| } | |
| this->m_has_val = true; | |
| rhs.m_has_val = false; | |
| } else { | |
| swap(error(), rhs.error()); // ADL | |
| } | |
| } | |
| constexpr void operator*() const noexcept { return val(); } | |
| constexpr bool has_value() const noexcept { return this->m_has_val; } | |
| constexpr explicit operator bool() const noexcept { return this->m_has_val; } | |
| constexpr void value() const& { | |
| static_assert(std::is_copy_constructible_v<E>, | |
| "E must be copy constructible, by LWG-3940"); | |
| if (!has_value()) ICEBERG_EXPECTED_THROW(bad_expected_access(err())); | |
| } | |
| constexpr void value() && { | |
| static_assert(std::is_copy_constructible_v<E>, | |
| "E must be copy constructible, by LWG-3940"); | |
| static_assert(std::is_move_constructible_v<E>, | |
| "E must be move constructible, by LWG-3940"); | |
| if (!has_value()) ICEBERG_EXPECTED_THROW(bad_expected_access(std::move(err()))); | |
| } | |
| constexpr const E& error() const& noexcept { return err(); } | |
| constexpr E& error() & noexcept { return err(); } | |
| constexpr const E&& error() const&& noexcept { return std::move(err()); } | |
| constexpr E&& error() && noexcept { return std::move(err()); } | |
| template <class G = E> | |
| constexpr E error_or(G&& v) const& noexcept( | |
| std::is_nothrow_copy_constructible_v<E> && | |
| expected_detail::is_nothrow_convertible_v<G, E>) { | |
| static_assert(std::is_copy_constructible_v<E>, "E must be copy-constructible"); | |
| static_assert(std::is_convertible_v<G, E>, "is_convertible_v<G, E> must be true"); | |
| if (this->m_has_val) { | |
| return static_cast<E>(std::forward<G>(v)); | |
| } else { | |
| return this->m_unexpect; | |
| } | |
| } | |
| template <class G = E> | |
| constexpr E error_or(G&& v) && noexcept( | |
| std::is_nothrow_move_constructible_v<E> && | |
| expected_detail::is_nothrow_convertible_v<G, E>) { | |
| static_assert(std::is_move_constructible_v<E>, "E must be move-constructible"); | |
| static_assert(std::is_convertible_v<G, E>, "is_convertible_v<G, E> must be true"); | |
| if (this->m_has_val) { | |
| return static_cast<E>(std::forward<G>(v)); | |
| } else { | |
| return std::move(this->m_unexpect); | |
| } | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, GE&>>* = nullptr> | |
| constexpr auto and_then(F&& f) & { | |
| using U = expected_detail::remove_cvref_t<std::invoke_result_t<F>>; | |
| static_assert(expected_detail::is_specialization_v<U, iceberg::expected>, | |
| "U (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename U::error_type, E>, | |
| "The error type must be the same after calling the F"); | |
| if (has_value()) | |
| return std::invoke(std::forward<F>(f)); | |
| else | |
| return U(unexpect, error()); | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, const GE&>>* = nullptr> | |
| constexpr auto and_then(F&& f) const& { | |
| using U = expected_detail::remove_cvref_t<std::invoke_result_t<F>>; | |
| static_assert(expected_detail::is_specialization_v<U, iceberg::expected>, | |
| "U (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename U::error_type, E>, | |
| "The error type must be the same after calling the F"); | |
| if (has_value()) | |
| return std::invoke(std::forward<F>(f)); | |
| else | |
| return U(unexpect, error()); | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, GE&&>>* = nullptr> | |
| constexpr auto and_then(F&& f) && { | |
| using U = expected_detail::remove_cvref_t<std::invoke_result_t<F>>; | |
| static_assert(expected_detail::is_specialization_v<U, iceberg::expected>, | |
| "U (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename U::error_type, E>, | |
| "The error type must be the same after calling the F"); | |
| if (has_value()) | |
| return std::invoke(std::forward<F>(f)); | |
| else | |
| return U(unexpect, std::move(error())); | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, const GE>>* = nullptr> | |
| constexpr auto and_then(F&& f) const&& { | |
| using U = expected_detail::remove_cvref_t<std::invoke_result_t<F>>; | |
| static_assert(expected_detail::is_specialization_v<U, iceberg::expected>, | |
| "U (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename U::error_type, E>, | |
| "The error type must be the same after calling the F"); | |
| if (has_value()) | |
| return std::invoke(std::forward<F>(f)); | |
| else | |
| return U(unexpect, std::move(error())); | |
| } | |
| template <class F> | |
| constexpr auto or_else(F&& f) & { | |
| using G = expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype(error())>>; | |
| static_assert(expected_detail::is_specialization_v<G, iceberg::expected>, | |
| "G (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename G::value_type, T>, | |
| "The value type must be the same after calling the F"); | |
| if (has_value()) | |
| return G(); | |
| else | |
| return std::invoke(std::forward<F>(f), error()); | |
| } | |
| template <class F> | |
| constexpr auto or_else(F&& f) const& { | |
| using G = expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype(error())>>; | |
| static_assert(expected_detail::is_specialization_v<G, iceberg::expected>, | |
| "G (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename G::value_type, T>, | |
| "The value type must be the same after calling the F"); | |
| if (has_value()) | |
| return G(); | |
| else | |
| return std::invoke(std::forward<F>(f), error()); | |
| } | |
| template <class F> | |
| constexpr auto or_else(F&& f) && { | |
| using G = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(error()))>>; | |
| static_assert(expected_detail::is_specialization_v<G, iceberg::expected>, | |
| "G (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename G::value_type, T>, | |
| "The value type must be the same after calling the F"); | |
| if (has_value()) | |
| return G(); | |
| else | |
| return std::invoke(std::forward<F>(f), std::move(error())); | |
| } | |
| template <class F> | |
| constexpr auto or_else(F&& f) const&& { | |
| using G = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(error()))>>; | |
| static_assert(expected_detail::is_specialization_v<G, iceberg::expected>, | |
| "G (return type of F) must be specialization of expected"); | |
| static_assert(std::is_same_v<typename G::value_type, T>, | |
| "The value type must be the same after calling the F"); | |
| if (has_value()) | |
| return G(); | |
| else | |
| return std::invoke(std::forward<F>(f), std::move(error())); | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, GE&>>* = nullptr> | |
| constexpr auto transform(F&& f) & { | |
| using U = expected_detail::remove_cvref_t<std::invoke_result_t<F>>; | |
| static_assert(expected_detail::is_value_type_valid_v<U>, | |
| "U must be a valid type for expected<U, E>"); | |
| // FIXME another constraint needed here | |
| if (!has_value()) { | |
| return expected<U, E>(unexpect, error()); | |
| } else { | |
| if constexpr (std::is_void_v<U>) { | |
| std::invoke(std::forward<F>(f)); | |
| return expected<U, E>{}; | |
| } else { | |
| return expected<U, E>(expected_detail::construct_with_invoke_result_t{}, | |
| std::forward<F>(f)); | |
| } | |
| } | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, const GE&>>* = nullptr> | |
| constexpr auto transform(F&& f) const& { | |
| using U = expected_detail::remove_cvref_t<std::invoke_result_t<F>>; | |
| static_assert(expected_detail::is_value_type_valid_v<U>, | |
| "U must be a valid type for expected<U, E>"); | |
| // FIXME another constraint needed here | |
| if (!has_value()) { | |
| return expected<U, E>(unexpect, error()); | |
| } else { | |
| if constexpr (std::is_void_v<U>) { | |
| std::invoke(std::forward<F>(f)); | |
| return expected<U, E>{}; | |
| } else { | |
| return expected<U, E>(expected_detail::construct_with_invoke_result_t{}, | |
| std::forward<F>(f)); | |
| } | |
| } | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, GE&&>>* = nullptr> | |
| constexpr auto transform(F&& f) && { | |
| using U = expected_detail::remove_cvref_t<std::invoke_result_t<F>>; | |
| static_assert(expected_detail::is_value_type_valid_v<U>, | |
| "U must be a valid type for expected<U, E>"); | |
| // FIXME another constraint needed here | |
| if (!has_value()) { | |
| return expected<U, E>(unexpect, std::move(error())); | |
| } else { | |
| if constexpr (std::is_void_v<U>) { | |
| std::invoke(std::forward<F>(f)); | |
| return expected<U, E>{}; | |
| } else { | |
| return expected<U, E>(expected_detail::construct_with_invoke_result_t{}, | |
| std::forward<F>(f)); | |
| } | |
| } | |
| } | |
| template <class F, class GE = E, | |
| std::enable_if_t<std::is_constructible_v<GE, const GE>>* = nullptr> | |
| constexpr auto transform(F&& f) const&& { | |
| using U = expected_detail::remove_cvref_t<std::invoke_result_t<F>>; | |
| static_assert(expected_detail::is_value_type_valid_v<U>, | |
| "U must be a valid type for expected<U, E>"); | |
| // FIXME another constraint needed here | |
| if (!has_value()) { | |
| return expected<U, E>(unexpect, std::move(error())); | |
| } else { | |
| if constexpr (std::is_void_v<U>) { | |
| std::invoke(std::forward<F>(f)); | |
| return expected<U, E>{}; | |
| } else { | |
| return expected<U, E>(expected_detail::construct_with_invoke_result_t{}, | |
| std::forward<F>(f)); | |
| } | |
| } | |
| } | |
| template <class F> | |
| constexpr auto transform_error(F&& f) & { | |
| using G = expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype(error())>>; | |
| static_assert(expected_detail::is_error_type_valid_v<G>, | |
| "G must be a valid type for expected<void, G>"); | |
| // FIXME another constraint needed here | |
| if (has_value()) { | |
| return expected<T, G>(); | |
| } else { | |
| return expected<T, G>(expected_detail::construct_with_invoke_result_t{}, unexpect, | |
| std::forward<F>(f), error()); | |
| } | |
| } | |
| template <class F> | |
| constexpr auto transform_error(F&& f) const& { | |
| using G = expected_detail::remove_cvref_t<std::invoke_result_t<F, decltype(error())>>; | |
| static_assert(expected_detail::is_error_type_valid_v<G>, | |
| "G must be a valid type for expected<void, G>"); | |
| // FIXME another constraint needed here | |
| if (has_value()) { | |
| return expected<T, G>(); | |
| } else { | |
| return expected<T, G>(expected_detail::construct_with_invoke_result_t{}, unexpect, | |
| std::forward<F>(f), error()); | |
| } | |
| } | |
| template <class F> | |
| constexpr auto transform_error(F&& f) && { | |
| using G = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(error()))>>; | |
| static_assert(expected_detail::is_error_type_valid_v<G>, | |
| "G must be a valid type for expected<void, G>"); | |
| // FIXME another constraint needed here | |
| if (has_value()) { | |
| return expected<T, G>(); | |
| } else { | |
| return expected<T, G>(expected_detail::construct_with_invoke_result_t{}, unexpect, | |
| std::forward<F>(f), std::move(error())); | |
| } | |
| } | |
| template <class F> | |
| constexpr auto transform_error(F&& f) const&& { | |
| using G = expected_detail::remove_cvref_t< | |
| std::invoke_result_t<F, decltype(std::move(error()))>>; | |
| static_assert(expected_detail::is_error_type_valid_v<G>, | |
| "G must be a valid type for expected<void, G>"); | |
| // FIXME another constraint needed here | |
| if (has_value()) { | |
| return expected<T, G>(); | |
| } else { | |
| return expected<T, G>(expected_detail::construct_with_invoke_result_t{}, unexpect, | |
| std::forward<F>(f), std::move(error())); | |
| } | |
| } | |
| template <class T2, class E2> | |
| [[nodiscard]] friend constexpr std::enable_if_t< | |
| std::is_void_v<T2> && | |
| expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, | |
| bool> | |
| operator==(const expected& x, const expected<T2, E2>& y) noexcept( | |
| expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>) { | |
| if (x.has_value() != y.has_value()) { | |
| return false; | |
| } else if (x.has_value()) { | |
| return true; | |
| } else { | |
| return x.error() == y.error(); | |
| } | |
| } | |
| #if ICEBERG_EXPECTED_CPLUSPLUS < 202'002L | |
| template <class T2, class E2> | |
| [[nodiscard]] friend constexpr std::enable_if_t< | |
| std::is_void_v<T2> && | |
| expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, | |
| bool> operator!=(const expected& x, | |
| const expected<T2, E2>& y) noexcept(noexcept(x == y)) { | |
| return !(x == y); | |
| } | |
| #endif | |
| template <class E2> | |
| [[nodiscard]] friend constexpr std::enable_if_t< | |
| expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> | |
| operator==(const expected& x, const unexpected<E2>& e) noexcept( | |
| expected_detail::is_nothrow_equality_result_convertible_to_bool_v<E, E2>) { | |
| if (x.has_value()) { | |
| return false; | |
| } else { | |
| return x.error() == e.error(); | |
| } | |
| } | |
| #if ICEBERG_EXPECTED_CPLUSPLUS < 202'002L | |
| template <class E2> | |
| [[nodiscard]] friend constexpr std::enable_if_t< | |
| expected_detail::is_equality_result_convertible_to_bool_v<E, E2>, bool> | |
| operator!=(const expected& x, const unexpected<E2>& e) noexcept(noexcept(x == e)) { | |
| return !(x == e); | |
| } | |
| #endif | |
| }; | |
| #if ICEBERG_EXPECTED_CPLUSPLUS < 202'002L | |
| // Deduce the expected operand to reject conversions exposed by MSVC's permissive C++17 | |
| // hidden-friend lookup. | |
| template <class T, class E, class T2> | |
| [[nodiscard]] constexpr std::enable_if_t< | |
| !std::is_void_v<T> && !expected_detail::is_specialization_v<T2, iceberg::expected> && | |
| expected_detail::is_equality_result_convertible_to_bool_v<T, T2>, | |
| bool> | |
| operator==(const T2& v, const expected<T, E>& x) noexcept(noexcept(x == v)) { | |
| return x == v; | |
| } | |
| template <class T, class E, class T2> | |
| [[nodiscard]] constexpr std::enable_if_t< | |
| !std::is_void_v<T> && !expected_detail::is_specialization_v<T2, iceberg::expected> && | |
| expected_detail::is_equality_result_convertible_to_bool_v<T, T2>, | |
| bool> | |
| operator!=(const T2& v, const expected<T, E>& x) noexcept(noexcept(x == v)) { | |
| return x != v; | |
| } | |
| template < | |
| class T, class E, class E2, | |
| std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>>* = | |
| nullptr> | |
| [[nodiscard]] constexpr bool operator==(const unexpected<E2>& e, | |
| const expected<T, E>& x) noexcept(noexcept(x == | |
| e)) { | |
| return x == e; | |
| } | |
| template < | |
| class T, class E, class E2, | |
| std::enable_if_t<expected_detail::is_equality_result_convertible_to_bool_v<E, E2>>* = | |
| nullptr> | |
| [[nodiscard]] constexpr bool operator!=(const unexpected<E2>& e, | |
| const expected<T, E>& x) noexcept(noexcept(x == | |
| e)) { | |
| return x != e; | |
| } | |
| #endif | |
| // standalone swap for void value type | |
| template <class E, std::enable_if_t<std::is_move_constructible_v<E> && | |
| std::is_swappable_v<E>>* = nullptr> | |
| constexpr void swap(expected<void, E>& lhs, | |
| expected<void, E>& rhs) noexcept(noexcept(lhs.swap(rhs))) { | |
| lhs.swap(rhs); | |
| } | |
| } // namespace iceberg | |
| // NOLINTEND |