lidavidm commented on code in PR #40: URL: https://github.com/apache/iceberg-cpp/pull/40#discussion_r1929975364
########## src/iceberg/expected.h: ########## @@ -0,0 +1,2332 @@ +/* + * 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. + */ + +#pragma once + +#include <exception> +#include <functional> +#include <type_traits> +#include <utility> + +#include "iceberg/iceberg_export.h" + +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 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: + virtual 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 expected : private expected_detail::move_assign_base<T, E>, + private expected_detail::default_ctor_base<T, E> { Review Comment: Oops, yes, `[[nodiscard]]`. But then that means we can't switch to std::expected down the line, because we've diverged. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: issues-unsubscr...@iceberg.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: issues-unsubscr...@iceberg.apache.org For additional commands, e-mail: issues-h...@iceberg.apache.org