This is an automated email from the ASF dual-hosted git repository.

fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new cd82335  backport c++23 std::expected (#40)
cd82335 is described below

commit cd823357b181c19504bc7b7270aa6e8e8cc7ed2a
Author: Junwang Zhao <[email protected]>
AuthorDate: Wed Feb 26 19:03:22 2025 +0800

    backport c++23 std::expected (#40)
    
    After studied [0], [1] and [2], I'm inclined to backport [0] to
    iceberg-cpp, the main reason is that [0] has exactly the same APIs as
    std::expected, [1] provide some extra member functions like `map` and
    `map_error`, [2] has different API names like `then` and `thenOrThrow`.
    
    We want users to use iceberg::expected the same way as std::expected,
    and we will replace iceberg::expected with std::expected when we decide
    to move to c++23 some day, by backporting [0], we can facilitate a
    smoother transition process.
    
    We discussed about `Exceptions vs Expected` in #14, while backporting
    [0], I had the feeling we shouldn't choose one over the other, we can
    use both approaches effectively.
    
    expected provide monadic operations like `and_then`, `transform`,
    `or_else`, `transform_error`, let us do method chaining, which is a good
    sign.
    
    [0] https://github.com/zeus-cpp/expected
    [1] https://github.com/TartanLlama/expected
    [2] https://github.com/facebook/folly/blob/main/folly/Expected.h
    [3] https://en.cppreference.com/w/cpp/utility/expected
    
    ---------
    
    Signed-off-by: Junwang Zhao <[email protected]>
---
 .github/.licenserc.yaml          |    1 +
 .github/workflows/cpp-linter.yml |    1 +
 LICENSE                          |   28 +
 NOTICE                           |    4 +
 src/iceberg/expected.h           | 2359 ++++++++++++++++++++++++++++++++++++++
 test/core/CMakeLists.txt         |    6 +
 test/core/expected_test.cc       |  627 ++++++++++
 7 files changed, 3026 insertions(+)

diff --git a/.github/.licenserc.yaml b/.github/.licenserc.yaml
index 75545a5..c657a03 100644
--- a/.github/.licenserc.yaml
+++ b/.github/.licenserc.yaml
@@ -11,5 +11,6 @@ header:
     - '.github/**'
     - 'LICENSE'
     - 'NOTICE'
+    - 'src/iceberg/expected.h'
 
   comment: on-failure
diff --git a/.github/workflows/cpp-linter.yml b/.github/workflows/cpp-linter.yml
index cc10555..da58dca 100644
--- a/.github/workflows/cpp-linter.yml
+++ b/.github/workflows/cpp-linter.yml
@@ -44,6 +44,7 @@ jobs:
         with:
           style: file
           tidy-checks: ''
+          version: 19
           files-changed-only: true
           lines-changed-only: true
           thread-comments: true
diff --git a/LICENSE b/LICENSE
index 261eeb9..1c87025 100644
--- a/LICENSE
+++ b/LICENSE
@@ -199,3 +199,31 @@
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
+
+--------------------------------------------------------------------------------
+
+The file src/iceberg/expected.h contains code adapted from
+
+https://github.com/zeus-cpp/expected
+
+with the following license (MIT)
+
+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.
diff --git a/NOTICE b/NOTICE
index e466e8c..c71e81d 100644
--- a/NOTICE
+++ b/NOTICE
@@ -3,3 +3,7 @@ Copyright 2024-2025 The Apache Software Foundation
 
 This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
+
+This product includes code from zeus-cpp
+ * Copyright (c) 2024 zeus-cpp
+ * https://github.com/zeus-cpp/expected
diff --git a/src/iceberg/expected.h b/src/iceberg/expected.h
new file mode 100644
index 0000000..9d28d38
--- /dev/null
+++ b/src/iceberg/expected.h
@@ -0,0 +1,2359 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <exception>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "iceberg/iceberg_export.h"
+
+// NOLINTBEGIN
+
+namespace iceberg {
+
+namespace expected_detail {
+
+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>()));
+
+}  // 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.m_val == rhs.error())) {
+    return lhs.m_val == rhs.error();
+  }
+  template <class E2>
+  friend constexpr bool operator!=(
+      const unexpected& lhs,
+      const unexpected<E2>& rhs) noexcept(noexcept(lhs.m_val != rhs.error())) {
+    return lhs.m_val != 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:
+  const char* what() const noexcept override { return "Bad expected access"; }
+
+ protected:
+  bad_expected_access() = default;
+  bad_expected_access(const bad_expected_access&) = default;
+  bad_expected_access(bad_expected_access&&) = default;
+  bad_expected_access& operator=(const bad_expected_access&) = default;
+  bad_expected_access& operator=(bad_expected_access&&) = 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<std::remove_cvref_t<U>, std::in_place_t> &&
+    !std::is_same_v<expected<T, E>, std::remove_cvref_t<U>> &&
+    !expected_detail::is_specialization_v<std::remove_cvref_t<U>, unexpected> 
&&
+    (!std::is_same_v<std::remove_cv_t<T>, bool> ||  // LWG-3836
+     !expected_detail::is_specialization_v<std::remove_cvref_t<U>, expected>)>;
+
+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 {
+
+struct no_init_t {
+  explicit no_init_t() = default;
+};
+inline constexpr no_init_t no_init{};
+
+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) {}
+  constexpr ~ReinitGuard() noexcept {
+    if (_target) {
+      std::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();
+    }
+    std::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();
+    }
+    std::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)};
+    std::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;
+
+  constexpr storage_base() noexcept(noexcept(T{})) : m_val(T{}), 
m_has_val(true) {}
+  constexpr storage_base(no_init_t) noexcept : m_no_init(), m_has_val(false) {}
+
+  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_no_init;
+  };
+  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;
+
+  constexpr storage_base() noexcept(noexcept(T{})) : m_val(T{}), 
m_has_val(true) {}
+  constexpr storage_base(no_init_t) noexcept : m_no_init(), m_has_val(false) {}
+
+  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) {}
+
+  constexpr ~storage_base() noexcept {
+    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_no_init;
+  };
+  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) {}
+
+  constexpr storage_base(no_init_t) noexcept : m_val(), m_has_val(false) {}
+
+  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) {}
+
+  constexpr storage_base(no_init_t) noexcept : m_val(), m_has_val(false) {}
+
+  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) {}
+
+  constexpr ~storage_base() noexcept {
+    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;
+
+  template <class... Args>
+  constexpr void construct(Args&&... args) noexcept(
+      std::is_nothrow_constructible_v<T, Args...>) {
+    std::construct_at(&this->m_val, std::forward<Args>(args)...);
+    this->m_has_val = true;
+  }
+
+  template <class Rhs>
+  constexpr void construct_with(Rhs&& rhs) noexcept(
+      std::is_nothrow_constructible_v<T, Rhs>) {
+    std::construct_at(&this->m_val, std::forward<Rhs>(rhs).get());
+    this->m_has_val = true;
+  }
+
+  template <class... Args>
+  constexpr void construct_error(Args&&... args) noexcept(
+      std::is_nothrow_constructible_v<E, Args...>) {
+    std::construct_at(&this->m_unexpect, std::forward<Args>(args)...);
+    this->m_has_val = false;
+  }
+
+  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 void construct() noexcept { this->m_has_val = true; }
+
+  // This function doesn't use its argument, but needs it so that code in
+  // levels above this can work independently of whether T is void
+  template <class Rhs>
+  constexpr void construct_with(Rhs&&) noexcept {
+    this->m_has_val = true;
+  }
+
+  template <class... Args>
+  constexpr void construct_error(Args&&... args)  //
+      noexcept(std::is_nothrow_constructible_v<E, Args...>) {
+    std::construct_at(&this->m_unexpect, std::forward<Args>(args)...);
+    this->m_has_val = false;
+  }
+
+  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>(no_init) {
+    if (rhs.m_has_val) {
+      this->construct_with(rhs);
+    } else {
+      this->construct_error(rhs.geterr());
+    }
+  }
+
+  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>(no_init) {
+    if (rhs.m_has_val) {
+      this->construct_with(std::move(rhs));
+    } else {
+      this->construct_error(std::move(rhs.geterr()));
+    }
+  }
+
+  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) {
+      std::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) {
+      std::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_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:
+  using value_type = T;
+  using error_type = E;
+  using unexpected_type = unexpected<E>;
+
+  // 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_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&>)
+      : ctor_base(expected_detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct(*rhs);
+    } else {
+      this->construct_error(rhs.error());
+    }
+  }
+
+  // explicit const reference
+  template <class U, class G,
+            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&>)
+      : ctor_base(expected_detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct(*rhs);
+    } else {
+      this->construct_error(rhs.error());
+    }
+  }
+
+  // implicit rvalue
+  template <class U, class G,
+            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>)
+      : ctor_base(expected_detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct(std::move(*rhs));
+    } else {
+      this->construct_error(std::move(rhs.error()));
+    }
+  }
+
+  // explicit rvalue
+  template <class U,  //
+            class G,  //
+            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>)
+      : ctor_base(expected_detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct(std::move(*rhs));
+    } else {
+      this->construct_error(std::move(rhs.error()));
+    }
+  }
+
+  // template <class U = T>
+  //     expected(U &&)
+
+  // implicit
+  template <class U = 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 = 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)) {}
+
+  // 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 = T,                                                             
         //
+      std::enable_if_t<                                                        
         //
+          !std::is_same_v<expected, std::remove_cvref_t<U>> &&                 
         //
+          !expected_detail::is_specialization_v<std::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 *std::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 *std::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>) {
+          std::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)};
+          std::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();
+        }
+        std::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)};
+        std::construct_at(std::addressof(this->error()), 
std::move(rhs.error()));
+        guard._target = nullptr;
+
+        if constexpr (!std::is_trivially_destructible_v<E>) {
+          rhs.error().~E();
+        }
+        std::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()) 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()) 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()) 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()) 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>
+  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>
+  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 = std::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 = std::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 =
+        std::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 =
+        std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 =
+        std::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 =
+        std::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 = std::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 = std::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 = std::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 = std::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>, bool> 
operator==(
+      const expected& x,
+      const expected<T2, E2>& y) noexcept(noexcept(*x == *y) &&
+                                          noexcept(x.error() == y.error())) {
+    if (x.has_value() != y.has_value()) {
+      return false;
+    } else if (x.has_value()) {
+      return *x == *y;
+    } else {
+      return x.error() == y.error();
+    }
+  }
+
+  template <class T2>
+  [[nodiscard]] friend constexpr bool operator==(const expected& x, const T2& 
v)  //
+      noexcept(noexcept(*x == v)) {
+    if (x.has_value()) {
+      return static_cast<bool>(*x == v);
+    } else {
+      return false;
+    }
+  }
+
+  template <class E2>
+  [[nodiscard]] friend constexpr bool operator==(
+      const expected& x,
+      const unexpected<E2>& e) noexcept(noexcept(x.error() == e.error())) {
+    if (x.has_value()) {
+      return false;
+    } else {
+      return static_cast<bool>(x.error() == e.error());
+    }
+  }
+};
+
+// 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:
+  using value_type = T;
+  using error_type = E;
+  using unexpected_type = unexpected<E>;
+
+  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_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&>)
+      : ctor_base(expected_detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct();
+    } else {
+      this->construct_error(rhs.error());
+    }
+  }
+
+  template <
+      class U,                                                           //
+      class G,                                                           //
+      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&>)
+      : ctor_base(expected_detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct();
+    } else {
+      this->construct_error(rhs.error());
+    }
+  }
+
+  template <class U,                                                   //
+            class G,                                                   //
+            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>)
+      : ctor_base(expected_detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct();
+    } else {
+      this->construct_error(std::move(rhs.error()));
+    }
+  }
+
+  template <class U,                                                    //
+            class G,                                                    //
+            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>)
+      : ctor_base(expected_detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct();
+    } else {
+      this->construct_error(std::move(rhs.error()));
+    }
+  }
+
+  // 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()) {
+      std::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()) {
+      std::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) {
+      std::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) {
+      std::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()) 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()) 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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 = std::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>, bool> 
operator==(
+      const expected& x,
+      const expected<T2, E2>& y) noexcept(noexcept(x.error() == y.error())) {
+    if (x.has_value() != y.has_value()) {
+      return false;
+    } else {
+      return x.has_value() || static_cast<bool>(x.error() == y.error());
+    }
+  }
+
+  template <class E2>
+  [[nodiscard]] friend constexpr bool operator==(
+      const expected& x,
+      const unexpected<E2>& e) noexcept(noexcept(x.error() == e.error())) {
+    if (x.has_value()) {
+      return false;
+    } else {
+      return static_cast<bool>(x.error() == e.error());
+    }
+  }
+};
+
+// 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>
+ICEBERG_EXPORT constexpr void swap(
+    expected<void, E>& lhs, expected<void, E>& rhs) 
noexcept(noexcept(lhs.swap(rhs))) {
+  lhs.swap(rhs);
+}
+
+}  // namespace iceberg
+
+// NOLINTEND
diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt
index 81c0973..23b0844 100644
--- a/test/core/CMakeLists.txt
+++ b/test/core/CMakeLists.txt
@@ -29,3 +29,9 @@ if(ICEBERG_AVRO)
   target_include_directories(avro_unittest PRIVATE "${ICEBERG_INCLUDES}")
   add_test(NAME avro_unittest COMMAND avro_unittest)
 endif()
+
+add_executable(expected_test)
+target_sources(expected_test PRIVATE expected_test.cc)
+target_link_libraries(expected_test PRIVATE iceberg_static GTest::gtest_main)
+target_include_directories(expected_test PRIVATE "${ICEBERG_INCLUDES}")
+add_test(NAME expected_test COMMAND expected_test)
diff --git a/test/core/expected_test.cc b/test/core/expected_test.cc
new file mode 100644
index 0000000..411a3bb
--- /dev/null
+++ b/test/core/expected_test.cc
@@ -0,0 +1,627 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "iceberg/expected.h"
+
+#include <gtest/gtest.h>
+
+TEST(ExpectedTest, DefaultCons) {
+  iceberg::expected<int, int> e1;
+  iceberg::expected<int, int> e2{};
+  EXPECT_EQ(e1.value(), 0);
+  EXPECT_EQ(e2.value(), 0);
+}
+
+TEST(ExpectedTest, InplaceCons) {
+  iceberg::expected<void, int> e1{iceberg::unexpect};
+  iceberg::expected<void, int> e2{iceberg::unexpect, 42};
+  iceberg::expected<int, int> e3{iceberg::unexpect};
+  iceberg::expected<int, int> e4{iceberg::unexpect, 42};
+
+  EXPECT_EQ(e1.error(), 0);
+  EXPECT_EQ(e2.error(), 42);
+  EXPECT_EQ(e3.error(), 0);
+  EXPECT_EQ(e4.error(), 42);
+}
+
+TEST(ExpectedTest, ImplicitConversion) {
+  iceberg::expected<int, int> e = 42;
+
+  EXPECT_EQ(e.value(), 42);
+}
+
+TEST(ExpectedTest, ExplicitConversion) {
+  iceberg::expected<std::string, int> e1 = iceberg::unexpected<int>(42);
+  iceberg::expected<std::string, int> e2{iceberg::unexpect, 42};
+  EXPECT_EQ(e1.error(), 42);
+  EXPECT_EQ(e2.error(), 42);
+}
+
+TEST(ExpectedTest, ImplicitConversionFrom) {
+  EXPECT_FALSE((std::is_convertible_v<iceberg::expected<std::string, int>, 
int>));
+}
+
+TEST(ExpectedTest, ExplictVoidE) {
+  iceberg::expected<void, int> e1 = {};
+  iceberg::expected<void, int> e2 = iceberg::unexpected{42};
+
+  EXPECT_TRUE(e1.has_value());
+  EXPECT_TRUE((std::is_same_v<decltype(e1.value()), void>));
+  EXPECT_EQ(e1.error(), 0);
+  EXPECT_EQ(e2.error(), 42);
+}
+
+TEST(ExpectedTest, Emplace) {
+  struct S {
+    S(int i, double d) noexcept : i(i), d(d) {}
+    int i;
+    double d;
+  };
+  iceberg::expected<S, std::nullopt_t> e{iceberg::unexpect, std::nullopt};
+  e.emplace(42, 3.14);
+  EXPECT_EQ(e.value().i, 42);
+  EXPECT_EQ(e.value().d, 3.14);
+}
+
+TEST(ExpectedTest, Equality) {
+  iceberg::expected<int, int> const v1;
+  iceberg::expected<int, int> const v2{42};
+  iceberg::expected<int, int> const v3 = 42;
+  iceberg::expected<int, int> const e1{iceberg::unexpect, 0};
+  iceberg::expected<int, int> const e2{iceberg::unexpect, 42};
+  iceberg::expected<int, int> const e3 = iceberg::unexpected(42);
+  EXPECT_TRUE(v1 != v2);
+  EXPECT_TRUE(v2 == v3);
+  EXPECT_TRUE(v1 != e1);
+  EXPECT_TRUE(v1 != e2);
+  EXPECT_TRUE(e1 != e2);
+  EXPECT_TRUE(e2 == e3);
+  EXPECT_TRUE(e1 != v1);
+  EXPECT_TRUE(e1 != v2);
+}
+
+TEST(ExpectedTest, UnexpectedError) {
+  auto e = iceberg::unexpected(42);
+  EXPECT_EQ(e.error(), 42);
+}
+
+TEST(ExpectedTest, BadExpectedAccess) {
+  iceberg::expected<void, int> e = iceberg::unexpected(42);
+  EXPECT_THROW(
+      {
+        try {
+          e.value();
+        } catch (const iceberg::bad_expected_access<int>& ex) {
+          // and this tests that it has the correct message
+          EXPECT_EQ(ex.error(), 42);
+          throw;
+        }
+      },
+      iceberg::bad_expected_access<int>);
+}
+
+TEST(ExpectedTest, ExpectedVoidEEmplace) {
+  iceberg::expected<void, int> e1{std::in_place};
+  iceberg::expected<void, int> e2;
+  e2.emplace();
+}
+
+TEST(ExpectedTest, Swap) {
+  iceberg::expected<int, int> e1{42};
+  iceberg::expected<int, int> e2{1'337};
+
+  e1.swap(e2);
+  EXPECT_EQ(e1.value(), 1'337);
+  EXPECT_EQ(e2.value(), 42);
+
+  swap(e1, e2);
+  EXPECT_EQ(e1.value(), 42);
+  EXPECT_EQ(e2.value(), 1'337);
+}
+
+TEST(ExpectedTest, CopyInitialization) {
+  {
+    iceberg::expected<std::string, int> e = {};
+    EXPECT_TRUE(e);
+  }
+}
+
+TEST(ExpectedTest, BraceContructedValue) {
+  {  // non-void
+    iceberg::expected<int, int> e{{}};
+    EXPECT_TRUE(e);
+  }
+#if !defined(__GNUC__)
+  {  // std::string
+    iceberg::expected<std::string, int> e{{}};
+    EXPECT_TRUE(e);
+  }
+#endif
+}
+
+TEST(ExpectedTest, LWG3836) {
+  struct BaseError {};
+  struct DerivedError : BaseError {};
+
+  iceberg::expected<bool, DerivedError> e1(false);
+  iceberg::expected<bool, BaseError> e2(e1);
+  EXPECT_TRUE(!e2.value());
+
+  iceberg::expected<void, DerivedError> e3{};
+  iceberg::expected<void, BaseError> e4(e3);
+  EXPECT_TRUE(e4.has_value());
+}
+
+TEST(ExpectedTest, Assignment) {
+  // non-void
+  {
+    iceberg::expected<int, int> e1{iceberg::unexpect, 1'337};
+    iceberg::expected<int, int> e2{42};
+
+    e1 = e2;
+    EXPECT_TRUE(e1);
+  }
+
+  // void
+  {
+    iceberg::expected<void, int> e1{iceberg::unexpect, 1'337};
+    iceberg::expected<void, int> e2{};
+
+    e1 = e2;
+    EXPECT_TRUE(e1);
+  }
+}
+
+TEST(ExpectedTest, Move) {
+  {
+    struct user_provided_move {
+      user_provided_move() = default;
+      user_provided_move(user_provided_move&&) noexcept {}
+    };
+
+    struct defaulted_move {
+      defaulted_move() = default;
+      defaulted_move(defaulted_move&&) = default;
+    };
+
+    {
+      using Expected = iceberg::expected<user_provided_move, int>;
+      Expected t1;
+      Expected tm1(std::move(t1));
+      EXPECT_TRUE(tm1);
+    }
+    {
+      using Expected = iceberg::expected<defaulted_move, int>;
+      Expected t2;
+      Expected tm2(std::move(t2));  // should compile
+      EXPECT_TRUE(tm2);
+    }
+  }
+
+  {
+    class MoveOnly {
+     public:
+      MoveOnly() = default;
+
+      // Non-copyable
+      MoveOnly(const MoveOnly&) = delete;
+      MoveOnly& operator=(const MoveOnly&) = delete;
+
+      // Movable trivially
+      MoveOnly(MoveOnly&&) = default;
+      MoveOnly& operator=(MoveOnly&&) = default;
+    };
+
+    {
+      using Expected = iceberg::expected<MoveOnly, int>;
+      Expected a{};
+      Expected b = std::move(a);  // should compile
+    }
+  }
+}
+
+namespace {
+
+template <bool ShouldEqual, typename T, typename U>
+constexpr bool EqualityTester(T const& lhs, U const& rhs) {
+  return (lhs == rhs) == ShouldEqual && (lhs != rhs) != ShouldEqual &&
+         (rhs == lhs) == ShouldEqual && (rhs != lhs) != ShouldEqual;
+}
+
+struct Type1 {
+  std::string value;
+
+  explicit Type1(std::string v) : value(std::move(v)) {}
+  explicit Type1(const char* v) : value(v) {}
+  Type1(Type1 const&) = delete;
+  Type1& operator=(Type1 const&) = delete;
+  Type1(Type1&& other) = default;
+  Type1& operator=(Type1&&) = default;
+  ~Type1() = default;
+
+  bool operator==(Type1 const& rhs) const { return value == rhs.value; }
+};
+
+struct Type2 {
+  std::string value;
+
+  explicit Type2(std::string v) : value(std::move(v)) {}
+  explicit Type2(const char* v) : value(v) {}
+  Type2(Type2 const&) = delete;
+  Type2& operator=(Type2 const&) = delete;
+  Type2(Type2&& other) = default;
+  Type2& operator=(Type2&&) = default;
+  ~Type2() = default;
+
+  bool operator==(Type2 const& rhs) const { return value == rhs.value; }
+};
+
+inline bool operator==(Type1 const& lhs, Type2 const& rhs) {
+  return lhs.value == rhs.value;
+}
+
+inline bool operator==(Type2 const& lhs, Type1 const& rhs) { return rhs == 
lhs; }
+
+}  // namespace
+
+TEST(ExpectedTest, EqualityTest) {
+  // expected<T, E> (T is not void)
+  {
+    using T = Type1;
+    using E = Type2;
+    using Expected = iceberg::expected<T, E>;
+
+    // compare with same type expected<T, E>
+    {
+      Expected const value1("value1");
+      Expected const value2("value2");
+      Expected const value1Copy("value1");
+      Expected const error1 = iceberg::unexpected<E>("error1");
+      Expected const error2 = iceberg::unexpected<E>("error2");
+      Expected const error1Copy = iceberg::unexpected<E>("error1");
+
+      EXPECT_TRUE(EqualityTester<true>(value1, value1Copy));
+      EXPECT_TRUE(EqualityTester<false>(value1, value2));
+      EXPECT_TRUE(EqualityTester<true>(error1, error1Copy));
+      EXPECT_TRUE(EqualityTester<false>(error1, error2));
+      EXPECT_TRUE(EqualityTester<false>(value1, error1));
+    }
+
+    // compare with different type expected<T2, E2>
+    {
+      using T2 = Type2;
+      using E2 = Type1;
+      ASSERT_FALSE((std::is_same_v<T, T2>));
+      ASSERT_FALSE((std::is_same_v<E, E2>));
+      using Expected2 = iceberg::expected<T2, E2>;
+
+      Expected const value1("value1");
+      Expected2 const value2("value2");
+      Expected2 const value1Same("value1");
+      Expected const error1 = iceberg::unexpected<E>("error1");
+      Expected2 const error2 = iceberg::unexpected<E2>("error2");
+      Expected2 const error1Same = iceberg::unexpected<E2>("error1");
+
+      EXPECT_TRUE(EqualityTester<true>(value1, value1Same));
+      EXPECT_TRUE(EqualityTester<false>(value1, value2));
+      EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
+      EXPECT_TRUE(EqualityTester<false>(error1, error2));
+      EXPECT_TRUE(EqualityTester<false>(value1, error1));
+    }
+
+    // compare with same value type T
+    {
+      Expected const value1("value1");
+      Expected const error1 = iceberg::unexpected<E>("error1");
+      T const value2("value2");
+      T const value1Same("value1");
+
+      EXPECT_TRUE(EqualityTester<true>(value1, value1Same));
+      EXPECT_TRUE(EqualityTester<false>(value1, value2));
+      EXPECT_TRUE(EqualityTester<false>(error1, value2));
+    }
+
+    // compare with different value type T2
+    {
+      using T2 = Type2;
+      ASSERT_FALSE((std::is_same_v<T, T2>));
+
+      Expected const value1("value1");
+      Expected const error1 = iceberg::unexpected<E>("error1");
+      T2 const value2("value2");
+      T2 const value1Same("value1");
+
+      EXPECT_TRUE(EqualityTester<true>(value1, value1Same));
+      EXPECT_TRUE(EqualityTester<false>(value1, value2));
+      EXPECT_TRUE(EqualityTester<false>(error1, value2));
+    }
+
+    // compare with same error type unexpected<E>
+    {
+      Expected const value1("value1");
+      Expected const error1 = iceberg::unexpected<E>("error1");
+      auto const error2 = iceberg::unexpected<E>("error2");
+      auto const error1Same = iceberg::unexpected<E>("error1");
+
+      EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
+      EXPECT_TRUE(EqualityTester<false>(error1, error2));
+      EXPECT_TRUE(EqualityTester<false>(value1, error2));
+    }
+
+    // compare with different error type unexpected<E2>
+    {
+      using E2 = Type1;
+      ASSERT_FALSE((std::is_same_v<E, E2>));
+
+      Expected const value1("value1");
+      Expected const error1 = iceberg::unexpected<E>("error1");
+      auto const error2 = iceberg::unexpected<E2>("error2");
+      auto const error1Same = iceberg::unexpected<E2>("error1");
+
+      EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
+      EXPECT_TRUE(EqualityTester<false>(error1, error2));
+      EXPECT_TRUE(EqualityTester<false>(value1, error2));
+    }
+  }
+
+  // expected<void, E>
+  {
+    using E = Type1;
+    using Expected = iceberg::expected<void, E>;
+
+    // compare with same type expected<void, E>
+    {
+      Expected const value1;
+      Expected const value2;
+      Expected const error1 = iceberg::unexpected<E>("error1");
+      Expected const error2 = iceberg::unexpected<E>("error2");
+      Expected const error1Copy = iceberg::unexpected<E>("error1");
+
+      EXPECT_TRUE(EqualityTester<true>(value1, value2));
+      EXPECT_TRUE(EqualityTester<true>(error1, error1Copy));
+      EXPECT_TRUE(EqualityTester<false>(error1, error2));
+      EXPECT_TRUE(EqualityTester<false>(value1, error1));
+    }
+
+    // compare with different type expected<void, E2>
+    {
+      using E2 = Type2;
+      ASSERT_FALSE((std::is_same_v<E, E2>));
+      using Expected2 = iceberg::expected<void, E2>;
+
+      Expected const value1;
+      Expected2 const value2;
+      Expected const error1 = iceberg::unexpected<E>("error1");
+      Expected2 const error2 = iceberg::unexpected<E2>("error2");
+      Expected2 const error1Same = iceberg::unexpected<E2>("error1");
+
+      EXPECT_TRUE(EqualityTester<true>(value1, value2));
+      EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
+      EXPECT_TRUE(EqualityTester<false>(error1, error2));
+      EXPECT_TRUE(EqualityTester<false>(value1, error1));
+    }
+
+    // compare with same error type unexpected<E>
+    {
+      Expected const value1;
+      Expected const error1 = iceberg::unexpected<E>("error1");
+      auto const error2 = iceberg::unexpected<E>("error2");
+      auto const error1Same = iceberg::unexpected<E>("error1");
+
+      EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
+      EXPECT_TRUE(EqualityTester<false>(error1, error2));
+      EXPECT_TRUE(EqualityTester<false>(value1, error2));
+    }
+
+    // compare with different error type unexpected<E2>
+    {
+      using E2 = Type2;
+      ASSERT_FALSE((std::is_same_v<E, E2>));
+
+      Expected const value1;
+      Expected const error1 = iceberg::unexpected<E>("error1");
+      auto const error2 = iceberg::unexpected<E2>("error2");
+      auto const error1Same = iceberg::unexpected<E2>("error1");
+
+      EXPECT_TRUE(EqualityTester<true>(error1, error1Same));
+      EXPECT_TRUE(EqualityTester<false>(error1, error2));
+      EXPECT_TRUE(EqualityTester<false>(value1, error2));
+    }
+  }
+}
+
+TEST(ExpectedTest, AndThen) {
+  using T = std::string;
+  using Expected = iceberg::expected<int, T>;
+
+  {  // non-void
+    using Expected2 = iceberg::expected<double, T>;
+
+    Expected e = 42;
+
+    auto const newVal = e.and_then([](int x) { return Expected2{x * 2}; });
+
+    EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, 
Expected2>));
+  }
+  {  // void
+    using Expected2 = iceberg::expected<void, T>;
+
+    Expected e = 42;
+
+    auto const newVal = e.and_then([](int) -> Expected2 { return Expected2{}; 
});
+
+    EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, 
Expected2>));
+  }
+}
+
+TEST(ExpectedTest, VoidAndThen) {
+  using T = std::string;
+  using Expected = iceberg::expected<void, T>;
+
+  {  // non-void
+    using Expected2 = iceberg::expected<double, T>;
+
+    Expected e{};
+
+    auto const newVal = e.and_then([]() { return Expected2{}; });
+
+    EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, 
Expected2>));
+  }
+  {  // void
+    using Expected2 = iceberg::expected<void, T>;
+
+    Expected e{};
+
+    auto const newVal = e.and_then([]() -> Expected2 { return Expected2{}; });
+
+    EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, 
Expected2>));
+  }
+}
+
+TEST(ExpectedTest, OrElse) {
+  using T = std::string;
+  using Expected = iceberg::expected<T, int>;
+
+  {  // non-void
+    using Expected2 = iceberg::expected<T, double>;
+
+    Expected e{};
+
+    auto const newVal = e.or_else([](auto&&) { return Expected2{}; });
+
+    EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, 
Expected2>));
+  }
+}
+
+TEST(ExpectedTest, VoidOrElse) {
+  using Expected = iceberg::expected<void, int>;
+
+  {  // void
+    using Expected2 = iceberg::expected<void, double>;
+
+    Expected e{};
+
+    auto const newVal = e.or_else([](auto&&) { return Expected2{}; });
+
+    EXPECT_TRUE(newVal.has_value());
+    EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, 
Expected2>));
+  }
+}
+
+TEST(ExpectedTest, Transform) {
+  using Expected = iceberg::expected<int, int>;
+
+  {
+    Expected e{42};
+
+    auto const newVal = e.transform([](int n) { return n + 100; });
+
+    EXPECT_TRUE((std::is_same_v<std::remove_cv_t<decltype(newVal)>, 
Expected>));
+    EXPECT_EQ(newVal.value(), 142);
+  }
+
+  {
+    Expected e = iceberg::unexpected{-1};
+    auto const newVal = e.transform_error([](int n) { return n + 100; });
+
+    EXPECT_EQ(newVal.error(), 99);
+  }
+}
+
+namespace {
+struct FromType {};
+
+enum class NoThrowConvertible {
+  Yes,
+  No,
+};
+
+template <NoThrowConvertible N>
+struct ToType;
+
+template <>
+struct ToType<NoThrowConvertible::Yes> {
+  ToType() = default;
+  ToType(ToType const&) = default;
+  ToType(ToType&&) = default;
+
+  explicit ToType(FromType const&) noexcept {}
+};
+
+template <>
+struct ToType<NoThrowConvertible::No> {
+  ToType() = default;
+  ToType(ToType const&) = default;
+  ToType(ToType&&) = default;
+
+  explicit ToType(FromType const&) noexcept(false) {}
+};
+
+}  // namespace
+
+TEST(ExpectedTest, ValueOrNoexcept) {
+  {  // noexcept(true)
+    using T = ToType<NoThrowConvertible::Yes>;
+    using E = int;
+    using Expected = iceberg::expected<T, E>;
+    Expected e;
+    EXPECT_TRUE(noexcept(e.value_or(FromType{})));
+  }
+  {  // noexcept(false)
+    using T = ToType<NoThrowConvertible::No>;
+    using E = int;
+    using Expected = iceberg::expected<T, E>;
+    Expected e;
+    EXPECT_FALSE(noexcept(e.value_or(FromType{})));
+  }
+}
+
+TEST(ExpectedTest, ErrorOrNoexcept) {
+  {  // noexcept(true)
+    using T = int;
+    using E = ToType<NoThrowConvertible::Yes>;
+    using Expected = iceberg::expected<T, E>;
+    Expected e;
+    EXPECT_TRUE(noexcept(e.error_or(FromType{})));
+  }
+  {  // noexcept(false)
+    using T = int;
+    using E = ToType<NoThrowConvertible::No>;
+    using Expected = iceberg::expected<T, E>;
+    Expected e;
+    EXPECT_FALSE(noexcept(e.error_or(FromType{})));
+  }
+}
+
+TEST(ExpectedTest, VoidTErrorOrNoxcept) {
+  {  // noexcept(true)
+    using T = void;
+    using E = ToType<NoThrowConvertible::Yes>;
+    using Expected = iceberg::expected<T, E>;
+    Expected e;
+    EXPECT_TRUE(noexcept(e.error_or(FromType{})));
+  }
+  {  // noexcept(false)
+    using T = void;
+    using E = ToType<NoThrowConvertible::No>;
+    using Expected = iceberg::expected<T, E>;
+    Expected e;
+    EXPECT_FALSE(noexcept(e.error_or(FromType{})));
+  }
+}

Reply via email to