From d6f11fd1a0a2feef87404836bb338f861d58198d Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 20:45:40 -0400 Subject: [PATCH 01/15] feat: add unexpected partial specialization (pointer storage) Add a partial specialization unexpected that stores a pointer instead of a value, plus the reference_constructs_from_temporary_v trait it needs. This is the uniform error-storage building block: a union cannot hold a reference member directly, but it can hold unexpected (just a pointer), so every expected<> union can store unexpected for both value and reference E. Purely additive over the primary unexpected template. --- include/beman/expected/unexpected.hpp | 83 +++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/include/beman/expected/unexpected.hpp b/include/beman/expected/unexpected.hpp index b3654f4..4870520 100644 --- a/include/beman/expected/unexpected.hpp +++ b/include/beman/expected/unexpected.hpp @@ -5,6 +5,7 @@ #ifndef BEMAN_EXPECTED_INCLUDED_FROM_INTERFACE_UNIT #include + #include #include #include #endif @@ -27,6 +28,18 @@ template struct is_unexpected_specialization : std::false_type {}; template struct is_unexpected_specialization> : std::true_type {}; + +// reference_constructs_from_temporary / reference_converts_from_temporary +#ifdef __cpp_lib_reference_from_temporary +using std::reference_constructs_from_temporary_v; +using std::reference_converts_from_temporary_v; +#elif __has_builtin(__reference_constructs_from_temporary) +template +inline constexpr bool reference_constructs_from_temporary_v = __reference_constructs_from_temporary(T, U); +template +inline constexpr bool reference_converts_from_temporary_v = __reference_converts_from_temporary(T, U); +#endif + } // namespace detail // [expected.unexpected] @@ -92,6 +105,76 @@ class unexpected { template unexpected(E) -> unexpected; +// [expected.unexpected], partial specialization for reference E +// Stores a pointer to the referenced object; keeps expected<> from needing a +// separate set of specializations just to hold a reference error type. +template +class unexpected { + static_assert(std::is_object_v, + "unexpected: referenced type must be an object type (not void, reference, or function)"); + static_assert(!std::is_array_v, "unexpected: referenced type must not be an array type"); + static_assert(!detail::is_unexpected_specialization>::value, + "unexpected: referenced type must not be a specialization of unexpected"); + // Deliberately no cv-qualification static_assert: unlike the primary template, the referenced + // type may be cv-qualified (e.g. unexpected). + + public: + constexpr unexpected(const unexpected&) = default; + constexpr unexpected(unexpected&&) = default; + + // Binds E& directly to the referenced object; deleted below when G would bind to a temporary. + template + requires(!std::is_same_v, unexpected> && + !std::is_same_v, std::in_place_t> && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr explicit unexpected(G&& e) noexcept + : ptr_(std::addressof(static_cast(std::forward(e)))) {} + + // Deleted: binding would dangle (G materializes a temporary) + template + requires(detail::reference_constructs_from_temporary_v) + constexpr unexpected(G&&) = delete; + + // Deleted catch-all: neither constructible nor a dangling case + template + requires(!std::is_same_v, unexpected> && + !std::is_same_v, std::in_place_t> && !std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr unexpected(G&&) = delete; + + // Single-argument in_place_t overload — lets expected's uniform + // construct_at(addressof(unex_), std::in_place, args...) pattern work whether E is a + // reference or not. Naturally restricted to arity 1: there is no variadic overload here, + // and expected only ever calls this when is_constructible_v already holds. + template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr explicit unexpected(std::in_place_t, G&& e) noexcept + : ptr_(std::addressof(static_cast(std::forward(e)))) {} + + template + requires(detail::reference_constructs_from_temporary_v) + constexpr unexpected(std::in_place_t, G&&) = delete; + + constexpr unexpected& operator=(const unexpected&) = default; + constexpr unexpected& operator=(unexpected&&) = default; + + // Single overload — shallow-const, matching expected's existing error() style: + // there is nothing to move out of a pointer to an external object. + constexpr E& error() const noexcept { return *ptr_; } + + constexpr void swap(unexpected& other) noexcept { std::swap(ptr_, other.ptr_); } + + template + friend constexpr bool operator==(const unexpected& x, const unexpected& y) { + return *x.ptr_ == y.error(); + } + + friend constexpr void swap(unexpected& x, unexpected& y) noexcept { x.swap(y); } + + private: + E* ptr_; +}; + } // namespace expected } // namespace beman From 25c360e7e5424a41abcc0f4ce8a35c4cc03cd654 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 20:48:32 -0400 Subject: [PATCH 02/15] feat: expected stores unexpected, enabling reference error types Change the primary expected union to hold unexpected instead of a raw E. Because unexpected stores a pointer, this makes E an lvalue reference valid with no extra specialization: expected is just the primary template. Adds the reference-E converting constructors and the rebinding operator= from unexpected; deletes the dangling value-G cases. Uses the move-then-deref idiom (*std::move(rhs), std::move(x).error()) throughout so converting from a reference-holding expected copies the referent instead of stealing it. expected is now well-formed, so its negative test is dropped; the error-reference test group is added. --- include/beman/expected/expected.hpp | 525 ++++++++++----- tests/beman/expected/CMakeLists.txt | 16 +- tests/beman/expected/expected_e_ref_fail.cpp | 6 - tests/beman/expected/expected_ref_e.test.cpp | 597 ++++++++++++++++++ ...d_ref_e_and_then_wrong_error_type_fail.cpp | 10 + .../expected_ref_e_assign_unexpected_fail.cpp | 12 + .../expected_ref_e_const_lvalue_fail.cpp | 10 + ...d_ref_e_construct_from_unexpected_fail.cpp | 11 + .../expected/expected_ref_e_e_array_fail.cpp | 8 + ...ed_ref_e_or_else_wrong_value_type_fail.cpp | 10 + .../expected/expected_ref_e_t_array_fail.cpp | 5 + .../expected_ref_e_t_inplace_fail.cpp | 6 + .../expected_ref_e_t_unexpect_fail.cpp | 5 + .../expected_ref_e_t_unexpected_fail.cpp | 5 + .../expected_ref_e_temporary_error_fail.cpp | 9 + ..._ref_e_transform_error_ref_result_fail.cpp | 10 + tests/beman/expected/testing/types.hpp | 131 ++++ 17 files changed, 1201 insertions(+), 175 deletions(-) delete mode 100644 tests/beman/expected/expected_e_ref_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e.test.cpp create mode 100644 tests/beman/expected/expected_ref_e_and_then_wrong_error_type_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_assign_unexpected_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_const_lvalue_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_construct_from_unexpected_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_e_array_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_or_else_wrong_value_type_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_t_array_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_t_inplace_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_t_unexpect_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_t_unexpected_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_temporary_error_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_transform_error_ref_result_fail.cpp create mode 100644 tests/beman/expected/testing/types.hpp diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index e14de53..a9ffa2f 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -83,6 +83,20 @@ constexpr void reinit_expected(NewVal& newval, CurVal& oldval, Args&&... args) { } } +// reference_constructs_from_temporary_v / reference_converts_from_temporary_v now live in +// unexpected.hpp's detail namespace (beman::expected::detail), since unexpected needs them +// too and unexpected.hpp must not depend on expected.hpp. + +// unexpect_dangles_v: true iff constructing expected's error in place from Args... +// would bind a reference E to a temporary. False whenever E is not a reference, or arity != 1 +// (a reference can only ever bind from a single argument), so it never affects the value-E path. +template +inline constexpr bool unexpect_dangles_v = false; + +template +inline constexpr bool unexpect_dangles_v = + std::is_reference_v && reference_constructs_from_temporary_v; + } // namespace detail template @@ -107,14 +121,17 @@ constexpr bool converts_from_any_cvref = std::disjunction_v class expected { static_assert(!std::is_reference_v, "T must not be a reference (use expected specialization)"); - static_assert(!std::is_reference_v, "E must not be a reference (use expected specialization)"); - static_assert(!std::is_void_v, "E must not be void"); + static_assert(!std::is_rvalue_reference_v, "E must not be an rvalue reference"); + static_assert(!std::is_void_v>, "E must not be void"); static_assert(!std::is_same_v, std::in_place_t>, "T must not be in_place_t"); static_assert(!std::is_same_v, unexpect_t>, "T must not be unexpect_t"); static_assert(!std::is_array_v, "T must not be an array type"); static_assert(!detail::is_unexpected_specialization>::value, "T must not be a specialization of unexpected"); - static_assert(!std::is_array_v, "E must not be an array type"); + static_assert(!std::is_array_v>, "E must not be an array type"); + + private: + using error_value_type = std::remove_cv_t>; public: using value_type = T; @@ -134,28 +151,31 @@ class expected { // Copy constructor (trivial path) constexpr expected(const expected&) - requires(std::is_trivially_copy_constructible_v && std::is_trivially_copy_constructible_v) + requires(std::is_trivially_copy_constructible_v && std::is_trivially_copy_constructible_v>) = default; // Copy constructor (non-trivial path) constexpr expected(const expected& rhs) - requires(std::is_copy_constructible_v && std::is_copy_constructible_v && - !(std::is_trivially_copy_constructible_v && std::is_trivially_copy_constructible_v)); + requires(std::is_copy_constructible_v && std::is_copy_constructible_v> && + !(std::is_trivially_copy_constructible_v && + std::is_trivially_copy_constructible_v>)); // Move constructor (trivial path) constexpr expected(expected&&) noexcept - requires(std::is_trivially_move_constructible_v && std::is_trivially_move_constructible_v) + requires(std::is_trivially_move_constructible_v && std::is_trivially_move_constructible_v>) = default; // Move constructor (non-trivial path) constexpr expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_move_constructible_v) - requires(std::is_move_constructible_v && std::is_move_constructible_v && - !(std::is_trivially_move_constructible_v && std::is_trivially_move_constructible_v)); + std::is_nothrow_move_constructible_v>) + requires(std::is_move_constructible_v && std::is_move_constructible_v> && + !(std::is_trivially_move_constructible_v && + std::is_trivially_move_constructible_v>)); - // Converting copy constructor from expected + // Converting copy constructor from expected — value-E path template - requires(std::is_constructible_v && std::is_constructible_v && + requires(!std::is_reference_v && std::is_constructible_v && + std::is_constructible_v && (std::is_same_v> || !detail::converts_from_any_cvref>) && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && @@ -164,9 +184,9 @@ class expected { constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) expected(const expected& rhs); - // Converting move constructor from expected + // Converting move constructor from expected — value-E path template - requires(std::is_constructible_v && std::is_constructible_v && + requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && (std::is_same_v> || !detail::converts_from_any_cvref>) && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && @@ -174,6 +194,20 @@ class expected { !std::is_constructible_v, const expected &&>) constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) expected(expected&& rhs); + // Converting constructor from expected — reference-E path: only accepts sources + // whose error type G is itself a reference convertible to E. + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + std::is_convertible_v) + constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) + expected(const expected& rhs); + + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + std::is_convertible_v) + constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) + expected(expected&& rhs); + // Constructor from value U&& template > requires(!std::is_same_v, std::in_place_t> && @@ -182,20 +216,41 @@ class expected { !detail::is_unexpected_specialization>::value && (!std::is_same_v> || !detail::is_expected_specialization>::value)) - constexpr explicit(!std::is_convertible_v) expected(U&& v) : has_val_(true) { - std::construct_at(std::addressof(val_), std::forward(v)); - } + constexpr explicit(!std::is_convertible_v) expected(U&& v); - // Constructor from unexpected const& + // Constructor from unexpected const& / && — value-E path template - requires std::is_constructible_v + requires(!std::is_reference_v && std::is_constructible_v) constexpr explicit(!std::is_convertible_v) expected(const unexpected& e); - // Constructor from unexpected&& template - requires std::is_constructible_v + requires(!std::is_reference_v && std::is_constructible_v) constexpr explicit(!std::is_convertible_v) expected(unexpected&& e); + // Constructor from unexpected — reference-E path. Allowed only when G is itself a reference, + // i.e. the source unexpected holds a reference to an external object, so binding E& to e.error() + // cannot dangle regardless of the source's value category. No const_cast is needed. + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) noexcept; + + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) noexcept; + + // Deleted for reference E with value G: the referent lives inside the unexpected object, so + // binding E& to it would dangle once a temporary source is destroyed. Use (unexpect, lvalue), or + // an unexpected holding an external object, instead. + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected(const unexpected&) = delete; + + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected(unexpected&&) = delete; + // In-place constructor for value template requires std::is_constructible_v @@ -208,9 +263,21 @@ class expected { // In-place constructor for error template - requires std::is_constructible_v + requires(std::is_constructible_v && !detail::unexpect_dangles_v) constexpr explicit expected(unexpect_t, Args&&... args); + // Deleted: single argument would bind E& to a temporary — dangling prevention + template + requires(detail::unexpect_dangles_v) + constexpr expected(unexpect_t, Args&&...) = delete; + + // Deleted catch-all: reference E, argument neither constructible nor a dangling case + // (e.g. binding a non-const E& from a const lvalue). + template + requires(std::is_reference_v && !std::is_constructible_v && + !detail::unexpect_dangles_v) + constexpr expected(unexpect_t, Args&&...) = delete; + // In-place constructor for error with initializer_list template requires std::is_constructible_v&, Args...> @@ -221,11 +288,11 @@ class expected { // ------------------------------------------------------------------------- constexpr ~expected() - requires(std::is_trivially_destructible_v && std::is_trivially_destructible_v) + requires(std::is_trivially_destructible_v && std::is_trivially_destructible_v>) = default; constexpr ~expected() - requires(!(std::is_trivially_destructible_v && std::is_trivially_destructible_v)); + requires(!(std::is_trivially_destructible_v && std::is_trivially_destructible_v>)); // ------------------------------------------------------------------------- // [expected.object.assign] Assignment @@ -234,37 +301,40 @@ class expected { // Copy assignment (trivial path) constexpr expected& operator=(const expected&) requires(std::is_trivially_copy_constructible_v && std::is_trivially_copy_assignable_v && - std::is_trivially_destructible_v && std::is_trivially_copy_constructible_v && - std::is_trivially_copy_assignable_v && std::is_trivially_destructible_v) + std::is_trivially_destructible_v && std::is_trivially_copy_constructible_v> && + std::is_trivially_copy_assignable_v> && + std::is_trivially_destructible_v>) = default; // Copy assignment (non-trivial path) constexpr expected& operator=(const expected& rhs) - requires(std::is_copy_constructible_v && std::is_copy_assignable_v && std::is_copy_constructible_v && - std::is_copy_assignable_v && - (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v) && + requires(std::is_copy_constructible_v && std::is_copy_assignable_v && + std::is_copy_constructible_v> && std::is_copy_assignable_v> && + (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v>) && !(std::is_trivially_copy_constructible_v && std::is_trivially_copy_assignable_v && - std::is_trivially_destructible_v && std::is_trivially_copy_constructible_v && - std::is_trivially_copy_assignable_v && std::is_trivially_destructible_v)); + std::is_trivially_destructible_v && std::is_trivially_copy_constructible_v> && + std::is_trivially_copy_assignable_v> && + std::is_trivially_destructible_v>)); // Move assignment (trivial path) constexpr expected& operator=(expected&&) noexcept requires(std::is_trivially_move_constructible_v && std::is_trivially_move_assignable_v && - std::is_trivially_destructible_v && std::is_trivially_move_constructible_v && - std::is_trivially_move_assignable_v && std::is_trivially_destructible_v) + std::is_trivially_destructible_v && std::is_trivially_move_constructible_v> && + std::is_trivially_move_assignable_v> && + std::is_trivially_destructible_v>) = default; // Move assignment (non-trivial path) - constexpr expected& operator=(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_move_assignable_v && - std::is_nothrow_move_constructible_v && - std::is_nothrow_move_assignable_v) - requires(std::is_move_constructible_v && std::is_move_assignable_v && std::is_move_constructible_v && - std::is_move_assignable_v && - (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v) && + constexpr expected& operator=(expected&& rhs) noexcept( + std::is_nothrow_move_constructible_v && std::is_nothrow_move_assignable_v && + std::is_nothrow_move_constructible_v> && std::is_nothrow_move_assignable_v>) + requires(std::is_move_constructible_v && std::is_move_assignable_v && + std::is_move_constructible_v> && std::is_move_assignable_v> && + (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v>) && !(std::is_trivially_move_constructible_v && std::is_trivially_move_assignable_v && - std::is_trivially_destructible_v && std::is_trivially_move_constructible_v && - std::is_trivially_move_assignable_v && std::is_trivially_destructible_v)); + std::is_trivially_destructible_v && std::is_trivially_move_constructible_v> && + std::is_trivially_move_assignable_v> && + std::is_trivially_destructible_v>)); // Assignment from value U&& template > @@ -272,31 +342,44 @@ class expected { !detail::is_unexpected_specialization>::value && std::is_constructible_v && std::is_assignable_v && (std::is_nothrow_constructible_v || std::is_nothrow_move_constructible_v || - std::is_nothrow_move_constructible_v)) - constexpr expected& operator=(U&& v) { - if (has_val_) { - val_ = std::forward(v); - } else { - detail::reinit_expected(val_, unex_, std::forward(v)); - has_val_ = true; - } - return *this; - } + std::is_nothrow_move_constructible_v>)) + constexpr expected& operator=(U&& v); - // Assignment from unexpected const& + // Assignment from unexpected — value-E path template - requires(std::is_constructible_v && std::is_assignable_v && + requires(!std::is_reference_v && std::is_constructible_v && + std::is_assignable_v && (std::is_nothrow_constructible_v || std::is_nothrow_move_constructible_v || - std::is_nothrow_move_constructible_v)) + std::is_nothrow_move_constructible_v>)) constexpr expected& operator=(const unexpected& e); - // Assignment from unexpected&& template - requires(std::is_constructible_v && std::is_assignable_v && + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v && (std::is_nothrow_constructible_v || std::is_nothrow_move_constructible_v || - std::is_nothrow_move_constructible_v)) + std::is_nothrow_move_constructible_v>)) + constexpr expected& operator=(unexpected&& e); + + // Rebinding assignment for reference E from reference G — binds unex_ to the external + // referent (never dangles; pointer store is noexcept). Mirrors the reference-E constructor. + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr expected& operator=(const unexpected& e); + + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) constexpr expected& operator=(unexpected&& e); + // Deleted for reference E with value G: would rebind E& to unexpected's temporary storage. + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected& operator=(const unexpected&) = delete; + + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected& operator=(unexpected&&) = delete; + // Emplace: destroy current value/error, construct value in-place template requires std::is_nothrow_constructible_v @@ -310,12 +393,12 @@ class expected { // [expected.object.swap] Swap // ------------------------------------------------------------------------- - constexpr void - swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_swappable_v && - std::is_nothrow_move_constructible_v && std::is_nothrow_swappable_v) - requires(std::is_swappable_v && std::is_swappable_v && std::is_move_constructible_v && - std::is_move_constructible_v && - (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v)); + constexpr void swap(expected& rhs) noexcept( + std::is_nothrow_move_constructible_v && std::is_nothrow_swappable_v && + std::is_nothrow_move_constructible_v> && (std::is_reference_v || std::is_nothrow_swappable_v)) + requires(std::is_swappable_v && (std::is_reference_v || std::is_swappable_v) && + std::is_move_constructible_v && std::is_move_constructible_v> && + (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v>)); friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } @@ -350,11 +433,13 @@ class expected { template > constexpr T value_or(U&& def) &&; - template - constexpr E error_or(G&& def) const&; + template + requires(std::is_copy_constructible_v && std::is_convertible_v) + constexpr error_value_type error_or(G&& def) const&; - template - constexpr E error_or(G&& def) &&; + template + requires(std::is_move_constructible_v && std::is_convertible_v) + constexpr error_value_type error_or(G&& def) &&; // ------------------------------------------------------------------------- // [expected.object.monadic] Monadic operations @@ -440,8 +525,8 @@ class expected { private: bool has_val_; union { - T val_; - E unex_; + T val_; + unexpected unex_; }; }; @@ -458,8 +543,8 @@ constexpr expected::expected() noexcept(std::is_nothrow_default_constructi template constexpr expected::expected(const expected& rhs) - requires(std::is_copy_constructible_v && std::is_copy_constructible_v && - !(std::is_trivially_copy_constructible_v && std::is_trivially_copy_constructible_v)) + requires(std::is_copy_constructible_v && std::is_copy_constructible_v> && + !(std::is_trivially_copy_constructible_v && std::is_trivially_copy_constructible_v>)) : has_val_(rhs.has_val_) { if (has_val_) std::construct_at(std::addressof(val_), rhs.val_); @@ -469,9 +554,9 @@ constexpr expected::expected(const expected& rhs) template constexpr expected::expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_move_constructible_v) - requires(std::is_move_constructible_v && std::is_move_constructible_v && - !(std::is_trivially_move_constructible_v && std::is_trivially_move_constructible_v)) + std::is_nothrow_move_constructible_v>) + requires(std::is_move_constructible_v && std::is_move_constructible_v> && + !(std::is_trivially_move_constructible_v && std::is_trivially_move_constructible_v>)) : has_val_(rhs.has_val_) { if (has_val_) std::construct_at(std::addressof(val_), std::move(rhs.val_)); @@ -481,7 +566,7 @@ constexpr expected::expected(expected&& rhs) noexcept(std::is_nothrow_move template template - requires(std::is_constructible_v && std::is_constructible_v && + requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && (std::is_same_v> || !detail::converts_from_any_cvref>) && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && @@ -496,7 +581,7 @@ constexpr expected::expected(const expected& rhs) : has_val_(rhs.has template template - requires(std::is_constructible_v && std::is_constructible_v && + requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && (std::is_same_v> || !detail::converts_from_any_cvref>) && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && @@ -504,23 +589,73 @@ template !std::is_constructible_v, const expected &&>) constexpr expected::expected(expected&& rhs) : has_val_(rhs.has_value()) { if (has_val_) - std::construct_at(std::addressof(val_), std::move(*rhs)); + std::construct_at(std::addressof(val_), *std::move(rhs)); else - std::construct_at(std::addressof(unex_), std::move(rhs.error())); + std::construct_at(std::addressof(unex_), std::move(rhs).error()); +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + std::is_convertible_v) +constexpr expected::expected(const expected& rhs) : has_val_(rhs.has_value()) { + if (has_val_) + std::construct_at(std::addressof(val_), *rhs); + else + std::construct_at(std::addressof(unex_), rhs.error()); +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + std::is_convertible_v) +constexpr expected::expected(expected&& rhs) : has_val_(rhs.has_value()) { + if (has_val_) + std::construct_at(std::addressof(val_), *std::move(rhs)); + else + std::construct_at(std::addressof(unex_), rhs.error()); +} + +template +template + requires(!std::is_same_v, std::in_place_t> && + !std::is_same_v, unexpect_t> && + !std::is_same_v, expected> && std::is_constructible_v && + !detail::is_unexpected_specialization>::value && + (!std::is_same_v> || + !detail::is_expected_specialization>::value)) +constexpr expected::expected(U&& v) : has_val_(true) { + std::construct_at(std::addressof(val_), std::forward(v)); } template template - requires std::is_constructible_v + requires(!std::is_reference_v && std::is_constructible_v) constexpr expected::expected(const unexpected& e) : has_val_(false) { std::construct_at(std::addressof(unex_), e.error()); } template template - requires std::is_constructible_v + requires(!std::is_reference_v && std::is_constructible_v) constexpr expected::expected(unexpected&& e) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::move(e.error())); + std::construct_at(std::addressof(unex_), std::move(e).error()); +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(const unexpected& e) noexcept : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(unexpected&& e) noexcept : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); } template @@ -539,16 +674,16 @@ constexpr expected::expected(std::in_place_t, std::initializer_list il, template template - requires std::is_constructible_v + requires(std::is_constructible_v && !detail::unexpect_dangles_v) constexpr expected::expected(unexpect_t, Args&&... args) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::forward(args)...); + std::construct_at(std::addressof(unex_), std::in_place, std::forward(args)...); } template template requires std::is_constructible_v&, Args...> constexpr expected::expected(unexpect_t, std::initializer_list il, Args&&... args) : has_val_(false) { - std::construct_at(std::addressof(unex_), il, std::forward(args)...); + std::construct_at(std::addressof(unex_), std::in_place, il, std::forward(args)...); } // ============================================================================= @@ -557,7 +692,7 @@ constexpr expected::expected(unexpect_t, std::initializer_list il, Args template constexpr expected::~expected() - requires(!(std::is_trivially_destructible_v && std::is_trivially_destructible_v)) + requires(!(std::is_trivially_destructible_v && std::is_trivially_destructible_v>)) { if (has_val_) std::destroy_at(std::addressof(val_)); @@ -571,12 +706,12 @@ constexpr expected::~expected() template constexpr expected& expected::operator=(const expected& rhs) - requires(std::is_copy_constructible_v && std::is_copy_assignable_v && std::is_copy_constructible_v && - std::is_copy_assignable_v && - (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v) && + requires(std::is_copy_constructible_v && std::is_copy_assignable_v && + std::is_copy_constructible_v> && std::is_copy_assignable_v> && + (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v>) && !(std::is_trivially_copy_constructible_v && std::is_trivially_copy_assignable_v && - std::is_trivially_destructible_v && std::is_trivially_copy_constructible_v && - std::is_trivially_copy_assignable_v && std::is_trivially_destructible_v)) + std::is_trivially_destructible_v && std::is_trivially_copy_constructible_v> && + std::is_trivially_copy_assignable_v> && std::is_trivially_destructible_v>)) { if (has_val_ && rhs.has_val_) { val_ = rhs.val_; @@ -595,16 +730,15 @@ constexpr expected& expected::operator=(const expected& rhs) } template -constexpr expected& expected::operator=(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_move_assignable_v && - std::is_nothrow_move_constructible_v && - std::is_nothrow_move_assignable_v) - requires(std::is_move_constructible_v && std::is_move_assignable_v && std::is_move_constructible_v && - std::is_move_assignable_v && - (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v) && +constexpr expected& expected::operator=(expected&& rhs) noexcept( + std::is_nothrow_move_constructible_v && std::is_nothrow_move_assignable_v && + std::is_nothrow_move_constructible_v> && std::is_nothrow_move_assignable_v>) + requires(std::is_move_constructible_v && std::is_move_assignable_v && + std::is_move_constructible_v> && std::is_move_assignable_v> && + (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v>) && !(std::is_trivially_move_constructible_v && std::is_trivially_move_assignable_v && - std::is_trivially_destructible_v && std::is_trivially_move_constructible_v && - std::is_trivially_move_assignable_v && std::is_trivially_destructible_v)) + std::is_trivially_destructible_v && std::is_trivially_move_constructible_v> && + std::is_trivially_move_assignable_v> && std::is_trivially_destructible_v>)) { if (has_val_ && rhs.has_val_) { val_ = std::move(rhs.val_); @@ -620,14 +754,31 @@ constexpr expected& expected::operator=(expected&& rhs) noexcept(std return *this; } +template +template + requires(!std::is_same_v, std::remove_cvref_t> && + !detail::is_unexpected_specialization>::value && std::is_constructible_v && + std::is_assignable_v && + (std::is_nothrow_constructible_v || std::is_nothrow_move_constructible_v || + std::is_nothrow_move_constructible_v>)) +constexpr expected& expected::operator=(U&& v) { + if (has_val_) { + val_ = std::forward(v); + } else { + detail::reinit_expected(val_, unex_, std::forward(v)); + has_val_ = true; + } + return *this; +} + template template - requires(std::is_constructible_v && std::is_assignable_v && + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v && (std::is_nothrow_constructible_v || std::is_nothrow_move_constructible_v || - std::is_nothrow_move_constructible_v)) + std::is_nothrow_move_constructible_v>)) constexpr expected& expected::operator=(const unexpected& e) { if (!has_val_) { - unex_ = e.error(); + unex_.error() = e.error(); } else { detail::reinit_expected(unex_, val_, e.error()); has_val_ = false; @@ -637,19 +788,52 @@ constexpr expected& expected::operator=(const unexpected& e) { template template - requires(std::is_constructible_v && std::is_assignable_v && + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v && (std::is_nothrow_constructible_v || std::is_nothrow_move_constructible_v || - std::is_nothrow_move_constructible_v)) + std::is_nothrow_move_constructible_v>)) constexpr expected& expected::operator=(unexpected&& e) { if (!has_val_) { - unex_ = std::move(e.error()); + unex_.error() = std::move(e).error(); } else { - detail::reinit_expected(unex_, val_, std::move(e.error())); + detail::reinit_expected(unex_, val_, std::move(e).error()); has_val_ = false; } return *this; } +// Rebinding assignment for reference E from reference G. Repoints unex_ (unexpected) to the +// external referent via construct_at — NOT `unex_.error() = ...`, which would mutate the old +// pointee instead of rebinding. Binding is noexcept; e.error() is the shallow external E&. +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected& expected::operator=(const unexpected& e) { + if (has_val_) { + std::destroy_at(std::addressof(val_)); + std::construct_at(std::addressof(unex_), e.error()); + has_val_ = false; + } else { + std::construct_at(std::addressof(unex_), e.error()); + } + return *this; +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected& expected::operator=(unexpected&& e) { + if (has_val_) { + std::destroy_at(std::addressof(val_)); + std::construct_at(std::addressof(unex_), e.error()); + has_val_ = false; + } else { + std::construct_at(std::addressof(unex_), e.error()); + } + return *this; +} + // ============================================================================= // [expected.object.assign] Out-of-line emplace definitions // ============================================================================= @@ -685,13 +869,12 @@ constexpr T& expected::emplace(std::initializer_list il, Args&&... args // ============================================================================= template -constexpr void expected::swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_swappable_v && - std::is_nothrow_move_constructible_v && - std::is_nothrow_swappable_v) - requires(std::is_swappable_v && std::is_swappable_v && std::is_move_constructible_v && - std::is_move_constructible_v && - (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v)) +constexpr void expected::swap(expected& rhs) noexcept( + std::is_nothrow_move_constructible_v && std::is_nothrow_swappable_v && + std::is_nothrow_move_constructible_v> && (std::is_reference_v || std::is_nothrow_swappable_v)) + requires(std::is_swappable_v && (std::is_reference_v || std::is_swappable_v) && + std::is_move_constructible_v && std::is_move_constructible_v> && + (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v>)) { if (has_val_ && rhs.has_val_) { using std::swap; @@ -701,8 +884,8 @@ constexpr void expected::swap(expected& rhs) noexcept(std::is_nothrow_move swap(unex_, rhs.unex_); } else if (has_val_) { // this has value, rhs has error - if constexpr (std::is_nothrow_move_constructible_v) { - E tmp(std::move(rhs.unex_)); + if constexpr (std::is_nothrow_move_constructible_v>) { + unexpected tmp(std::move(rhs.unex_)); std::destroy_at(std::addressof(rhs.unex_)); if constexpr (std::is_nothrow_move_constructible_v) { std::construct_at(std::addressof(rhs.val_), std::move(val_)); @@ -808,35 +991,45 @@ constexpr bool expected::has_value() const noexcept { template constexpr const T& expected::value() const& { - static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); + static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); if (!has_val_) - throw bad_expected_access(unex_); + throw bad_expected_access(unex_.error()); return val_; } template constexpr T& expected::value() & { - static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); + static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); if (!has_val_) - throw bad_expected_access(unex_); + throw bad_expected_access(unex_.error()); return val_; } template constexpr const T&& expected::value() const&& { - static_assert(std::is_copy_constructible_v && std::is_constructible_v, - "value() && requires E be copy-constructible and constructible from move(error())"); + if constexpr (std::is_reference_v) { + static_assert(std::is_copy_constructible_v && std::is_move_constructible_v, + "value() const&& requires E to be copy and move constructible"); + } else { + static_assert(std::is_copy_constructible_v && std::is_constructible_v, + "value() && requires E be copy-constructible and constructible from move(error())"); + } if (!has_val_) - throw bad_expected_access(std::move(unex_)); + throw bad_expected_access(std::move(unex_).error()); return std::move(val_); } template constexpr T&& expected::value() && { - static_assert(std::is_copy_constructible_v && std::is_constructible_v, - "value() && requires E be copy-constructible and constructible from move(error())"); + if constexpr (std::is_reference_v) { + static_assert(std::is_copy_constructible_v && std::is_move_constructible_v, + "value() && requires E to be copy and move constructible"); + } else { + static_assert(std::is_copy_constructible_v && std::is_constructible_v, + "value() && requires E be copy-constructible and constructible from move(error())"); + } if (!has_val_) - throw bad_expected_access(std::move(unex_)); + throw bad_expected_access(std::move(unex_).error()); return std::move(val_); } @@ -846,7 +1039,7 @@ constexpr const E& expected::error() const& noexcept { if (has_val_) BEMAN_EXPECTED_TRAP(); #endif - return unex_; + return unex_.error(); } template @@ -855,7 +1048,7 @@ constexpr E& expected::error() & noexcept { if (has_val_) BEMAN_EXPECTED_TRAP(); #endif - return unex_; + return unex_.error(); } template @@ -864,7 +1057,7 @@ constexpr const E&& expected::error() const&& noexcept { if (has_val_) BEMAN_EXPECTED_TRAP(); #endif - return std::move(unex_); + return std::move(unex_).error(); } template @@ -873,7 +1066,7 @@ constexpr E&& expected::error() && noexcept { if (has_val_) BEMAN_EXPECTED_TRAP(); #endif - return std::move(unex_); + return std::move(unex_).error(); } template @@ -898,22 +1091,22 @@ constexpr T expected::value_or(U&& def) && { template template -constexpr E expected::error_or(G&& def) const& { - static_assert(std::is_copy_constructible_v, "error_or requires is_copy_constructible_v"); - static_assert(std::is_convertible_v, "error_or requires is_convertible_v"); + requires(std::is_copy_constructible_v::error_value_type> && + std::is_convertible_v::error_value_type>) +constexpr typename expected::error_value_type expected::error_or(G&& def) const& { if (!has_val_) - return unex_; - return static_cast(std::forward(def)); + return unex_.error(); + return static_cast(std::forward(def)); } template template -constexpr E expected::error_or(G&& def) && { - static_assert(std::is_move_constructible_v, "error_or requires is_move_constructible_v"); - static_assert(std::is_convertible_v, "error_or requires is_convertible_v"); + requires(std::is_move_constructible_v::error_value_type> && + std::is_convertible_v::error_value_type>) +constexpr typename expected::error_value_type expected::error_or(G&& def) && { if (!has_val_) - return std::move(unex_); - return static_cast(std::forward(def)); + return std::move(unex_).error(); + return static_cast(std::forward(def)); } // ============================================================================= @@ -931,7 +1124,7 @@ constexpr auto expected::and_then(F&& f) & { "and_then: F must return expected with the same error_type"); if (has_val_) return std::invoke(std::forward(f), val_); - return U(unexpect, unex_); + return U(unexpect, unex_.error()); } template @@ -945,7 +1138,7 @@ constexpr auto expected::and_then(F&& f) && { "and_then: F must return expected with the same error_type"); if (has_val_) return std::invoke(std::forward(f), std::move(val_)); - return U(unexpect, std::move(unex_)); + return U(unexpect, std::move(unex_).error()); } template @@ -959,7 +1152,7 @@ constexpr auto expected::and_then(F&& f) const& { "and_then: F must return expected with the same error_type"); if (has_val_) return std::invoke(std::forward(f), val_); - return U(unexpect, unex_); + return U(unexpect, unex_.error()); } template @@ -973,7 +1166,7 @@ constexpr auto expected::and_then(F&& f) const&& { "and_then: F must return expected with the same error_type"); if (has_val_) return std::invoke(std::forward(f), std::move(val_)); - return U(unexpect, std::move(unex_)); + return U(unexpect, std::move(unex_).error()); } template @@ -982,11 +1175,10 @@ template constexpr auto expected::or_else(F&& f) & { using G = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); - static_assert(std::is_same_v, - "or_else: F must return expected with the same value_type"); + static_assert(std::is_same_v, "or_else: F must return expected with the same value_type"); if (has_val_) return G(std::in_place, val_); - return std::invoke(std::forward(f), unex_); + return std::invoke(std::forward(f), unex_.error()); } template @@ -995,11 +1187,10 @@ template constexpr auto expected::or_else(F&& f) && { using G = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); - static_assert(std::is_same_v, - "or_else: F must return expected with the same value_type"); + static_assert(std::is_same_v, "or_else: F must return expected with the same value_type"); if (has_val_) return G(std::in_place, std::move(val_)); - return std::invoke(std::forward(f), std::move(unex_)); + return std::invoke(std::forward(f), std::move(unex_).error()); } template @@ -1008,11 +1199,10 @@ template constexpr auto expected::or_else(F&& f) const& { using G = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); - static_assert(std::is_same_v, - "or_else: F must return expected with the same value_type"); + static_assert(std::is_same_v, "or_else: F must return expected with the same value_type"); if (has_val_) return G(std::in_place, val_); - return std::invoke(std::forward(f), unex_); + return std::invoke(std::forward(f), unex_.error()); } template @@ -1021,11 +1211,10 @@ template constexpr auto expected::or_else(F&& f) const&& { using G = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); - static_assert(std::is_same_v, - "or_else: F must return expected with the same value_type"); + static_assert(std::is_same_v, "or_else: F must return expected with the same value_type"); if (has_val_) return G(std::in_place, std::move(val_)); - return std::invoke(std::forward(f), std::move(unex_)); + return std::invoke(std::forward(f), std::move(unex_).error()); } template @@ -1045,11 +1234,11 @@ constexpr auto expected::transform(F&& f) & { std::invoke(std::forward(f), val_); if (has_val_) return expected(); - return expected(unexpect, unex_); + return expected(unexpect, unex_.error()); } else { if (has_val_) return expected(std::invoke(std::forward(f), val_)); - return expected(unexpect, unex_); + return expected(unexpect, unex_.error()); } } @@ -1070,11 +1259,11 @@ constexpr auto expected::transform(F&& f) && { std::invoke(std::forward(f), std::move(val_)); if (has_val_) return expected(); - return expected(unexpect, std::move(unex_)); + return expected(unexpect, std::move(unex_).error()); } else { if (has_val_) return expected(std::invoke(std::forward(f), std::move(val_))); - return expected(unexpect, std::move(unex_)); + return expected(unexpect, std::move(unex_).error()); } } @@ -1095,11 +1284,11 @@ constexpr auto expected::transform(F&& f) const& { std::invoke(std::forward(f), val_); if (has_val_) return expected(); - return expected(unexpect, unex_); + return expected(unexpect, unex_.error()); } else { if (has_val_) return expected(std::invoke(std::forward(f), val_)); - return expected(unexpect, unex_); + return expected(unexpect, unex_.error()); } } @@ -1120,11 +1309,11 @@ constexpr auto expected::transform(F&& f) const&& { std::invoke(std::forward(f), std::move(val_)); if (has_val_) return expected(); - return expected(unexpect, std::move(unex_)); + return expected(unexpect, std::move(unex_).error()); } else { if (has_val_) return expected(std::invoke(std::forward(f), std::move(val_))); - return expected(unexpect, std::move(unex_)); + return expected(unexpect, std::move(unex_).error()); } } @@ -1140,7 +1329,7 @@ constexpr auto expected::transform_error(F&& f) & { "transform_error: G must not be a specialization of unexpected"); if (has_val_) return expected(std::in_place, val_); - return expected(unexpect, std::invoke(std::forward(f), unex_)); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); } template @@ -1155,7 +1344,7 @@ constexpr auto expected::transform_error(F&& f) && { "transform_error: G must not be a specialization of unexpected"); if (has_val_) return expected(std::in_place, std::move(val_)); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_))); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } template @@ -1170,7 +1359,7 @@ constexpr auto expected::transform_error(F&& f) const& { "transform_error: G must not be a specialization of unexpected"); if (has_val_) return expected(std::in_place, val_); - return expected(unexpect, std::invoke(std::forward(f), unex_)); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); } template @@ -1185,7 +1374,7 @@ constexpr auto expected::transform_error(F&& f) const&& { "transform_error: G must not be a specialization of unexpected"); if (has_val_) return expected(std::in_place, std::move(val_)); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_))); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } // ============================================================================= diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 87025b0..aba6bc0 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources( expected_constraints.test.cpp expected_trivial.test.cpp expected_monadic_constraints.test.cpp + expected_ref_e.test.cpp todo.test.cpp ) target_link_libraries( @@ -53,9 +54,22 @@ add_fail_test(unexpected_self_fail unexpected_self_fail.cpp) # Step 3 — expected ill-formed T/E [expected.object.general] add_fail_test(expected_t_ref_fail expected_t_ref_fail.cpp) -add_fail_test(expected_e_ref_fail expected_e_ref_fail.cpp) add_fail_test(expected_t_array_fail expected_t_array_fail.cpp) +# expected — error-reference specialization (via unexpected) +add_fail_test(expected_ref_e_const_lvalue_fail expected_ref_e_const_lvalue_fail.cpp) +add_fail_test(expected_ref_e_temporary_error_fail expected_ref_e_temporary_error_fail.cpp) +add_fail_test(expected_ref_e_and_then_wrong_error_type_fail expected_ref_e_and_then_wrong_error_type_fail.cpp) +add_fail_test(expected_ref_e_e_array_fail expected_ref_e_e_array_fail.cpp) +add_fail_test(expected_ref_e_or_else_wrong_value_type_fail expected_ref_e_or_else_wrong_value_type_fail.cpp) +add_fail_test(expected_ref_e_t_array_fail expected_ref_e_t_array_fail.cpp) +add_fail_test(expected_ref_e_t_inplace_fail expected_ref_e_t_inplace_fail.cpp) +add_fail_test(expected_ref_e_t_unexpect_fail expected_ref_e_t_unexpect_fail.cpp) +add_fail_test(expected_ref_e_t_unexpected_fail expected_ref_e_t_unexpected_fail.cpp) +add_fail_test(expected_ref_e_transform_error_ref_result_fail expected_ref_e_transform_error_ref_result_fail.cpp) +add_fail_test(expected_ref_e_assign_unexpected_fail expected_ref_e_assign_unexpected_fail.cpp) +add_fail_test(expected_ref_e_construct_from_unexpected_fail expected_ref_e_construct_from_unexpected_fail.cpp) + # Step 3 — expected emplace Mandates: nothrow_constructible_v add_fail_test(expected_emplace_throwing_fail expected_emplace_throwing_fail.cpp) diff --git a/tests/beman/expected/expected_e_ref_fail.cpp b/tests/beman/expected/expected_e_ref_fail.cpp deleted file mode 100644 index 857207f..0000000 --- a/tests/beman/expected/expected_e_ref_fail.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE COMPILE TEST: expected is ill-formed [expected.object.general] para 2 -// E must not be a reference type. -#include - -beman::expected::expected e; // must not compile diff --git a/tests/beman/expected/expected_ref_e.test.cpp b/tests/beman/expected/expected_ref_e.test.cpp new file mode 100644 index 0000000..d5331c0 --- /dev/null +++ b/tests/beman/expected/expected_ref_e.test.cpp @@ -0,0 +1,597 @@ +// tests/beman/expected/expected_ref_e.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include + +#include "testing/types.hpp" + +#include +#include + +using namespace beman::expected; + +// ============================================================================= +// Type-level static assertions +// ============================================================================= + +// expected is a valid specialization — default constructible (value side) +static_assert(std::is_default_constructible_v>); +static_assert(std::is_constructible_v, std::in_place_t, int>); + +// error() returns E& (shallow const — const expected still returns E&, not const E&) +static_assert(std::is_same_v>().error()), int&>); +static_assert(std::is_same_v>().error()), int&>); + +// value() returns T& (non-const) / const T& (const) +static_assert(std::is_same_v&>().value()), int&>); +static_assert(std::is_same_v&>().value()), const int&>); + +// operator-> returns T* / const T* +static_assert(std::is_same_v>().operator->()), int*>); +static_assert(std::is_same_v>().operator->()), const int*>); + +// Copy/move constructible +static_assert(std::is_copy_constructible_v>); +static_assert(std::is_move_constructible_v>); + +// Triviality: when T is trivial, copy/move/assign/destroy should be trivial +static_assert(std::is_trivially_copy_constructible_v>); +static_assert(std::is_trivially_move_constructible_v>); +static_assert(std::is_trivially_copy_assignable_v>); +static_assert(std::is_trivially_move_assignable_v>); +static_assert(std::is_trivially_destructible_v>); + +// Non-trivial T: still constructible/assignable but not trivially +static_assert(std::is_copy_constructible_v>); +static_assert(std::is_move_constructible_v>); +static_assert(!std::is_trivially_copy_constructible_v>); +static_assert(!std::is_trivially_move_constructible_v>); +static_assert(!std::is_trivially_destructible_v>); + +// Cannot construct from temporary error (rvalue deleted, or any type creating a temp E) +static_assert(!std::is_constructible_v, unexpect_t, int&&>); +// Cross-type temporary: float would create a temp double when binding const double& +static_assert(!std::is_constructible_v, unexpect_t, float>); +// Lvalue of same type is fine +static_assert(std::is_constructible_v, unexpect_t, const double&>); + +// Converting construction from expected +static_assert(std::is_constructible_v, const expected&>); + +// ============================================================================= +// Construction — value side (same as primary template) +// ============================================================================= + +TEST_CASE("expected: default construct has value", "[expected_ref_e]") { + expected e; + REQUIRE(e.has_value()); + CHECK(*e == 0); +} + +TEST_CASE("expected: construct from value", "[expected_ref_e]") { + expected e = 42; + REQUIRE(e.has_value()); + CHECK(*e == 42); +} + +TEST_CASE("expected: in_place value construction", "[expected_ref_e]") { + expected e(std::in_place, 3, 'x'); + REQUIRE(e.has_value()); + CHECK(*e == "xxx"); +} + +// ============================================================================= +// Construction — error side (binds reference) +// ============================================================================= + +TEST_CASE("expected: construct from unexpect lvalue ref", "[expected_ref_e]") { + int err = 7; + expected e(unexpect, err); + REQUIRE(!e.has_value()); + CHECK(&e.error() == &err); + CHECK(e.error() == 7); +} + +TEST_CASE("expected: copy construct preserves error pointer", "[expected_ref_e]") { + int err = 42; + expected a(unexpect, err); + expected b = a; + REQUIRE(!b.has_value()); + CHECK(&b.error() == &err); +} + +TEST_CASE("expected: move construct preserves error pointer", "[expected_ref_e]") { + int err = 5; + expected a(unexpect, err); + expected b = std::move(a); + REQUIRE(!b.has_value()); + CHECK(&b.error() == &err); +} + +// ============================================================================= +// Error rebind semantics on assignment +// ============================================================================= + +TEST_CASE("expected: error rebind via copy assignment", "[expected_ref_e]") { + int err1 = 1, err2 = 2; + expected a(unexpect, err1); + expected b(unexpect, err2); + a = b; + REQUIRE(!a.has_value()); + CHECK(&a.error() == &err2); + // err1 unchanged — rebind, not assign-through + CHECK(err1 == 1); +} + +TEST_CASE("expected: rebind does NOT assign through error reference", "[expected_ref_e]") { + int err1 = 10, err2 = 20; + expected a(unexpect, err1); + expected b(unexpect, err2); + a = b; + CHECK(err1 == 10); // err1 unchanged + CHECK(a.error() == 20); +} + +TEST_CASE("expected: assign value when in error state", "[expected_ref_e]") { + int err = 5; + expected e(unexpect, err); + e = 42; + REQUIRE(e.has_value()); + CHECK(*e == 42); +} + +TEST_CASE("expected: assign to error state via expected copy", "[expected_ref_e]") { + int err = 99; + expected e(42); + expected src(unexpect, err); + e = src; + REQUIRE(!e.has_value()); + CHECK(&e.error() == &err); +} + +// Safe alternative to e = unexpected(err): move-assign from a named expected. +// This is the correct idiom when E is a reference — no operator=(unexpected) +// exists for expected because it would bind E& to temporary storage. +TEST_CASE("expected: rebind error via move-assign from temporary expected", "[expected_ref_e]") { + int new_err = 7; + expected e(42); + e = expected(unexpect, new_err); + REQUIRE(!e.has_value()); + CHECK(&e.error() == &new_err); +} + +// ============================================================================= +// Shallow const on error +// ============================================================================= + +TEST_CASE("expected: shallow const allows mutation of error referent", "[expected_ref_e]") { + int err = 10; + const expected e(unexpect, err); + // error() returns int& (not const int&) — shallow const + e.error() = 20; + CHECK(err == 20); +} + +// ============================================================================= +// Observers +// ============================================================================= + +TEST_CASE("expected: operator*() and operator->() work normally", "[expected_ref_e]") { + expected e(std::in_place, "hello"); + CHECK(e->size() == 5); + CHECK(*e == "hello"); +} + +TEST_CASE("expected: value() returns T& (owned)", "[expected_ref_e]") { + expected e(42); + static_assert(std::is_same_v); + e.value() = 99; + CHECK(*e == 99); +} + +TEST_CASE("expected: value() throws on error", "[expected_ref_e]") { + int err = 5; + expected e(unexpect, err); + REQUIRE_THROWS_AS(e.value(), beman::expected::bad_expected_access); +} + +TEST_CASE("expected: error() returns E&", "[expected_ref_e]") { + int err = 7; + expected e(unexpect, err); + static_assert(std::is_same_v); + CHECK(&e.error() == &err); +} + +TEST_CASE("expected: value_or works normally for value side", "[expected_ref_e]") { + int err = 0; + expected a(42); + expected b(unexpect, err); + CHECK(a.value_or(0) == 42); + CHECK(b.value_or(99) == 99); +} + +TEST_CASE("expected: error_or returns E by value", "[expected_ref_e]") { + int err = 7; + expected a(unexpect, err); + expected b(42); + CHECK(a.error_or(0) == 7); + CHECK(b.error_or(0) == 0); +} + +// ============================================================================= +// Swap +// ============================================================================= + +TEST_CASE("expected: swap value-value", "[expected_ref_e]") { + expected a(1), b(2); + a.swap(b); + CHECK(*a == 2); + CHECK(*b == 1); +} + +TEST_CASE("expected: swap value-error", "[expected_ref_e]") { + int err = 99; + expected a(1), b(unexpect, err); + a.swap(b); + REQUIRE(!a.has_value()); + REQUIRE(b.has_value()); + CHECK(&a.error() == &err); + CHECK(*b == 1); +} + +TEST_CASE("expected: swap error-value", "[expected_ref_e]") { + int err = 99; + expected a(unexpect, err), b(1); + a.swap(b); + REQUIRE(a.has_value()); + REQUIRE(!b.has_value()); + CHECK(*a == 1); + CHECK(&b.error() == &err); +} + +TEST_CASE("expected: swap error-error rebinds pointers", "[expected_ref_e]") { + int e1 = 1, e2 = 2; + expected a(unexpect, e1), b(unexpect, e2); + a.swap(b); + CHECK(&a.error() == &e2); + CHECK(&b.error() == &e1); +} + +// ============================================================================= +// Equality +// ============================================================================= + +TEST_CASE("expected: equality of two value-holding instances", "[expected_ref_e]") { + expected a(42), b(42); + CHECK(a == b); +} + +TEST_CASE("expected: inequality when values differ", "[expected_ref_e]") { + expected a(1), b(2); + CHECK(!(a == b)); +} + +TEST_CASE("expected: equality with value type", "[expected_ref_e]") { + expected e(42); + CHECK(e == 42); + CHECK(!(e == 99)); +} + +TEST_CASE("expected: equality with unexpected (compares error values)", "[expected_ref_e]") { + int err = 7; + expected e(unexpect, err); + // Compares by value, not by pointer identity + CHECK(e == unexpected(7)); + CHECK(!(e == unexpected(8))); +} + +TEST_CASE("expected: value vs error always unequal", "[expected_ref_e]") { + int err = 0; + expected a(0), b(unexpect, err); + CHECK(!(a == b)); +} + +// ============================================================================= +// Monadic operations +// ============================================================================= + +TEST_CASE("expected: and_then works on value side", "[expected_ref_e]") { + expected e(5); + auto r = e.and_then([](int v) -> expected { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 10); +} + +TEST_CASE("expected: and_then propagates error ref", "[expected_ref_e]") { + int err = 3; + expected e(unexpect, err); + auto r = e.and_then([](int v) -> expected { return v * 2; }); + REQUIRE(!r.has_value()); + CHECK(&r.error() == &err); +} + +TEST_CASE("expected: or_else receives E& and can inspect error", "[expected_ref_e]") { + int err = 3; + expected e(unexpect, err); + auto r = e.or_else([](int& v) -> expected { return v * 10; }); + REQUIRE(r.has_value()); + CHECK(*r == 30); +} + +TEST_CASE("expected: or_else propagates value", "[expected_ref_e]") { + expected e(42); + auto r = e.or_else([](int& v) -> expected { return v + 1; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("expected: transform transforms value", "[expected_ref_e]") { + expected e(5); + auto r = e.transform([](int v) { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 10); +} + +TEST_CASE("expected: transform propagates error ref", "[expected_ref_e]") { + int err = 5; + expected e(unexpect, err); + auto r = e.transform([](int v) { return v * 2; }); + REQUIRE(!r.has_value()); + CHECK(&r.error() == &err); +} + +TEST_CASE("expected: transform_error transforms E&", "[expected_ref_e]") { + int err = 5; + expected e(unexpect, err); + auto r = e.transform_error([](int& v) -> std::string { return std::to_string(v); }); + REQUIRE(!r.has_value()); + CHECK(r.error() == "5"); +} + +TEST_CASE("expected: transform_error propagates value", "[expected_ref_e]") { + expected e(42); + auto r = e.transform_error([](int& v) -> std::string { return std::to_string(v); }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +// ============================================================================= +// Converting construction from expected +// ============================================================================= + +TEST_CASE("expected: converting copy construct from expected with value", "[expected_ref_e]") { + expected src(42L); + expected e(src); + REQUIRE(e.has_value()); + CHECK(*e == 42); +} + +TEST_CASE("expected: converting copy construct from expected with error", "[expected_ref_e]") { + int err = 7; + expected src(unexpect, err); + expected e(src); + REQUIRE(!e.has_value()); + CHECK(&e.error() == &err); +} + +TEST_CASE("expected: converting move construct from expected with value", "[expected_ref_e]") { + expected src(99L); + expected e(std::move(src)); + REQUIRE(e.has_value()); + CHECK(*e == 99); +} + +TEST_CASE("expected: converting move construct from expected preserves error pointer", + "[expected_ref_e]") { + int err = 5; + expected src(unexpect, err); + expected e(std::move(src)); + REQUIRE(!e.has_value()); + CHECK(&e.error() == &err); +} + +// ============================================================================= +// Dangling prevention — verify lvalue binding works +// ============================================================================= + +TEST_CASE("expected: lvalue error reference compiles and is addressable", "[expected_ref_e]") { + int err = 1; + expected e(unexpect, err); + CHECK(&e.error() == &err); +} + +// ============================================================================= +// Additional coverage tests: T,E& rvalue/const-rvalue monadic and traced paths +// ============================================================================= +using namespace beman::expected::testing; + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- +TEST_CASE("and_then rvalue on error short-circuits", "[expected_ref_e]") { + int err = 5; + expected e(unexpect, err); + auto r = std::move(e).and_then([](int) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 5); +} + +TEST_CASE("and_then const rvalue on error short-circuits", "[expected_ref_e]") { + int err = 5; + const expected e(unexpect, err); + auto r = std::move(e).and_then([](int) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(!r.has_value()); +} + +// --- Monadic: or_else rvalue/const-rvalue on VALUE state --- +TEST_CASE("or_else rvalue on value short-circuits", "[expected_ref_e]") { + expected e(42); + auto r = std::move(e).or_else([](int&) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("or_else const rvalue on value short-circuits", "[expected_ref_e]") { + const expected e(42); + auto r = std::move(e).or_else([](int&) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(r.has_value()); +} + +// --- Monadic: transform rvalue/const-rvalue on ERROR state --- +TEST_CASE("transform rvalue on error short-circuits", "[expected_ref_e]") { + int err = 3; + expected e(unexpect, err); + auto r = std::move(e).transform([](int v) { return v * 2; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("transform const rvalue on error short-circuits", "[expected_ref_e]") { + int err = 3; + const expected e(unexpect, err); + auto r = std::move(e).transform([](int v) { return v * 2; }); + REQUIRE(!r.has_value()); +} + +// --- Monadic: transform_error rvalue/const-rvalue --- +TEST_CASE("transform_error rvalue on value short-circuits", "[expected_ref_e]") { + expected e(42); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("transform_error const rvalue on value short-circuits", "[expected_ref_e]") { + const expected e(42); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("transform_error rvalue on error calls F", "[expected_ref_e]") { + int err = 5; + expected e(unexpect, err); + auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 6); +} + +TEST_CASE("transform_error const rvalue on error calls F", "[expected_ref_e]") { + int err = 5; + const expected e(unexpect, err); + auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 6); +} + +// --- value assignment on value-state --- +TEST_CASE("value assignment on value state", "[expected_ref_e]") { + expected e(10); + e = 20; + REQUIRE(e.has_value()); + CHECK(*e == 20); +} + +// --- Cross-type equality --- +TEST_CASE("equality error vs value", "[expected_ref_e]") { + int err = 0; + expected a(1); + expected b(unexpect, err); + CHECK_FALSE(a == b); +} + +// --------------------------------------------------------------------------- +// expected: lvalue monadic paths with non-trivial T +// --------------------------------------------------------------------------- + +TEST_CASE("ref: and_then lvalue error path", "[expected_ref_e][traced]") { + int err = 5; + expected e(unexpect, err); + auto r = e.and_then([](traced&) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("ref: or_else lvalue value path", "[expected_ref_e][traced]") { + expected e(std::in_place, 5); + auto r = e.or_else([](int&) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(r.has_value()); + CHECK(r->val == 5); +} + +TEST_CASE("ref: or_else rvalue value path", "[expected_ref_e][traced]") { + expected e(std::in_place, 5); + auto r = std::move(e).or_else([](int&) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: or_else const rvalue value path", "[expected_ref_e][traced]") { + const expected e(std::in_place, 5); + auto r = std::move(e).or_else([](int&) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform lvalue value path", "[expected_ref_e][traced]") { + expected e(std::in_place, 3); + auto r = e.transform([](traced& v) { return traced(v.val + 1); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform rvalue value path", "[expected_ref_e][traced]") { + expected e(std::in_place, 3); + auto r = std::move(e).transform([](traced&& v) { return traced(v.val + 1); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform const rvalue value path", "[expected_ref_e][traced]") { + const expected e(std::in_place, 3); + auto r = std::move(e).transform([](const traced& v) { return traced(v.val + 1); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error lvalue value path", "[expected_ref_e][traced]") { + expected e(std::in_place, 5); + auto r = e.transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error rvalue value path", "[expected_ref_e][traced]") { + expected e(std::in_place, 5); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error const rvalue value path", "[expected_ref_e][traced]") { + const expected e(std::in_place, 5); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: equality error vs value", "[expected_ref_e][traced]") { + int err = 0; + expected a(std::in_place, 1); + expected b(unexpect, err); + CHECK_FALSE(a == b); +} diff --git a/tests/beman/expected/expected_ref_e_and_then_wrong_error_type_fail.cpp b/tests/beman/expected/expected_ref_e_and_then_wrong_error_type_fail.cpp new file mode 100644 index 0000000..6e9849f --- /dev/null +++ b/tests/beman/expected/expected_ref_e_and_then_wrong_error_type_fail.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: and_then on expected must return expected with same error_type (E&) +// EXPECT: "F must return expected with the same error_type" +#include +void test() { + int err = 0; + beman::expected::expected e(42); + // F returns expected but error_type must be int& + e.and_then([](int v) -> beman::expected::expected { return v; }); +} diff --git a/tests/beman/expected/expected_ref_e_assign_unexpected_fail.cpp b/tests/beman/expected/expected_ref_e_assign_unexpected_fail.cpp new file mode 100644 index 0000000..71593af --- /dev/null +++ b/tests/beman/expected/expected_ref_e_assign_unexpected_fail.cpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected cannot be assigned from unexpected. +// No operator=(unexpected...) exists: binding E& to temporary unexpected +// storage would create a dangling reference. Use copy-assign from another +// expected to rebind the error reference safely. +// EXPECT: "no matching function" +#include +using namespace beman::expected; +void test() { + expected e(42); + e = unexpected(7); // must not compile +} diff --git a/tests/beman/expected/expected_ref_e_const_lvalue_fail.cpp b/tests/beman/expected/expected_ref_e_const_lvalue_fail.cpp new file mode 100644 index 0000000..ca89145 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_const_lvalue_fail.cpp @@ -0,0 +1,10 @@ +// tests/beman/expected/expected_ref_e_const_lvalue_fail.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: cannot bind a const lvalue to non-const E& — language-level constraint +// EXPECT: "no matching function|cannot bind" +#include +void test() { + const int err = 5; + // expected (non-const E) cannot be constructed from const int lvalue + beman::expected::expected e(beman::expected::unexpect, err); +} diff --git a/tests/beman/expected/expected_ref_e_construct_from_unexpected_fail.cpp b/tests/beman/expected/expected_ref_e_construct_from_unexpected_fail.cpp new file mode 100644 index 0000000..a8fff21 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_construct_from_unexpected_fail.cpp @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected cannot be constructed from unexpected. +// A value-typed unexpected stores G by value; binding E& to it would create a +// dangling reference when the unexpected object is destroyed. Construction from a +// reference-typed unexpected (which holds an external object) is allowed. +// EXPECT: "no matching function" +#include +using namespace beman::expected; +void test() { + expected e = unexpected(7); // must not compile +} diff --git a/tests/beman/expected/expected_ref_e_e_array_fail.cpp b/tests/beman/expected/expected_ref_e_e_array_fail.cpp new file mode 100644 index 0000000..583dc4f --- /dev/null +++ b/tests/beman/expected/expected_ref_e_e_array_fail.cpp @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: E must not be an array type in expected +// EXPECT: "E must not be an array type" +#include +void test() { + using arr = int[3]; + beman::expected::expected e{}; +} diff --git a/tests/beman/expected/expected_ref_e_or_else_wrong_value_type_fail.cpp b/tests/beman/expected/expected_ref_e_or_else_wrong_value_type_fail.cpp new file mode 100644 index 0000000..91117a8 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_or_else_wrong_value_type_fail.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: or_else on expected must return expected with same value_type (T) +// EXPECT: "F must return expected with the same value_type" +#include +void test() { + int err = 0; + beman::expected::expected e(beman::expected::unexpect, err); + // F returns expected but value_type must be int + e.or_else([](int& v) -> beman::expected::expected { return 0.0; }); +} diff --git a/tests/beman/expected/expected_ref_e_t_array_fail.cpp b/tests/beman/expected/expected_ref_e_t_array_fail.cpp new file mode 100644 index 0000000..67546bd --- /dev/null +++ b/tests/beman/expected/expected_ref_e_t_array_fail.cpp @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: T must not be an array type in expected +// EXPECT: "T must not be an array type" +#include +void test() { beman::expected::expected e{}; } diff --git a/tests/beman/expected/expected_ref_e_t_inplace_fail.cpp b/tests/beman/expected/expected_ref_e_t_inplace_fail.cpp new file mode 100644 index 0000000..b352c3a --- /dev/null +++ b/tests/beman/expected/expected_ref_e_t_inplace_fail.cpp @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: T must not be in_place_t in expected +// EXPECT: "T must not be in_place_t" +#include +#include +void test() { beman::expected::expected e{}; } diff --git a/tests/beman/expected/expected_ref_e_t_unexpect_fail.cpp b/tests/beman/expected/expected_ref_e_t_unexpect_fail.cpp new file mode 100644 index 0000000..335f509 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_t_unexpect_fail.cpp @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: T must not be unexpect_t in expected +// EXPECT: "T must not be unexpect_t" +#include +void test() { beman::expected::expected e{}; } diff --git a/tests/beman/expected/expected_ref_e_t_unexpected_fail.cpp b/tests/beman/expected/expected_ref_e_t_unexpected_fail.cpp new file mode 100644 index 0000000..21f6bd8 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_t_unexpected_fail.cpp @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: T must not be a specialization of unexpected in expected +// EXPECT: "T must not be a specialization of unexpected" +#include +void test() { beman::expected::expected, int&> e{}; } diff --git a/tests/beman/expected/expected_ref_e_temporary_error_fail.cpp b/tests/beman/expected/expected_ref_e_temporary_error_fail.cpp new file mode 100644 index 0000000..8b95b43 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_temporary_error_fail.cpp @@ -0,0 +1,9 @@ +// tests/beman/expected/expected_ref_e_temporary_error_fail.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: cannot bind a temporary (rvalue) to E& — dangling prevention +// EXPECT: "delete|no matching function" +#include +void test() { + // 42 is a temporary — expected must refuse to bind E& to it + beman::expected::expected e(beman::expected::unexpect, 42); +} diff --git a/tests/beman/expected/expected_ref_e_transform_error_ref_result_fail.cpp b/tests/beman/expected/expected_ref_e_transform_error_ref_result_fail.cpp new file mode 100644 index 0000000..deba573 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_transform_error_ref_result_fail.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: transform_error on expected must return object type G, not reference +// EXPECT: "G must be an object type" +#include +void test() { + int err = 0; + beman::expected::expected e(beman::expected::unexpect, err); + // G = int& (reference type, not object type) + e.transform_error([](int& v) -> int& { return v; }); +} diff --git a/tests/beman/expected/testing/types.hpp b/tests/beman/expected/testing/types.hpp new file mode 100644 index 0000000..0962df1 --- /dev/null +++ b/tests/beman/expected/testing/types.hpp @@ -0,0 +1,131 @@ +// tests/beman/expected/testing/types.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef BEMAN_EXPECTED_TESTING_TYPES_HPP +#define BEMAN_EXPECTED_TESTING_TYPES_HPP + +#include + +#include +#include + +namespace beman::expected::testing { + +// Non-trivially destructible, copyable, and movable. +// Triggers explicit destroy_at / construct_at paths in unions. +struct traced { + int val; + + constexpr explicit traced(int v = 0) noexcept : val(v) {} + constexpr traced(const traced& o) noexcept : val(o.val) {} + constexpr traced(traced&& o) noexcept : val(o.val) { o.val = -1; } + constexpr traced& operator=(const traced& o) { + val = o.val; + return *this; + } + constexpr traced& operator=(traced&& o) noexcept { + val = o.val; + o.val = -1; + return *this; + } + constexpr ~traced() { val = -999; } + + constexpr bool operator==(const traced& o) const { return val == o.val; } +}; + +static_assert(!std::is_trivially_destructible_v); +static_assert(!std::is_trivially_copy_constructible_v); +static_assert(!std::is_trivially_move_constructible_v); + +// Move-only, non-trivially destructible. +// Forces move-constructor and move-assignment paths; copy is deleted. +struct move_only { + int val; + + constexpr explicit move_only(int v = 0) : val(v) {} + constexpr move_only(move_only&& o) noexcept : val(o.val) { o.val = -1; } + constexpr move_only& operator=(move_only&& o) noexcept { + val = o.val; + o.val = -1; + return *this; + } + move_only(const move_only&) = delete; + move_only& operator=(const move_only&) = delete; + constexpr ~move_only() { val = -999; } + + constexpr bool operator==(const move_only& o) const { return val == o.val; } +}; + +static_assert(!std::is_copy_constructible_v); +static_assert(std::is_nothrow_move_constructible_v); + +// Accepts an initializer_list in its constructor. +// Triggers the in_place_t(initializer_list, Args...) and +// unexpect_t(initializer_list, Args...) constructor paths. +struct init_list_type { + int sum; + int count; + + constexpr init_list_type(std::initializer_list il, int extra = 0) noexcept : sum(extra), count(0) { + for (int v : il) { + sum += v; + ++count; + } + } + constexpr init_list_type(const init_list_type&) = default; + constexpr init_list_type(init_list_type&&) = default; + constexpr init_list_type& operator=(const init_list_type&) = default; + constexpr init_list_type& operator=(init_list_type&&) = default; + constexpr ~init_list_type() = default; + + constexpr bool operator==(const init_list_type& o) const { return sum == o.sum && count == o.count; } +}; + +// Convertible pair for testing converting constructors. +// widened is constructible from narrowed (implicit), triggering +// expected from expected. +struct narrowed { + int val; + constexpr explicit narrowed(int v = 0) : val(v) {} + constexpr bool operator==(const narrowed& o) const { return val == o.val; } +}; + +struct widened { + long val; + constexpr widened(narrowed n) : val(n.val) {} + constexpr explicit widened(long v) : val(v) {} + constexpr bool operator==(const widened& o) const { return val == o.val; } +}; + +// Derived from expected — tests constraint 23.6: if T is cv bool, +// remove_cvref_t must not be a specialization of expected. +// Also tests that value ctor rejects expected-derived types. +struct from_expected : beman::expected::expected { + using beman::expected::expected::expected; +}; + +// Derived from unexpected — tests Mandate: +// T must not be a specialization of unexpected. +struct from_unexpected : beman::expected::unexpected { + using beman::expected::unexpected::unexpected; +}; + +// Cross-type equality pair. +// eq_a and eq_b are equality-comparable to each other but are distinct types, +// enabling cross-type expected == expected tests. +struct eq_a { + int val; + constexpr explicit eq_a(int v = 0) : val(v) {} + constexpr bool operator==(const eq_a& o) const { return val == o.val; } +}; + +struct eq_b { + int val; + constexpr explicit eq_b(int v = 0) : val(v) {} + constexpr bool operator==(const eq_b& o) const { return val == o.val; } + friend constexpr bool operator==(const eq_a& a, const eq_b& b) { return a.val == b.val; } +}; + +} // namespace beman::expected::testing + +#endif // BEMAN_EXPECTED_TESTING_TYPES_HPP From 2561a896dad77ec704a3ffb37d74a350fa444cd5 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 20:50:20 -0400 Subject: [PATCH 03/15] feat: expected stores unexpected, enabling expected Rewrite the void specialization as the explicit expected form and store the error as unexpected in its union, so E may be an lvalue reference by the same unexpected mechanism as the primary. Adds the reference-E converting constructor from expected, the rebinding operator= from unexpected, the self-type exclusion on the converting constructor, and move-then-deref on all error accesses. expected is now well-formed, so its negative test is dropped; the void+error- reference test group is added. --- include/beman/expected/expected.hpp | 651 +++++++++++------- tests/beman/expected/CMakeLists.txt | 9 +- .../expected/expected_void_ref_e.test.cpp | 638 +++++++++++++++++ ...cted_void_ref_e_assign_unexpected_fail.cpp | 11 + .../expected_void_ref_e_const_lvalue_fail.cpp | 7 + ...d_ref_e_construct_from_unexpected_fail.cpp | 11 + .../expected_void_ref_e_no_value_or_fail.cpp | 7 + .../expected_void_ref_e_temporary_fail.cpp | 4 + .../beman/expected/expected_void_ref_fail.cpp | 5 - 9 files changed, 1073 insertions(+), 270 deletions(-) create mode 100644 tests/beman/expected/expected_void_ref_e.test.cpp create mode 100644 tests/beman/expected/expected_void_ref_e_assign_unexpected_fail.cpp create mode 100644 tests/beman/expected/expected_void_ref_e_const_lvalue_fail.cpp create mode 100644 tests/beman/expected/expected_void_ref_e_construct_from_unexpected_fail.cpp create mode 100644 tests/beman/expected/expected_void_ref_e_no_value_or_fail.cpp create mode 100644 tests/beman/expected/expected_void_ref_e_temporary_fail.cpp delete mode 100644 tests/beman/expected/expected_void_ref_fail.cpp diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index a9ffa2f..5d9d0e3 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -1381,17 +1381,20 @@ constexpr auto expected::transform_error(F&& f) const&& { // [expected.void] Partial specialization for void value type // ============================================================================= -template - requires std::is_void_v -class expected { - static_assert(!std::is_reference_v, "E must not be a reference"); - static_assert(!std::is_void_v, "E must not be void"); - static_assert(!std::is_array_v, "E must not be an array type"); - static_assert(std::is_same_v, E>, "E must not be cv-qualified"); - static_assert(!detail::is_unexpected_specialization::value, "E must not be an unexpected specialization"); +template +class expected { + static_assert(!std::is_rvalue_reference_v, "E must not be an rvalue reference"); + static_assert(!std::is_void_v>, "E must not be void"); + static_assert(!std::is_array_v>, "E must not be an array type"); + static_assert(std::is_reference_v || std::is_same_v, E>, "E must not be cv-qualified"); + static_assert(!detail::is_unexpected_specialization>>::value, + "E must not be an unexpected specialization"); + + private: + using error_value_type = std::remove_cv_t>; public: - using value_type = T; + using value_type = void; using error_type = E; using unexpected_type = unexpected; @@ -1402,72 +1405,116 @@ class expected { // [expected.void.cons] Constructors // ------------------------------------------------------------------------- - constexpr expected() noexcept : has_val_(true) {} + constexpr expected() noexcept; constexpr expected(const expected&) - requires std::is_trivially_copy_constructible_v + requires std::is_trivially_copy_constructible_v> = default; constexpr expected(const expected& rhs) - requires(std::is_copy_constructible_v && !std::is_trivially_copy_constructible_v); + requires(std::is_copy_constructible_v> && + !std::is_trivially_copy_constructible_v>); constexpr expected(expected&&) noexcept - requires std::is_trivially_move_constructible_v + requires std::is_trivially_move_constructible_v> = default; - constexpr expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v) - requires(std::is_move_constructible_v && !std::is_trivially_move_constructible_v); + constexpr expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v>) + requires(std::is_move_constructible_v> && + !std::is_trivially_move_constructible_v>); - // Converting constructor from expected where is_void_v + // Converting constructor from expected where is_void_v. Excludes U,G exactly + // matching this class's own void,E (the real copy/move constructors already handle that + // case) — instantiating this template for the self-referential case would otherwise probe + // unexpected's constructibility from this very class, which some standard library + // implementations of reference_constructs_from_temporary_v resolve as a circular constraint. template - requires(std::is_void_v && std::is_constructible_v && - !std::is_constructible_v, expected&> && + requires(std::is_void_v && !std::is_same_v && + std::is_constructible_v && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && !std::is_constructible_v, const expected&> && !std::is_constructible_v, const expected &&>) constexpr explicit(!std::is_convertible_v) expected(const expected& rhs); template - requires(std::is_void_v && std::is_constructible_v && + requires(std::is_void_v && !std::is_same_v && std::is_constructible_v && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && !std::is_constructible_v, const expected&> && !std::is_constructible_v, const expected &&>) constexpr explicit(!std::is_convertible_v) expected(expected&& rhs); - // Constructor from unexpected const& + // Constructor from unexpected const& / && — value-E path template - requires std::is_constructible_v + requires(!std::is_reference_v && std::is_constructible_v) constexpr explicit(!std::is_convertible_v) expected(const unexpected& e); - // Constructor from unexpected&& template - requires std::is_constructible_v + requires(!std::is_reference_v && std::is_constructible_v) constexpr explicit(!std::is_convertible_v) expected(unexpected&& e); + // Constructor from unexpected — reference-E path. Allowed only when G is itself a reference, + // so e.error() refers to an external object and binding E& cannot dangle. No const_cast needed. + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) noexcept; + + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) noexcept; + + // Deleted for reference E with value G: the referent lives inside the temporary unexpected, so + // binding E& to it would dangle once the source is destroyed. Use (unexpect, lvalue) instead. + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected(const unexpected&) = delete; + + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected(unexpected&&) = delete; + // In-place constructor for value (no args, just marks has-value) - constexpr explicit expected(std::in_place_t) noexcept : has_val_(true) {} + constexpr explicit expected(std::in_place_t) noexcept; // In-place constructor for error template - requires std::is_constructible_v + requires(std::is_constructible_v && !detail::unexpect_dangles_v) constexpr explicit expected(unexpect_t, Args&&... args); + // Deleted: single argument would bind E& to a temporary — dangling prevention + template + requires(detail::unexpect_dangles_v) + constexpr expected(unexpect_t, Args&&...) = delete; + + // Deleted catch-all: reference E, argument neither constructible nor a dangling case + // (e.g. binding a non-const E& from a const lvalue). + template + requires(std::is_reference_v && !std::is_constructible_v && + !detail::unexpect_dangles_v) + constexpr expected(unexpect_t, Args&&...) = delete; + // In-place constructor for error with initializer_list template requires std::is_constructible_v&, Args...> constexpr explicit expected(unexpect_t, std::initializer_list il, Args&&... args); + // Converting constructor from expected — reference-E path only + template + requires(std::is_reference_v && std::is_convertible_v) + constexpr explicit(!std::is_convertible_v) expected(const expected& rhs); + // ------------------------------------------------------------------------- // [expected.void.dtor] Destructor // ------------------------------------------------------------------------- constexpr ~expected() - requires std::is_trivially_destructible_v + requires std::is_trivially_destructible_v> = default; constexpr ~expected() - requires(!std::is_trivially_destructible_v); + requires(!std::is_trivially_destructible_v>); // ------------------------------------------------------------------------- // [expected.void.assign] Assignment @@ -1475,46 +1522,72 @@ class expected { // Copy assignment (trivial path) constexpr expected& operator=(const expected&) - requires(std::is_trivially_copy_constructible_v && std::is_trivially_copy_assignable_v && - std::is_trivially_destructible_v) + requires(std::is_trivially_copy_constructible_v> && + std::is_trivially_copy_assignable_v> && + std::is_trivially_destructible_v>) = default; // Copy assignment (non-trivial path) constexpr expected& operator=(const expected& rhs) - requires(std::is_copy_constructible_v && std::is_copy_assignable_v && - !(std::is_trivially_copy_constructible_v && std::is_trivially_copy_assignable_v && - std::is_trivially_destructible_v)); + requires(std::is_copy_constructible_v> && std::is_copy_assignable_v> && + !(std::is_trivially_copy_constructible_v> && + std::is_trivially_copy_assignable_v> && + std::is_trivially_destructible_v>)); // Move assignment (trivial path) constexpr expected& operator=(expected&&) noexcept - requires(std::is_trivially_move_constructible_v && std::is_trivially_move_assignable_v && - std::is_trivially_destructible_v) + requires(std::is_trivially_move_constructible_v> && + std::is_trivially_move_assignable_v> && + std::is_trivially_destructible_v>) = default; // Move assignment (non-trivial path) - constexpr expected& operator=(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_move_assignable_v) - requires(std::is_move_constructible_v && std::is_move_assignable_v && - !(std::is_trivially_move_constructible_v && std::is_trivially_move_assignable_v && - std::is_trivially_destructible_v)); + constexpr expected& operator=(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v> && + std::is_nothrow_move_assignable_v>) + requires(std::is_move_constructible_v> && std::is_move_assignable_v> && + !(std::is_trivially_move_constructible_v> && + std::is_trivially_move_assignable_v> && + std::is_trivially_destructible_v>)); + // Assignment from unexpected — value-E path. template - requires(std::is_constructible_v && std::is_assignable_v) + requires(!std::is_reference_v && std::is_constructible_v && + std::is_assignable_v) constexpr expected& operator=(const unexpected& e); template - requires(std::is_constructible_v && std::is_assignable_v) + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) constexpr expected& operator=(unexpected&& e); + // Rebinding assignment for reference E from reference G — binds unex_ to the external referent. + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr expected& operator=(const unexpected& e); + + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr expected& operator=(unexpected&& e); + + // Deleted for reference E with value G: would bind E& to storage inside the temporary unexpected. + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected& operator=(const unexpected&) = delete; + + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected& operator=(unexpected&&) = delete; + constexpr void emplace() noexcept; // ------------------------------------------------------------------------- // [expected.void.swap] Swap // ------------------------------------------------------------------------- - constexpr void swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_swappable_v) - requires(std::is_swappable_v && std::is_move_constructible_v); + constexpr void swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v> && + (std::is_reference_v || std::is_nothrow_swappable_v)) + requires((std::is_reference_v || std::is_swappable_v) && std::is_move_constructible_v>); friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } @@ -1522,53 +1595,35 @@ class expected { // [expected.void.obs] Observers // ------------------------------------------------------------------------- - constexpr explicit operator bool() const noexcept { return has_val_; } - constexpr bool has_value() const noexcept { return has_val_; } + constexpr explicit operator bool() const noexcept; + constexpr bool has_value() const noexcept; - constexpr void operator*() const noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - } + constexpr void operator*() const noexcept; constexpr void value() const&; constexpr void value() &&; - constexpr const E& error() const& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return unex_; - } - constexpr E& error() & noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return unex_; - } - constexpr const E&& error() const&& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::move(unex_); - } - constexpr E&& error() && noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::move(unex_); - } + // error() — shallow const for reference E: always returns E& regardless of const on expected + constexpr const E& error() const& noexcept; + constexpr E& error() & noexcept; + constexpr const E&& error() const&& noexcept; + constexpr E&& error() && noexcept; - template - constexpr E error_or(G&& def) const&; + template + requires(std::is_copy_constructible_v>> && + std::is_convertible_v>>) + constexpr error_value_type error_or(G&& def) const&; + + template + requires(std::is_move_constructible_v>> && + std::is_convertible_v>>) + constexpr error_value_type error_or(G&& def) &&; - template - constexpr E error_or(G&& def) &&; + // Deleted: value_or is not available for void expected. Gated to reference E only so that, + // for value E, no value_or overload is declared at all (there is nothing to delete against). + template + requires std::is_reference_v + constexpr void value_or(U&&) const = delete; // ------------------------------------------------------------------------- // [expected.void.monadic] Monadic operations @@ -1640,7 +1695,7 @@ class expected { private: bool has_val_; union { - E unex_; + unexpected unex_; }; }; @@ -1648,90 +1703,112 @@ class expected { // [expected.void.cons] Out-of-line constructor definitions // ============================================================================= -template - requires std::is_void_v -constexpr expected::expected(const expected& rhs) - requires(std::is_copy_constructible_v && !std::is_trivially_copy_constructible_v) +template +constexpr expected::expected() noexcept : has_val_(true) {} + +template +constexpr expected::expected(std::in_place_t) noexcept : has_val_(true) {} + +template +constexpr expected::expected(const expected& rhs) + requires(std::is_copy_constructible_v> && !std::is_trivially_copy_constructible_v>) : has_val_(rhs.has_val_) { if (!has_val_) std::construct_at(std::addressof(unex_), rhs.unex_); } -template - requires std::is_void_v -constexpr expected::expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v) - requires(std::is_move_constructible_v && !std::is_trivially_move_constructible_v) +template +constexpr expected::expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v>) + requires(std::is_move_constructible_v> && !std::is_trivially_move_constructible_v>) : has_val_(rhs.has_val_) { if (!has_val_) std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); } -template - requires std::is_void_v +template template - requires(std::is_void_v && std::is_constructible_v && - !std::is_constructible_v, expected&> && + requires(std::is_void_v && !std::is_same_v && + std::is_constructible_v && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && !std::is_constructible_v, const expected&> && !std::is_constructible_v, const expected &&>) -constexpr expected::expected(const expected& rhs) : has_val_(rhs.has_value()) { +constexpr expected::expected(const expected& rhs) : has_val_(rhs.has_value()) { if (!has_val_) std::construct_at(std::addressof(unex_), rhs.error()); } -template - requires std::is_void_v +template template - requires(std::is_void_v && std::is_constructible_v && + requires(std::is_void_v && !std::is_same_v && std::is_constructible_v && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && !std::is_constructible_v, const expected&> && !std::is_constructible_v, const expected &&>) -constexpr expected::expected(expected&& rhs) : has_val_(rhs.has_value()) { +constexpr expected::expected(expected&& rhs) : has_val_(rhs.has_value()) { if (!has_val_) - std::construct_at(std::addressof(unex_), std::move(rhs.error())); + std::construct_at(std::addressof(unex_), std::move(rhs).error()); } -template - requires std::is_void_v +template template - requires std::is_constructible_v -constexpr expected::expected(const unexpected& e) : has_val_(false) { + requires(!std::is_reference_v && std::is_constructible_v) +constexpr expected::expected(const unexpected& e) : has_val_(false) { std::construct_at(std::addressof(unex_), e.error()); } -template - requires std::is_void_v +template template - requires std::is_constructible_v -constexpr expected::expected(unexpected&& e) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::move(e.error())); + requires(!std::is_reference_v && std::is_constructible_v) +constexpr expected::expected(unexpected&& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::move(e).error()); } -template - requires std::is_void_v +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(const unexpected& e) noexcept : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(unexpected&& e) noexcept : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); +} + + +template template - requires std::is_constructible_v -constexpr expected::expected(unexpect_t, Args&&... args) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::forward(args)...); + requires(std::is_constructible_v && !detail::unexpect_dangles_v) +constexpr expected::expected(unexpect_t, Args&&... args) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::in_place, std::forward(args)...); } -template - requires std::is_void_v +template template requires std::is_constructible_v&, Args...> -constexpr expected::expected(unexpect_t, std::initializer_list il, Args&&... args) : has_val_(false) { - std::construct_at(std::addressof(unex_), il, std::forward(args)...); +constexpr expected::expected(unexpect_t, std::initializer_list il, Args&&... args) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::in_place, il, std::forward(args)...); +} + +template +template + requires(std::is_reference_v && std::is_convertible_v) +constexpr expected::expected(const expected& rhs) : has_val_(rhs.has_value()) { + if (!has_val_) + std::construct_at(std::addressof(unex_), rhs.error()); } // ============================================================================= // [expected.void.dtor] Out-of-line destructor // ============================================================================= -template - requires std::is_void_v -constexpr expected::~expected() - requires(!std::is_trivially_destructible_v) +template +constexpr expected::~expected() + requires(!std::is_trivially_destructible_v>) { if (!has_val_) std::destroy_at(std::addressof(unex_)); @@ -1741,60 +1818,54 @@ constexpr expected::~expected() // [expected.void.assign] Out-of-line assignment definitions // ============================================================================= -template - requires std::is_void_v -constexpr expected& expected::operator=(const expected& rhs) - requires(std::is_copy_constructible_v && std::is_copy_assignable_v && - !(std::is_trivially_copy_constructible_v && std::is_trivially_copy_assignable_v && - std::is_trivially_destructible_v)) +template +constexpr expected& expected::operator=(const expected& rhs) + requires(std::is_copy_constructible_v> && std::is_copy_assignable_v> && + !(std::is_trivially_copy_constructible_v> && + std::is_trivially_copy_assignable_v> && std::is_trivially_destructible_v>)) { if (has_val_ && rhs.has_val_) { // both value: no-op } else if (!has_val_ && !rhs.has_val_) { unex_ = rhs.unex_; } else if (has_val_) { - // was value, now error std::construct_at(std::addressof(unex_), rhs.unex_); has_val_ = false; } else { - // was error, now value std::destroy_at(std::addressof(unex_)); has_val_ = true; } return *this; } -template - requires std::is_void_v -constexpr expected& expected::operator=(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_move_assignable_v) - requires(std::is_move_constructible_v && std::is_move_assignable_v && - !(std::is_trivially_move_constructible_v && std::is_trivially_move_assignable_v && - std::is_trivially_destructible_v)) +template +constexpr expected& +expected::operator=(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v> && + std::is_nothrow_move_assignable_v>) + requires(std::is_move_constructible_v> && std::is_move_assignable_v> && + !(std::is_trivially_move_constructible_v> && + std::is_trivially_move_assignable_v> && std::is_trivially_destructible_v>)) { if (has_val_ && rhs.has_val_) { // both value: no-op } else if (!has_val_ && !rhs.has_val_) { unex_ = std::move(rhs.unex_); } else if (has_val_) { - // was value, now error std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); has_val_ = false; } else { - // was error, now value std::destroy_at(std::addressof(unex_)); has_val_ = true; } return *this; } -template - requires std::is_void_v +template template - requires(std::is_constructible_v && std::is_assignable_v) -constexpr expected& expected::operator=(const unexpected& e) { + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) +constexpr expected& expected::operator=(const unexpected& e) { if (!has_val_) { - unex_ = e.error(); + unex_.error() = e.error(); } else { std::construct_at(std::addressof(unex_), e.error()); has_val_ = false; @@ -1802,23 +1873,43 @@ constexpr expected& expected::operator=(const unexpected& e) { return *this; } -template - requires std::is_void_v +template template - requires(std::is_constructible_v && std::is_assignable_v) -constexpr expected& expected::operator=(unexpected&& e) { + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) +constexpr expected& expected::operator=(unexpected&& e) { if (!has_val_) { - unex_ = std::move(e.error()); + unex_.error() = std::move(e).error(); } else { - std::construct_at(std::addressof(unex_), std::move(e.error())); + std::construct_at(std::addressof(unex_), std::move(e).error()); has_val_ = false; } return *this; } -template - requires std::is_void_v -constexpr void expected::emplace() noexcept { +// Rebinding assignment for reference E from reference G. No value member to destroy; repoint unex_ +// via construct_at (not `unex_.error() = ...`, which would mutate the old pointee). +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected& expected::operator=(const unexpected& e) { + std::construct_at(std::addressof(unex_), e.error()); + has_val_ = false; + return *this; +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected& expected::operator=(unexpected&& e) { + std::construct_at(std::addressof(unex_), e.error()); + has_val_ = false; + return *this; +} + +template +constexpr void expected::emplace() noexcept { if (!has_val_) { std::destroy_at(std::addressof(unex_)); has_val_ = true; @@ -1829,11 +1920,11 @@ constexpr void expected::emplace() noexcept { // [expected.void.swap] Out-of-line swap definition // ============================================================================= -template - requires std::is_void_v -constexpr void expected::swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_swappable_v) - requires(std::is_swappable_v && std::is_move_constructible_v) +template +constexpr void expected::swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v> && + (std::is_reference_v || + std::is_nothrow_swappable_v)) + requires((std::is_reference_v || std::is_swappable_v) && std::is_move_constructible_v>) { if (has_val_ && rhs.has_val_) { // both value: no-op @@ -1841,13 +1932,11 @@ constexpr void expected::swap(expected& rhs) noexcept(std::is_nothrow_move using std::swap; swap(unex_, rhs.unex_); } else if (has_val_) { - // this has value, rhs has error std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); std::destroy_at(std::addressof(rhs.unex_)); has_val_ = false; rhs.has_val_ = true; } else { - // this has error, rhs has value rhs.swap(*this); } } @@ -1856,54 +1945,103 @@ constexpr void expected::swap(expected& rhs) noexcept(std::is_nothrow_move // [expected.void.obs] Out-of-line observer definitions // ============================================================================= -template - requires std::is_void_v -constexpr void expected::value() const& { - static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); +template +constexpr expected::operator bool() const noexcept { + return has_val_; +} + +template +constexpr bool expected::has_value() const noexcept { + return has_val_; +} + +template +constexpr void expected::operator*() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) if (!has_val_) - throw bad_expected_access(unex_); + BEMAN_EXPECTED_TRAP(); +#endif } -template - requires std::is_void_v -constexpr void expected::value() && { - static_assert(std::is_copy_constructible_v && std::is_move_constructible_v, - "value() && requires E be copy-constructible and move-constructible"); +template +constexpr void expected::value() const& { + static_assert(std::is_copy_constructible_v, "value() requires E to be copy constructible"); if (!has_val_) - throw bad_expected_access(std::move(unex_)); + throw bad_expected_access(unex_.error()); } -template - requires std::is_void_v +template +constexpr void expected::value() && { + static_assert(std::is_copy_constructible_v && std::is_move_constructible_v, + "value() && requires E to be copy and move constructible"); + if (!has_val_) + throw bad_expected_access(std::move(unex_).error()); +} + +template +constexpr const E& expected::error() const& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return unex_.error(); +} + +template +constexpr E& expected::error() & noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return unex_.error(); +} + +template +constexpr const E&& expected::error() const&& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(unex_).error(); +} + +template +constexpr E&& expected::error() && noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(unex_).error(); +} + +template template -constexpr E expected::error_or(G&& def) const& { - static_assert(std::is_copy_constructible_v, "error_or requires is_copy_constructible_v"); - static_assert(std::is_convertible_v, "error_or requires is_convertible_v"); + requires(std::is_copy_constructible_v>> && + std::is_convertible_v>>) +constexpr typename expected::error_value_type expected::error_or(G&& def) const& { if (!has_val_) - return unex_; - return static_cast(std::forward(def)); + return unex_.error(); + return static_cast(std::forward(def)); } -template - requires std::is_void_v +template template -constexpr E expected::error_or(G&& def) && { - static_assert(std::is_move_constructible_v, "error_or requires is_move_constructible_v"); - static_assert(std::is_convertible_v, "error_or requires is_convertible_v"); + requires(std::is_move_constructible_v>> && + std::is_convertible_v>>) +constexpr typename expected::error_value_type expected::error_or(G&& def) && { if (!has_val_) - return std::move(unex_); - return static_cast(std::forward(def)); + return std::move(unex_).error(); + return static_cast(std::forward(def)); } // ============================================================================= // [expected.void.monadic] Out-of-line monadic operation definitions // ============================================================================= -template - requires std::is_void_v +template template requires std::is_constructible_v -constexpr auto expected::and_then(F&& f) & { +constexpr auto expected::and_then(F&& f) & { using U = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "and_then: F must return a specialization of expected"); @@ -1911,14 +2049,13 @@ constexpr auto expected::and_then(F&& f) & { "and_then: F must return expected with the same error_type"); if (has_val_) return std::invoke(std::forward(f)); - return U(unexpect, unex_); + return U(unexpect, unex_.error()); } -template - requires std::is_void_v +template template requires std::is_constructible_v -constexpr auto expected::and_then(F&& f) && { +constexpr auto expected::and_then(F&& f) && { using U = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "and_then: F must return a specialization of expected"); @@ -1926,14 +2063,13 @@ constexpr auto expected::and_then(F&& f) && { "and_then: F must return expected with the same error_type"); if (has_val_) return std::invoke(std::forward(f)); - return U(unexpect, std::move(unex_)); + return U(unexpect, std::move(unex_).error()); } -template - requires std::is_void_v +template template requires std::is_constructible_v -constexpr auto expected::and_then(F&& f) const& { +constexpr auto expected::and_then(F&& f) const& { using U = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "and_then: F must return a specialization of expected"); @@ -1941,14 +2077,13 @@ constexpr auto expected::and_then(F&& f) const& { "and_then: F must return expected with the same error_type"); if (has_val_) return std::invoke(std::forward(f)); - return U(unexpect, unex_); + return U(unexpect, unex_.error()); } -template - requires std::is_void_v +template template requires std::is_constructible_v -constexpr auto expected::and_then(F&& f) const&& { +constexpr auto expected::and_then(F&& f) const&& { using U = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "and_then: F must return a specialization of expected"); @@ -1956,66 +2091,61 @@ constexpr auto expected::and_then(F&& f) const&& { "and_then: F must return expected with the same error_type"); if (has_val_) return std::invoke(std::forward(f)); - return U(unexpect, std::move(unex_)); + return U(unexpect, std::move(unex_).error()); } -template - requires std::is_void_v +template template -constexpr auto expected::or_else(F&& f) & { +constexpr auto expected::or_else(F&& f) & { using G = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); - static_assert(std::is_same_v, + static_assert(std::is_same_v, "or_else: F must return expected with the same value_type"); if (has_val_) return G(); - return std::invoke(std::forward(f), unex_); + return std::invoke(std::forward(f), unex_.error()); } -template - requires std::is_void_v +template template -constexpr auto expected::or_else(F&& f) && { +constexpr auto expected::or_else(F&& f) && { using G = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); - static_assert(std::is_same_v, + static_assert(std::is_same_v, "or_else: F must return expected with the same value_type"); if (has_val_) return G(); - return std::invoke(std::forward(f), std::move(unex_)); + return std::invoke(std::forward(f), std::move(unex_).error()); } -template - requires std::is_void_v +template template -constexpr auto expected::or_else(F&& f) const& { +constexpr auto expected::or_else(F&& f) const& { using G = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); - static_assert(std::is_same_v, + static_assert(std::is_same_v, "or_else: F must return expected with the same value_type"); if (has_val_) return G(); - return std::invoke(std::forward(f), unex_); + return std::invoke(std::forward(f), unex_.error()); } -template - requires std::is_void_v +template template -constexpr auto expected::or_else(F&& f) const&& { +constexpr auto expected::or_else(F&& f) const&& { using G = std::remove_cvref_t>; static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); - static_assert(std::is_same_v, + static_assert(std::is_same_v, "or_else: F must return expected with the same value_type"); if (has_val_) return G(); - return std::invoke(std::forward(f), std::move(unex_)); + return std::invoke(std::forward(f), std::move(unex_).error()); } -template - requires std::is_void_v +template template requires std::is_constructible_v -constexpr auto expected::transform(F&& f) & { +constexpr auto expected::transform(F&& f) & { using U = std::remove_cv_t>; if constexpr (!std::is_void_v) { static_assert(!std::is_array_v, "transform: U must not be an array type"); @@ -2029,19 +2159,18 @@ constexpr auto expected::transform(F&& f) & { std::invoke(std::forward(f)); if (has_val_) return expected(); - return expected(unexpect, unex_); + return expected(unexpect, unex_.error()); } else { if (has_val_) return expected(std::invoke(std::forward(f))); - return expected(unexpect, unex_); + return expected(unexpect, unex_.error()); } } -template - requires std::is_void_v +template template requires std::is_constructible_v -constexpr auto expected::transform(F&& f) && { +constexpr auto expected::transform(F&& f) && { using U = std::remove_cv_t>; if constexpr (!std::is_void_v) { static_assert(!std::is_array_v, "transform: U must not be an array type"); @@ -2055,19 +2184,18 @@ constexpr auto expected::transform(F&& f) && { std::invoke(std::forward(f)); if (has_val_) return expected(); - return expected(unexpect, std::move(unex_)); + return expected(unexpect, std::move(unex_).error()); } else { if (has_val_) return expected(std::invoke(std::forward(f))); - return expected(unexpect, std::move(unex_)); + return expected(unexpect, std::move(unex_).error()); } } -template - requires std::is_void_v +template template requires std::is_constructible_v -constexpr auto expected::transform(F&& f) const& { +constexpr auto expected::transform(F&& f) const& { using U = std::remove_cv_t>; if constexpr (!std::is_void_v) { static_assert(!std::is_array_v, "transform: U must not be an array type"); @@ -2081,19 +2209,18 @@ constexpr auto expected::transform(F&& f) const& { std::invoke(std::forward(f)); if (has_val_) return expected(); - return expected(unexpect, unex_); + return expected(unexpect, unex_.error()); } else { if (has_val_) return expected(std::invoke(std::forward(f))); - return expected(unexpect, unex_); + return expected(unexpect, unex_.error()); } } -template - requires std::is_void_v +template template requires std::is_constructible_v -constexpr auto expected::transform(F&& f) const&& { +constexpr auto expected::transform(F&& f) const&& { using U = std::remove_cv_t>; if constexpr (!std::is_void_v) { static_assert(!std::is_array_v, "transform: U must not be an array type"); @@ -2107,18 +2234,17 @@ constexpr auto expected::transform(F&& f) const&& { std::invoke(std::forward(f)); if (has_val_) return expected(); - return expected(unexpect, std::move(unex_)); + return expected(unexpect, std::move(unex_).error()); } else { if (has_val_) return expected(std::invoke(std::forward(f))); - return expected(unexpect, std::move(unex_)); + return expected(unexpect, std::move(unex_).error()); } } -template - requires std::is_void_v +template template -constexpr auto expected::transform_error(F&& f) & { +constexpr auto expected::transform_error(F&& f) & { using G = std::remove_cv_t>; static_assert(std::is_object_v, "transform_error: G must be an object type"); static_assert(!std::is_array_v, "transform_error: G must not be an array type"); @@ -2126,14 +2252,13 @@ constexpr auto expected::transform_error(F&& f) & { static_assert(!detail::is_unexpected_specialization::value, "transform_error: G must not be a specialization of unexpected"); if (has_val_) - return expected(); - return expected(unexpect, std::invoke(std::forward(f), unex_)); + return expected(); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); } -template - requires std::is_void_v +template template -constexpr auto expected::transform_error(F&& f) && { +constexpr auto expected::transform_error(F&& f) && { using G = std::remove_cv_t>; static_assert(std::is_object_v, "transform_error: G must be an object type"); static_assert(!std::is_array_v, "transform_error: G must not be an array type"); @@ -2141,14 +2266,13 @@ constexpr auto expected::transform_error(F&& f) && { static_assert(!detail::is_unexpected_specialization::value, "transform_error: G must not be a specialization of unexpected"); if (has_val_) - return expected(); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_))); + return expected(); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } -template - requires std::is_void_v +template template -constexpr auto expected::transform_error(F&& f) const& { +constexpr auto expected::transform_error(F&& f) const& { using G = std::remove_cv_t>; static_assert(std::is_object_v, "transform_error: G must be an object type"); static_assert(!std::is_array_v, "transform_error: G must not be an array type"); @@ -2156,14 +2280,13 @@ constexpr auto expected::transform_error(F&& f) const& { static_assert(!detail::is_unexpected_specialization::value, "transform_error: G must not be a specialization of unexpected"); if (has_val_) - return expected(); - return expected(unexpect, std::invoke(std::forward(f), unex_)); + return expected(); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); } -template - requires std::is_void_v +template template -constexpr auto expected::transform_error(F&& f) const&& { +constexpr auto expected::transform_error(F&& f) const&& { using G = std::remove_cv_t>; static_assert(std::is_object_v, "transform_error: G must be an object type"); static_assert(!std::is_array_v, "transform_error: G must not be an array type"); @@ -2171,8 +2294,8 @@ constexpr auto expected::transform_error(F&& f) const&& { static_assert(!detail::is_unexpected_specialization::value, "transform_error: G must not be a specialization of unexpected"); if (has_val_) - return expected(); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_))); + return expected(); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } } // namespace expected diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index aba6bc0..b9d2773 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -15,6 +15,7 @@ target_sources( expected_trivial.test.cpp expected_monadic_constraints.test.cpp expected_ref_e.test.cpp + expected_void_ref_e.test.cpp todo.test.cpp ) target_link_libraries( @@ -74,9 +75,15 @@ add_fail_test(expected_ref_e_construct_from_unexpected_fail expected_ref_e_const add_fail_test(expected_emplace_throwing_fail expected_emplace_throwing_fail.cpp) # Step 4 — expected ill-formed E [expected.void.general] -add_fail_test(expected_void_ref_fail expected_void_ref_fail.cpp) add_fail_test(expected_void_array_fail expected_void_array_fail.cpp) +# expected — void + error-reference specialization (via unexpected) +add_fail_test(expected_void_ref_e_const_lvalue_fail expected_void_ref_e_const_lvalue_fail.cpp) +add_fail_test(expected_void_ref_e_no_value_or_fail expected_void_ref_e_no_value_or_fail.cpp) +add_fail_test(expected_void_ref_e_temporary_fail expected_void_ref_e_temporary_fail.cpp) +add_fail_test(expected_void_ref_e_assign_unexpected_fail expected_void_ref_e_assign_unexpected_fail.cpp) +add_fail_test(expected_void_ref_e_construct_from_unexpected_fail expected_void_ref_e_construct_from_unexpected_fail.cpp) + # Step 5 — monadic Mandates [expected.object.monadic] add_fail_test(and_then_wrong_error_type_fail and_then_wrong_error_type_fail.cpp) add_fail_test(and_then_not_expected_fail and_then_not_expected_fail.cpp) diff --git a/tests/beman/expected/expected_void_ref_e.test.cpp b/tests/beman/expected/expected_void_ref_e.test.cpp new file mode 100644 index 0000000..578bd3c --- /dev/null +++ b/tests/beman/expected/expected_void_ref_e.test.cpp @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include +#include + +#include + +#include "testing/types.hpp" + +#include +#include + +using namespace beman::expected; + +// --------------------------------------------------------------------------- +// Type-level assertions +// --------------------------------------------------------------------------- + +static_assert(std::is_default_constructible_v>); +static_assert(std::is_nothrow_default_constructible_v>); + +static_assert(std::is_trivially_copy_constructible_v>); +static_assert(std::is_trivially_move_constructible_v>); +static_assert(std::is_trivially_destructible_v>); +static_assert(std::is_trivially_copyable_v>); + +static_assert(std::is_same_v>().error()), int&>); +static_assert(std::is_same_v>().error()), int&>); + +static_assert(std::is_void_v>())>); + +// absence of operator-> and value_or tested by _fail.cpp negative compile tests + +// --------------------------------------------------------------------------- +// Construction +// --------------------------------------------------------------------------- + +TEST_CASE("expected: default construct has value", "[expected_void_ref_e]") { + expected e; + REQUIRE(e.has_value()); + static_assert(std::is_nothrow_default_constructible_v>); +} + +TEST_CASE("expected: construct from unexpect+ref binds E&", "[expected_void_ref_e]") { + int err = 42; + expected e(unexpect, err); + REQUIRE(!e.has_value()); + CHECK(&e.error() == &err); + CHECK(e.error() == 42); +} + +TEST_CASE("expected: in_place_t constructor", "[expected_void_ref_e]") { + expected e(std::in_place); + CHECK(e.has_value()); + static_assert(noexcept(expected(std::in_place))); +} + +TEST_CASE("expected: copy construct from value state", "[expected_void_ref_e]") { + expected a; + expected b = a; + CHECK(b.has_value()); +} + +TEST_CASE("expected: copy construct from error state", "[expected_void_ref_e]") { + int err = 5; + expected a(unexpect, err); + expected b = a; + REQUIRE(!b.has_value()); + CHECK(&b.error() == &err); +} + +TEST_CASE("expected: move construct copies pointer", "[expected_void_ref_e]") { + int err = 3; + expected a(unexpect, err); + expected b = std::move(a); + REQUIRE(!b.has_value()); + CHECK(&b.error() == &err); +} + +TEST_CASE("expected: convert from expected", "[expected_void_ref_e]") { + int err = 7; + expected src(unexpect, err); + expected dst(src); + REQUIRE(!dst.has_value()); + CHECK(&dst.error() == &err); +} + +// --------------------------------------------------------------------------- +// Error rebind semantics on assignment +// --------------------------------------------------------------------------- + +TEST_CASE("expected: rebind error via copy assign from another expected", "[expected_void_ref_e]") { + int e1 = 1, e2 = 2; + expected a(unexpect, e1); + expected b(unexpect, e2); + a = b; + REQUIRE(!a.has_value()); + CHECK(&a.error() == &e2); + CHECK(e1 == 1); // unchanged — rebind, not assign-through +} + +TEST_CASE("expected: rebind does NOT assign through error reference", "[expected_void_ref_e]") { + int e1 = 100, e2 = 200; + expected a(unexpect, e1); + expected b(unexpect, e2); + a = b; + CHECK(e1 == 100); // e1 unchanged + CHECK(a.error() == 200); +} + +TEST_CASE("expected: assign to value state rebinds error", "[expected_void_ref_e]") { + int err = 99; + expected e; + e = expected(unexpect, err); + REQUIRE(!e.has_value()); + CHECK(&e.error() == &err); +} + +TEST_CASE("expected: assign error state to value state", "[expected_void_ref_e]") { + int err = 99; + expected e(unexpect, err); + e = expected(); + CHECK(e.has_value()); +} + +// --------------------------------------------------------------------------- +// Shallow const on error +// --------------------------------------------------------------------------- + +TEST_CASE("expected: shallow const allows mutation of error referent", "[expected_void_ref_e]") { + int err = 10; + const expected e(unexpect, err); + e.error() = 20; + CHECK(err == 20); +} + +// --------------------------------------------------------------------------- +// emplace() — transition to void state +// --------------------------------------------------------------------------- + +TEST_CASE("expected: emplace from error state sets has_value", "[expected_void_ref_e]") { + int err = 5; + expected e(unexpect, err); + e.emplace(); + CHECK(e.has_value()); + static_assert(noexcept(e.emplace())); +} + +TEST_CASE("expected: emplace from value state is no-op", "[expected_void_ref_e]") { + expected e; + e.emplace(); + CHECK(e.has_value()); +} + +// --------------------------------------------------------------------------- +// Observers +// --------------------------------------------------------------------------- + +TEST_CASE("expected: operator bool and has_value", "[expected_void_ref_e]") { + expected a; + int err = 0; + expected b(unexpect, err); + CHECK(a.has_value()); + CHECK(bool(a)); + CHECK(!b.has_value()); + CHECK(!bool(b)); +} + +TEST_CASE("expected: operator*() is void no-op", "[expected_void_ref_e]") { + expected e; + static_assert(std::is_void_v); + *e; +} + +TEST_CASE("expected: value() on success is no-op", "[expected_void_ref_e]") { + expected e; + e.value(); +} + +TEST_CASE("expected: value() throws bad_expected_access on error", "[expected_void_ref_e]") { + int err = 7; + expected e(unexpect, err); + REQUIRE_THROWS_AS(e.value(), beman::expected::bad_expected_access); +} + +TEST_CASE("expected: rvalue value() throws on error", "[expected_void_ref_e]") { + int err = 3; + expected e(unexpect, err); + REQUIRE_THROWS_AS(std::move(e).value(), beman::expected::bad_expected_access); +} + +TEST_CASE("expected: error() returns E& with correct address", "[expected_void_ref_e]") { + int err = 99; + expected e(unexpect, err); + static_assert(std::is_same_v); + CHECK(&e.error() == &err); +} + +TEST_CASE("expected: error_or returns E by value", "[expected_void_ref_e]") { + int err = 7; + expected a(unexpect, err); + expected b; + CHECK(a.error_or(0) == 7); + CHECK(b.error_or(42) == 42); +} + +// --------------------------------------------------------------------------- +// Swap +// --------------------------------------------------------------------------- + +TEST_CASE("expected: swap value-value (no-op)", "[expected_void_ref_e]") { + expected a, b; + a.swap(b); + CHECK(a.has_value()); + CHECK(b.has_value()); +} + +TEST_CASE("expected: swap value-error", "[expected_void_ref_e]") { + int err = 42; + expected a, b(unexpect, err); + a.swap(b); + REQUIRE(!a.has_value()); + REQUIRE(b.has_value()); + CHECK(&a.error() == &err); +} + +TEST_CASE("expected: swap error-value", "[expected_void_ref_e]") { + int err = 5; + expected a(unexpect, err), b; + a.swap(b); + REQUIRE(a.has_value()); + REQUIRE(!b.has_value()); + CHECK(&b.error() == &err); +} + +TEST_CASE("expected: swap error-error rebinds pointers", "[expected_void_ref_e]") { + int e1 = 1, e2 = 2; + expected a(unexpect, e1), b(unexpect, e2); + a.swap(b); + CHECK(&a.error() == &e2); + CHECK(&b.error() == &e1); + CHECK(e1 == 1); + CHECK(e2 == 2); +} + +// --------------------------------------------------------------------------- +// Equality +// --------------------------------------------------------------------------- + +TEST_CASE("expected: equality both have values", "[expected_void_ref_e]") { + expected a, b; + CHECK(a == b); +} + +TEST_CASE("expected: equality both have errors (same value)", "[expected_void_ref_e]") { + int e1 = 5, e2 = 5; + expected a(unexpect, e1), b(unexpect, e2); + CHECK(a == b); +} + +TEST_CASE("expected: equality mixed value/error", "[expected_void_ref_e]") { + expected a; + int err = 0; + expected b(unexpect, err); + CHECK(!(a == b)); +} + +TEST_CASE("expected: equality with unexpected", "[expected_void_ref_e]") { + int err = 7; + expected e(unexpect, err); + CHECK(e == unexpected(7)); + CHECK(!(e == unexpected(8))); +} + +// --------------------------------------------------------------------------- +// Monadic operations — void value + reference error +// --------------------------------------------------------------------------- + +TEST_CASE("expected: and_then calls F with no args", "[expected_void_ref_e]") { + expected e; + int calls = 0; + auto r = e.and_then([&]() -> expected { + ++calls; + return 42; + }); + CHECK(calls == 1); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("expected: and_then short-circuits on error", "[expected_void_ref_e]") { + int err = 3; + expected e(unexpect, err); + bool called = false; + auto r = e.and_then([&]() -> expected { + called = true; + return 0; + }); + CHECK(!called); + REQUIRE(!r.has_value()); + CHECK(&r.error() == &err); +} + +TEST_CASE("expected: or_else receives E& and can return success", "[expected_void_ref_e]") { + int err = 5; + expected e(unexpect, err); + auto r = e.or_else([](int& v) -> expected { + (void)v; + return {}; + }); + CHECK(r.has_value()); +} + +TEST_CASE("expected: or_else propagates E& to F", "[expected_void_ref_e]") { + int err = 5; + expected e(unexpect, err); + int* seen = nullptr; + auto r = e.or_else([&](int& v) -> expected { + seen = &v; + return expected(unexpect, v); + }); + CHECK(seen == &err); + REQUIRE(!r.has_value()); + CHECK(r.error() == 5); +} + +TEST_CASE("expected: or_else short-circuits on success", "[expected_void_ref_e]") { + expected e; + bool called = false; + int dummy = 0; + auto r = e.or_else([&](int&) -> expected { + called = true; + return expected(unexpect, dummy); + }); + CHECK(!called); + CHECK(r.has_value()); +} + +TEST_CASE("expected: transform calls F with no args", "[expected_void_ref_e]") { + expected e; + auto r = e.transform([]() { return 42; }); + static_assert(std::is_same_v>); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("expected: transform with void-returning F", "[expected_void_ref_e]") { + expected e; + int count = 0; + auto r = e.transform([&]() { ++count; }); + static_assert(std::is_same_v>); + CHECK(r.has_value()); + CHECK(count == 1); +} + +TEST_CASE("expected: transform short-circuits on error", "[expected_void_ref_e]") { + int err = 9; + expected e(unexpect, err); + bool called = false; + auto r = e.transform([&]() -> int { + called = true; + return 0; + }); + CHECK(!called); + REQUIRE(!r.has_value()); + CHECK(&r.error() == &err); +} + +TEST_CASE("expected: transform_error transforms E& to new type", "[expected_void_ref_e]") { + int err = 3; + expected e(unexpect, err); + auto r = e.transform_error([](int& v) -> std::string { return std::to_string(v); }); + static_assert(std::is_same_v>); + REQUIRE(!r.has_value()); + CHECK(r.error() == "3"); +} + +TEST_CASE("expected: transform_error with value short-circuits", "[expected_void_ref_e]") { + expected e; + bool called = false; + auto r = e.transform_error([&](int&) -> std::string { + called = true; + return ""; + }); + CHECK(!called); + CHECK(r.has_value()); +} + +// --------------------------------------------------------------------------- +// End-to-end chaining +// --------------------------------------------------------------------------- + +TEST_CASE("expected: monadic chaining happy path", "[expected_void_ref_e]") { + auto r = expected{}.and_then([]() -> expected { return 42; }).transform([](int v) { + return v * 2; + }); + REQUIRE(r.has_value()); + CHECK(*r == 84); +} + +TEST_CASE("expected: monadic chaining error path", "[expected_void_ref_e]") { + int err = 0; + auto r = expected(unexpect, err) + .and_then([]() -> expected { return 0; }) + .transform_error([](int& v) -> std::string { return std::to_string(v); }); + REQUIRE(!r.has_value()); +} + +// --------------------------------------------------------------------------- +// Triviality +// --------------------------------------------------------------------------- + +TEST_CASE("expected: trivial operations", "[expected_void_ref_e]") { + static_assert(std::is_trivially_copyable_v>); + static_assert(std::is_trivially_destructible_v>); +} + +// ============================================================================= +// Additional coverage tests: void,E& rvalue/const-rvalue monadic paths +// ============================================================================= +using namespace beman::expected::testing; + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- +TEST_CASE("and_then rvalue on error short-circuits", "[expected_void_ref_e]") { + int err = 5; + expected e(unexpect, err); + auto r = std::move(e).and_then([]() -> expected { return {}; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("and_then const rvalue on error short-circuits", "[expected_void_ref_e]") { + int err = 5; + const expected e(unexpect, err); + auto r = std::move(e).and_then([]() -> expected { return {}; }); + REQUIRE(!r.has_value()); +} + +// --- Monadic: and_then rvalue/const-rvalue on VALUE state --- +TEST_CASE("and_then rvalue on value calls F", "[expected_void_ref_e]") { + expected e; + bool called = false; + auto r = std::move(e).and_then([&]() -> expected { + called = true; + return 42; + }); + CHECK(called); + REQUIRE(r.has_value()); +} + +TEST_CASE("and_then const rvalue on value calls F", "[expected_void_ref_e]") { + const expected e; + auto r = std::move(e).and_then([]() -> expected { return 99; }); + REQUIRE(r.has_value()); + CHECK(*r == 99); +} + +// --- Monadic: or_else rvalue/const-rvalue on VALUE state --- +TEST_CASE("or_else rvalue on value short-circuits", "[expected_void_ref_e]") { + expected e; + auto r = std::move(e).or_else([](int&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("or_else const rvalue on value short-circuits", "[expected_void_ref_e]") { + const expected e; + auto r = std::move(e).or_else([](int&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +// --- Monadic: transform rvalue/const-rvalue --- +TEST_CASE("transform rvalue on error short-circuits", "[expected_void_ref_e]") { + int err = 3; + expected e(unexpect, err); + auto r = std::move(e).transform([]() { return 1; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("transform const rvalue on error short-circuits", "[expected_void_ref_e]") { + int err = 3; + const expected e(unexpect, err); + auto r = std::move(e).transform([]() { return 1; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("transform rvalue on value calls F", "[expected_void_ref_e]") { + expected e; + auto r = std::move(e).transform([]() { return 42; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("transform const rvalue on value calls F", "[expected_void_ref_e]") { + const expected e; + auto r = std::move(e).transform([]() { return 7; }); + REQUIRE(r.has_value()); + CHECK(*r == 7); +} + +// --- Monadic: transform_error rvalue/const-rvalue --- +TEST_CASE("transform_error rvalue on value short-circuits", "[expected_void_ref_e]") { + expected e; + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("transform_error const rvalue on value short-circuits", "[expected_void_ref_e]") { + const expected e; + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("transform_error rvalue on error calls F", "[expected_void_ref_e]") { + int err = 5; + expected e(unexpect, err); + auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 6); +} + +TEST_CASE("transform_error const rvalue on error calls F", "[expected_void_ref_e]") { + int err = 5; + const expected e(unexpect, err); + auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 6); +} + +// --- value() throw paths --- +TEST_CASE("value() throws on error state", "[expected_void_ref_e]") { + int err = 42; + expected e(unexpect, err); + CHECK_THROWS_AS(e.value(), beman::expected::bad_expected_access); +} + +TEST_CASE("value() rvalue throws on error state", "[expected_void_ref_e]") { + int err = 42; + expected e(unexpect, err); + CHECK_THROWS_AS(std::move(e).value(), beman::expected::bad_expected_access); +} + +// --- Cross-type equality --- +TEST_CASE("equality error vs value", "[expected_void_ref_e]") { + int err = 0; + expected a; + expected b(unexpect, err); + CHECK_FALSE(a == b); +} + +// --------------------------------------------------------------------------- +// expected: lvalue monadic paths +// --------------------------------------------------------------------------- + +TEST_CASE("ref: and_then lvalue error path", "[expected_void_ref_e][traced]") { + int err = 5; + expected e(unexpect, err); + auto r = e.and_then([]() -> expected { return {}; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("ref: and_then const lvalue error path", "[expected_void_ref_e][traced]") { + int err = 5; + const expected e(unexpect, err); + auto r = e.and_then([]() -> expected { return {}; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("ref: or_else lvalue value path", "[expected_void_ref_e][traced]") { + expected e; + auto r = e.or_else([](int&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: or_else rvalue value path", "[expected_void_ref_e][traced]") { + expected e; + auto r = std::move(e).or_else([](int&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: or_else const rvalue value path", "[expected_void_ref_e][traced]") { + const expected e; + auto r = std::move(e).or_else([](int&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform lvalue value path", "[expected_void_ref_e][traced]") { + expected e; + auto r = e.transform([]() { return 42; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("ref: transform lvalue error path", "[expected_void_ref_e][traced]") { + int err = 3; + expected e(unexpect, err); + auto r = e.transform([]() { return 0; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("ref: transform rvalue value path", "[expected_void_ref_e][traced]") { + expected e; + auto r = std::move(e).transform([]() { return 42; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform const rvalue value path", "[expected_void_ref_e][traced]") { + const expected e; + auto r = std::move(e).transform([]() { return 42; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform to void lvalue", "[expected_void_ref_e][traced]") { + expected e; + auto r = e.transform([]() {}); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error lvalue value path", "[expected_void_ref_e][traced]") { + expected e; + auto r = e.transform_error([](int&) { return 0; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error rvalue value path", "[expected_void_ref_e][traced]") { + expected e; + auto r = std::move(e).transform_error([](int&) { return 0; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error const rvalue value path", "[expected_void_ref_e][traced]") { + const expected e; + auto r = std::move(e).transform_error([](int&) { return 0; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: value() lvalue throws", "[expected_void_ref_e][traced]") { + int err = 42; + expected e(unexpect, err); + CHECK_THROWS_AS(e.value(), beman::expected::bad_expected_access); +} diff --git a/tests/beman/expected/expected_void_ref_e_assign_unexpected_fail.cpp b/tests/beman/expected/expected_void_ref_e_assign_unexpected_fail.cpp new file mode 100644 index 0000000..0ed7a19 --- /dev/null +++ b/tests/beman/expected/expected_void_ref_e_assign_unexpected_fail.cpp @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected cannot be assigned from unexpected. +// No operator=(unexpected...) exists: binding E& to temporary unexpected +// storage would create a dangling reference. +// EXPECT: "no matching function" +#include +using namespace beman::expected; +void test() { + expected e; + e = unexpected(7); // must not compile +} diff --git a/tests/beman/expected/expected_void_ref_e_const_lvalue_fail.cpp b/tests/beman/expected/expected_void_ref_e_const_lvalue_fail.cpp new file mode 100644 index 0000000..5e4b652 --- /dev/null +++ b/tests/beman/expected/expected_void_ref_e_const_lvalue_fail.cpp @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: cannot bind const lvalue to non-const E& +#include +void test() { + const int err = 5; + beman::expected::expected e(beman::expected::unexpect, err); +} diff --git a/tests/beman/expected/expected_void_ref_e_construct_from_unexpected_fail.cpp b/tests/beman/expected/expected_void_ref_e_construct_from_unexpected_fail.cpp new file mode 100644 index 0000000..b37eea7 --- /dev/null +++ b/tests/beman/expected/expected_void_ref_e_construct_from_unexpected_fail.cpp @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected cannot be constructed from unexpected. +// A value-typed unexpected stores G by value; binding E& to it would create a +// dangling reference when the unexpected object is destroyed. Construction from a +// reference-typed unexpected (which holds an external object) is allowed. +// EXPECT: "no matching function" +#include +using namespace beman::expected; +void test() { + expected e = unexpected(7); // must not compile +} diff --git a/tests/beman/expected/expected_void_ref_e_no_value_or_fail.cpp b/tests/beman/expected/expected_void_ref_e_no_value_or_fail.cpp new file mode 100644 index 0000000..d4ff123 --- /dev/null +++ b/tests/beman/expected/expected_void_ref_e_no_value_or_fail.cpp @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected has no value_or member +#include +void test() { + beman::expected::expected e; + e.value_or(0); +} diff --git a/tests/beman/expected/expected_void_ref_e_temporary_fail.cpp b/tests/beman/expected/expected_void_ref_e_temporary_fail.cpp new file mode 100644 index 0000000..3e2619b --- /dev/null +++ b/tests/beman/expected/expected_void_ref_e_temporary_fail.cpp @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: cannot bind temporary to E& — dangling prevention +#include +void test() { beman::expected::expected e(beman::expected::unexpect, 42); } diff --git a/tests/beman/expected/expected_void_ref_fail.cpp b/tests/beman/expected/expected_void_ref_fail.cpp deleted file mode 100644 index 41bffaa..0000000 --- a/tests/beman/expected/expected_void_ref_fail.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// Negative compile test: expected where E is a reference is ill-formed. -#include - -beman::expected::expected x; // should fail From db1c54b5374b0c9142639180df26b5548d8beb31 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 20:53:12 -0400 Subject: [PATCH 04/15] feat: add expected reference specialization (covers T&,E and T&,E&) Add the expected partial specialization: value is a reference, stored as a pointer, with the error stored as unexpected so E may also be a reference (this single specialization covers both expected and expected). Out-of-line definitions throughout, dangling-temporary prevention via reference_constructs_from_temporary_v, shallow move-then- deref conversions, and the full monadic surface. Brings in the reference value/both-reference test groups, the shared testing/types.hpp, and the cross-cutting review-corrections regression suite. Removes the todo.test.cpp scaffolding. Header and tests now match the release state. --- include/beman/expected/expected.hpp | 1033 +++++++++++++++++ tests/beman/expected/CMakeLists.txt | 281 ++++- .../expected/and_then_not_expected_fail.cpp | 1 + .../and_then_wrong_error_type_fail.cpp | 2 +- .../expected/bad_expected_access.test.cpp | 18 + tests/beman/expected/expected.test.cpp | 414 +++++++ ...ted_bool_value_ctor_from_expected_fail.cpp | 5 +- .../expected_emplace_throwing_fail.cpp | 4 +- .../beman/expected/expected_monadic.test.cpp | 252 ++++ tests/beman/expected/expected_ref.test.cpp | 890 ++++++++++++++ ...ted_ref_and_then_wrong_error_type_fail.cpp | 13 + .../beman/expected/expected_ref_both.test.cpp | 686 +++++++++++ ...ef_both_and_then_wrong_error_type_fail.cpp | 10 + ...pected_ref_both_assign_unexpected_fail.cpp | 13 + ...ef_both_construct_from_unexpected_fail.cpp | 11 + .../expected_ref_both_e_array_fail.cpp | 9 + .../expected_ref_both_inplace_value_fail.cpp | 9 + .../expected_ref_both_no_default_fail.cpp | 9 + ...ref_both_or_else_wrong_value_type_fail.cpp | 15 + .../expected_ref_both_t_array_fail.cpp | 9 + .../expected_ref_both_t_inplace_fail.cpp | 10 + .../expected_ref_both_t_unexpect_fail.cpp | 9 + .../expected_ref_both_t_unexpected_fail.cpp | 9 + .../expected_ref_both_temporary_err_fail.cpp | 9 + .../expected_ref_both_temporary_val_fail.cpp | 9 + ...f_both_transform_error_ref_result_fail.cpp | 10 + .../expected_ref_constraints.test.cpp | 259 +++++ .../expected/expected_ref_e_array_fail.cpp | 9 + .../beman/expected/expected_ref_e_cv_fail.cpp | 9 + .../expected/expected_ref_e_ref_fail.cpp | 9 + .../expected/expected_ref_e_void_fail.cpp | 9 + .../expected_ref_inplace_value_fail.cpp | 9 + .../expected/expected_ref_no_default_fail.cpp | 8 + ...cted_ref_or_else_wrong_value_type_fail.cpp | 15 + .../expected/expected_ref_temporary_fail.cpp | 8 + ...ed_ref_transform_error_ref_result_fail.cpp | 10 + .../expected_review_corrections.test.cpp | 262 +++++ .../beman/expected/expected_t_array_fail.cpp | 4 +- tests/beman/expected/expected_t_ref_fail.cpp | 4 +- .../expected_unexpected_value_type_fail.cpp | 3 +- tests/beman/expected/expected_void.test.cpp | 79 ++ .../expected/expected_void_array_fail.cpp | 3 +- .../expected/expected_void_monadic.test.cpp | 251 ++++ .../or_else_wrong_value_type_fail.cpp | 1 + tests/beman/expected/todo.test.cpp | 10 - .../transform_error_ref_result_fail.cpp | 1 + .../beman/expected/unexpected_array_fail.cpp | 4 +- .../beman/expected/unexpected_cvref_fail.cpp | 4 +- tests/beman/expected/unexpected_self_fail.cpp | 4 +- .../void_and_then_wrong_error_type_fail.cpp | 3 +- .../void_or_else_wrong_value_type_fail.cpp | 3 +- 51 files changed, 4652 insertions(+), 69 deletions(-) create mode 100644 tests/beman/expected/expected_ref.test.cpp create mode 100644 tests/beman/expected/expected_ref_and_then_wrong_error_type_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both.test.cpp create mode 100644 tests/beman/expected/expected_ref_both_and_then_wrong_error_type_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_assign_unexpected_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_construct_from_unexpected_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_e_array_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_inplace_value_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_no_default_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_or_else_wrong_value_type_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_t_array_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_t_inplace_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_t_unexpect_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_t_unexpected_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_temporary_err_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_temporary_val_fail.cpp create mode 100644 tests/beman/expected/expected_ref_both_transform_error_ref_result_fail.cpp create mode 100644 tests/beman/expected/expected_ref_constraints.test.cpp create mode 100644 tests/beman/expected/expected_ref_e_array_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_cv_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_ref_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_void_fail.cpp create mode 100644 tests/beman/expected/expected_ref_inplace_value_fail.cpp create mode 100644 tests/beman/expected/expected_ref_no_default_fail.cpp create mode 100644 tests/beman/expected/expected_ref_or_else_wrong_value_type_fail.cpp create mode 100644 tests/beman/expected/expected_ref_temporary_fail.cpp create mode 100644 tests/beman/expected/expected_ref_transform_error_ref_result_fail.cpp create mode 100644 tests/beman/expected/expected_review_corrections.test.cpp delete mode 100644 tests/beman/expected/todo.test.cpp diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 5d9d0e3..47d9693 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -2298,6 +2298,1039 @@ constexpr auto expected::transform_error(F&& f) const&& { return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } +// ============================================================================= +// Partial specialization: expected — reference value type +// (E may be an object type or an lvalue reference to one) +// ============================================================================= + +template +class expected { + static_assert(!std::is_array_v, "T must not be an array type"); + static_assert(std::is_object_v, "T must be an object type"); + static_assert(!std::is_same_v, std::in_place_t>, "T must not be in_place_t"); + static_assert(!std::is_same_v, unexpect_t>, "T must not be unexpect_t"); + static_assert(!detail::is_unexpected_specialization>::value, + "T must not be a specialization of unexpected"); + static_assert(!std::is_rvalue_reference_v, "E must not be an rvalue reference"); + static_assert(!std::is_void_v>, "E must not be void"); + static_assert(!std::is_array_v>, "E must not be an array type"); + static_assert(std::is_object_v>, "E must be an object type"); + static_assert(std::is_reference_v || std::is_same_v, E>, "E must not be cv-qualified"); + + private: + using error_value_type = std::remove_cv_t>; + + public: + using value_type = T&; + using error_type = E; + using unexpected_type = unexpected; + + template + using rebind = expected; + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + expected() = delete; + + // Copy constructor (trivial path) + constexpr expected(const expected&) + requires std::is_trivially_copy_constructible_v> + = default; + + // Copy constructor (non-trivial path) + constexpr expected(const expected& rhs) noexcept(std::is_nothrow_copy_constructible_v>) + requires(std::is_copy_constructible_v> && !std::is_trivially_copy_constructible_v>); + + // Move constructor (trivial path) + constexpr expected(expected&&) noexcept + requires std::is_trivially_move_constructible_v> + = default; + + // Move constructor (non-trivial path) + constexpr expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v>) + requires(std::is_move_constructible_v> && !std::is_trivially_move_constructible_v>); + + // Deleted: no in-place value constructor — T& cannot be constructed in-place + template + constexpr expected(std::in_place_t, Args&&...) = delete; + + // Value constructor — takes U that can bind to T& + template + requires(!std::is_same_v, std::in_place_t> && + !std::is_same_v, expected> && + !detail::is_unexpected_specialization>::value && + std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) expected(U&& u) noexcept( + std::is_nothrow_constructible_v) + : has_val_(true) { + T& r = std::forward(u); + val_ = std::addressof(r); + } + + // Deleted: binding a temporary to T& creates a dangling reference + template + requires(detail::reference_constructs_from_temporary_v) + constexpr expected(U&&) = delete; + + // Converting constructor from expected (copy) — value-E path + template + requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) + expected(const expected& rhs); + + // Converting constructor from expected (move) — value-E path + template + requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) expected(expected&& rhs); + + // Converting constructor from expected (copy/move) — reference-E path: only accepts + // sources whose error type G is itself a reference convertible to E. + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + std::is_convertible_v && !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) + expected(const expected& rhs); + + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + std::is_convertible_v && !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) + expected(expected&& rhs); + + // Constructor from unexpected const& / && — value-E path + template + requires(!std::is_reference_v && std::is_constructible_v) + constexpr explicit(!std::is_convertible_v) expected(const unexpected& e); + + template + requires(!std::is_reference_v && std::is_constructible_v) + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e); + + // Constructor from unexpected — reference-E path. Allowed only when G is itself a reference, + // i.e. the source unexpected holds a reference to an external object, so binding E& to e.error() + // cannot dangle regardless of the source's value category. No const_cast is needed. + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) noexcept; + + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) noexcept; + + // Deleted for reference E with value G: the referent lives inside the unexpected object, so + // binding E& to it would dangle once a temporary source is destroyed. Use (unexpect, lvalue), or + // an unexpected holding an external object, instead. + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected(const unexpected&) = delete; + + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected(unexpected&&) = delete; + + // In-place constructor for error + template + requires(std::is_constructible_v && !detail::unexpect_dangles_v) + constexpr explicit expected(unexpect_t, Args&&... args); + + // Deleted: single argument would bind E& to a temporary — dangling prevention + template + requires(detail::unexpect_dangles_v) + constexpr expected(unexpect_t, Args&&...) = delete; + + // Deleted catch-all: reference E, argument neither constructible nor a dangling case + template + requires(std::is_reference_v && !std::is_constructible_v && + !detail::unexpect_dangles_v) + constexpr expected(unexpect_t, Args&&...) = delete; + + // In-place constructor for error with initializer_list + template + requires std::is_constructible_v&, Args...> + constexpr explicit expected(unexpect_t, std::initializer_list il, Args&&... args); + + // ------------------------------------------------------------------------- + // Destructor + // ------------------------------------------------------------------------- + + constexpr ~expected() + requires std::is_trivially_destructible_v> + = default; + + constexpr ~expected() + requires(!std::is_trivially_destructible_v>); + + // ------------------------------------------------------------------------- + // Assignment (rebind semantics) + // ------------------------------------------------------------------------- + + // Copy assignment (trivial path) + constexpr expected& operator=(const expected&) + requires(std::is_trivially_copy_constructible_v> && + std::is_trivially_copy_assignable_v> && + std::is_trivially_destructible_v>) + = default; + + // Copy assignment (non-trivial path) + constexpr expected& operator=(const expected& rhs) + requires(std::is_copy_constructible_v> && std::is_copy_assignable_v> && + !(std::is_trivially_copy_constructible_v> && + std::is_trivially_copy_assignable_v> && + std::is_trivially_destructible_v>)); + + // Move assignment (trivial path) + constexpr expected& operator=(expected&&) noexcept + requires(std::is_trivially_move_constructible_v> && + std::is_trivially_move_assignable_v> && + std::is_trivially_destructible_v>) + = default; + + // Move assignment (non-trivial path) + constexpr expected& operator=(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v> && + std::is_nothrow_move_assignable_v>) + requires(std::is_move_constructible_v> && std::is_move_assignable_v> && + !(std::is_trivially_move_constructible_v> && + std::is_trivially_move_assignable_v> && + std::is_trivially_destructible_v>)); + + // Rebind reference from lvalue + template + requires(!std::is_same_v, expected> && + !detail::is_unexpected_specialization>::value && + std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr expected& operator=(U&& u) { + if (has_val_) { + T& r = std::forward(u); + val_ = std::addressof(r); + } else { + T& r = std::forward(u); // bind first: if it throws, the error state is left intact + std::destroy_at(std::addressof(unex_)); + val_ = std::addressof(r); + has_val_ = true; + } + return *this; + } + + // Assignment from unexpected — value-E path + template + requires(!std::is_reference_v && std::is_constructible_v && + std::is_assignable_v) + constexpr expected& operator=(const unexpected& e); + + template + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) + constexpr expected& operator=(unexpected&& e); + + // Rebinding assignment for reference E from reference G — binds unex_ to the external referent. + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr expected& operator=(const unexpected& e); + + template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr expected& operator=(unexpected&& e); + + // Deleted for reference E with value G: would rebind E& to unexpected's temporary storage. + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected& operator=(const unexpected&) = delete; + + template + requires(std::is_reference_v && !std::is_reference_v) + constexpr expected& operator=(unexpected&&) = delete; + + // emplace — rebind the reference + template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr T& emplace(U&& u) noexcept(std::is_nothrow_constructible_v); + + // ------------------------------------------------------------------------- + // Swap + // ------------------------------------------------------------------------- + + constexpr void swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v> && + (std::is_reference_v || std::is_nothrow_swappable_v)) + requires((std::is_reference_v || std::is_swappable_v) && std::is_move_constructible_v>); + + friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } + + // ------------------------------------------------------------------------- + // Observers + // ------------------------------------------------------------------------- + + constexpr T* operator->() const noexcept; + constexpr T& operator*() const noexcept; + + constexpr explicit operator bool() const noexcept; + constexpr bool has_value() const noexcept; + + constexpr T& value() const&; + constexpr T& value() &&; + + // error() — shallow const: always returns E& regardless of const on expected + constexpr const E& error() const& noexcept; + constexpr E& error() & noexcept; + constexpr const E&& error() const&& noexcept; + constexpr E&& error() && noexcept; + + template > + requires(std::is_object_v && !std::is_array_v) + constexpr std::remove_cv_t value_or(U&& def) const; + + template + requires(std::is_copy_constructible_v && std::is_convertible_v) + constexpr error_value_type error_or(G&& def) const&; + + template + requires(std::is_move_constructible_v && std::is_convertible_v) + constexpr error_value_type error_or(G&& def) &&; + + // ------------------------------------------------------------------------- + // Monadic operations + // ------------------------------------------------------------------------- + + template + requires std::is_constructible_v + constexpr auto and_then(F&& f) &; + template + requires std::is_constructible_v + constexpr auto and_then(F&& f) &&; + template + requires std::is_constructible_v + constexpr auto and_then(F&& f) const&; + template + requires std::is_constructible_v + constexpr auto and_then(F&& f) const&&; + + template + constexpr auto or_else(F&& f) &; + template + constexpr auto or_else(F&& f) &&; + template + constexpr auto or_else(F&& f) const&; + template + constexpr auto or_else(F&& f) const&&; + + // transform: f receives T& (value); error propagates as E; result is expected + template + requires std::is_constructible_v + constexpr auto transform(F&& f) &; + template + requires std::is_constructible_v + constexpr auto transform(F&& f) &&; + template + requires std::is_constructible_v + constexpr auto transform(F&& f) const&; + template + requires std::is_constructible_v + constexpr auto transform(F&& f) const&&; + + // transform_error: f receives E; value propagates as T&; result is expected + template + constexpr auto transform_error(F&& f) &; + template + constexpr auto transform_error(F&& f) &&; + template + constexpr auto transform_error(F&& f) const&; + template + constexpr auto transform_error(F&& f) const&&; + + // ------------------------------------------------------------------------- + // Equality operators (hidden friends) + // ------------------------------------------------------------------------- + + template + requires(!std::is_void_v) + friend constexpr bool operator==(const expected& x, const expected& y) { + if (x.has_value() != y.has_value()) + return false; + if (x.has_value()) + return *x == *y; + return x.error() == y.error(); + } + + template + requires(!detail::is_expected_specialization::value) + friend constexpr bool operator==(const expected& x, const T2& val) { + return x.has_value() && static_cast(*x == val); + } + + template + friend constexpr bool operator==(const expected& x, const unexpected& e) { + return !x.has_value() && static_cast(x.error() == e.error()); + } + + private: + bool has_val_; + union { + T* val_; + unexpected unex_; + }; +}; + +// ============================================================================= +// Out-of-line constructor definitions +// ============================================================================= + +template +constexpr expected::expected(const expected& rhs) noexcept(std::is_nothrow_copy_constructible_v>) + requires(std::is_copy_constructible_v> && !std::is_trivially_copy_constructible_v>) + : has_val_(rhs.has_val_) { + if (has_val_) + val_ = rhs.val_; + else + std::construct_at(std::addressof(unex_), rhs.unex_); +} + +template +constexpr expected::expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v>) + requires(std::is_move_constructible_v> && !std::is_trivially_move_constructible_v>) + : has_val_(rhs.has_val_) { + if (has_val_) + val_ = rhs.val_; + else + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); +} + + +template +template + requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(const expected& rhs) : has_val_(rhs.has_value()) { + if (has_val_) { + T& r = *rhs; + val_ = std::addressof(r); + } else { + std::construct_at(std::addressof(unex_), rhs.error()); + } +} + +template +template + requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(expected&& rhs) : has_val_(rhs.has_value()) { + if (has_val_) { + T& r = *rhs; + val_ = std::addressof(r); + } else { + std::construct_at(std::addressof(unex_), std::move(rhs).error()); + } +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + std::is_convertible_v && !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(const expected& rhs) : has_val_(rhs.has_value()) { + if (has_val_) { + T& r = *rhs; + val_ = std::addressof(r); + } else { + std::construct_at(std::addressof(unex_), rhs.error()); + } +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + std::is_convertible_v && !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(expected&& rhs) : has_val_(rhs.has_value()) { + if (has_val_) { + T& r = *rhs; + val_ = std::addressof(r); + } else { + std::construct_at(std::addressof(unex_), rhs.error()); + } +} + +template +template + requires(!std::is_reference_v && std::is_constructible_v) +constexpr expected::expected(const unexpected& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); +} + +template +template + requires(!std::is_reference_v && std::is_constructible_v) +constexpr expected::expected(unexpected&& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::move(e).error()); +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(const unexpected& e) noexcept : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(unexpected&& e) noexcept : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); +} + +template +template + requires(std::is_constructible_v && !detail::unexpect_dangles_v) +constexpr expected::expected(unexpect_t, Args&&... args) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::in_place, std::forward(args)...); +} + +template +template + requires std::is_constructible_v&, Args...> +constexpr expected::expected(unexpect_t, std::initializer_list il, Args&&... args) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::in_place, il, std::forward(args)...); +} + +// ============================================================================= +// Out-of-line destructor +// ============================================================================= + +template +constexpr expected::~expected() + requires(!std::is_trivially_destructible_v>) +{ + if (!has_val_) + std::destroy_at(std::addressof(unex_)); +} + +// ============================================================================= +// Out-of-line assignment definitions +// ============================================================================= + +template +constexpr expected& expected::operator=(const expected& rhs) + requires(std::is_copy_constructible_v> && std::is_copy_assignable_v> && + !(std::is_trivially_copy_constructible_v> && + std::is_trivially_copy_assignable_v> && std::is_trivially_destructible_v>)) +{ + if (has_val_ && rhs.has_val_) { + val_ = rhs.val_; + } else if (!has_val_ && !rhs.has_val_) { + unex_ = rhs.unex_; + } else if (has_val_) { + std::construct_at(std::addressof(unex_), rhs.unex_); + has_val_ = false; + } else { + std::destroy_at(std::addressof(unex_)); + val_ = rhs.val_; + has_val_ = true; + } + return *this; +} + +template +constexpr expected& +expected::operator=(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v> && + std::is_nothrow_move_assignable_v>) + requires(std::is_move_constructible_v> && std::is_move_assignable_v> && + !(std::is_trivially_move_constructible_v> && + std::is_trivially_move_assignable_v> && std::is_trivially_destructible_v>)) +{ + if (has_val_ && rhs.has_val_) { + val_ = rhs.val_; + } else if (!has_val_ && !rhs.has_val_) { + unex_ = std::move(rhs.unex_); + } else if (has_val_) { + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); + has_val_ = false; + } else { + std::destroy_at(std::addressof(unex_)); + val_ = rhs.val_; + has_val_ = true; + } + return *this; +} + + +template +template + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) +constexpr expected& expected::operator=(const unexpected& e) { + if (!has_val_) { + unex_.error() = e.error(); + } else { + std::construct_at(std::addressof(unex_), e.error()); + has_val_ = false; + } + return *this; +} + +template +template + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) +constexpr expected& expected::operator=(unexpected&& e) { + if (!has_val_) { + unex_.error() = std::move(e).error(); + } else { + std::construct_at(std::addressof(unex_), std::move(e).error()); + has_val_ = false; + } + return *this; +} + +// Rebinding assignment for reference E from reference G. val_ is a T* (trivially destructible), +// so no destroy is needed; repoint unex_ via construct_at (not `unex_.error() = ...`). +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected& expected::operator=(const unexpected& e) { + std::construct_at(std::addressof(unex_), e.error()); + has_val_ = false; + return *this; +} + +template +template + requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected& expected::operator=(unexpected&& e) { + std::construct_at(std::addressof(unex_), e.error()); + has_val_ = false; + return *this; +} + +template +template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) +constexpr T& expected::emplace(U&& u) noexcept(std::is_nothrow_constructible_v) { + T& r = std::forward(u); // bind first: if it throws, the current state is left intact + if (!has_val_) { + std::destroy_at(std::addressof(unex_)); + has_val_ = true; + } + val_ = std::addressof(r); + return *val_; +} + +// ============================================================================= +// Out-of-line swap definition +// ============================================================================= + +template +constexpr void expected::swap(expected& rhs) noexcept( + std::is_nothrow_move_constructible_v> && (std::is_reference_v || std::is_nothrow_swappable_v)) + requires((std::is_reference_v || std::is_swappable_v) && std::is_move_constructible_v>) +{ + if (has_val_ && rhs.has_val_) { + std::swap(val_, rhs.val_); + } else if (!has_val_ && !rhs.has_val_) { + using std::swap; + swap(unex_, rhs.unex_); + } else if (has_val_) { + // this has value (pointer), rhs has error + T* tmp = val_; + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); + std::destroy_at(std::addressof(rhs.unex_)); + rhs.val_ = tmp; + has_val_ = false; + rhs.has_val_ = true; + } else { + rhs.swap(*this); + } +} + +// ============================================================================= +// Out-of-line observer definitions +// ============================================================================= + +template +constexpr T* expected::operator->() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return val_; +} + +template +constexpr T& expected::operator*() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return *val_; +} + +template +constexpr expected::operator bool() const noexcept { + return has_val_; +} + +template +constexpr bool expected::has_value() const noexcept { + return has_val_; +} + +template +constexpr T& expected::value() const& { + static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); + if (!has_val_) + throw bad_expected_access(unex_.error()); + return *val_; +} + +template +constexpr T& expected::value() && { + if constexpr (std::is_reference_v) { + static_assert(std::is_copy_constructible_v, "value() requires E to be copy constructible"); + } else { + static_assert(std::is_copy_constructible_v && std::is_move_constructible_v, + "value() && requires E be copy and move constructible"); + } + if (!has_val_) + throw bad_expected_access(std::move(unex_).error()); + return *val_; +} + +template +constexpr const E& expected::error() const& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return unex_.error(); +} + +template +constexpr E& expected::error() & noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return unex_.error(); +} + +template +constexpr const E&& expected::error() const&& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(unex_).error(); +} + +template +constexpr E&& expected::error() && noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(unex_).error(); +} + +template +template + requires(std::is_object_v && !std::is_array_v) +constexpr std::remove_cv_t expected::value_or(U&& def) const { + using X = std::remove_cv_t; + static_assert(std::is_convertible_v, "value_or requires T& convertible to remove_cv_t"); + static_assert(std::is_convertible_v, "value_or requires is_convertible_v>"); + if (has_val_) + return *val_; + return static_cast(std::forward(def)); +} + +template +template + requires(std::is_copy_constructible_v::error_value_type> && + std::is_convertible_v::error_value_type>) +constexpr typename expected::error_value_type expected::error_or(G&& def) const& { + if (!has_val_) + return unex_.error(); + return static_cast(std::forward(def)); +} + +template +template + requires(std::is_move_constructible_v::error_value_type> && + std::is_convertible_v::error_value_type>) +constexpr typename expected::error_value_type expected::error_or(G&& def) && { + if (!has_val_) + return std::move(unex_).error(); + return static_cast(std::forward(def)); +} + +// ============================================================================= +// Out-of-line monadic operation definitions +// ============================================================================= + +template +template + requires std::is_constructible_v +constexpr auto expected::and_then(F&& f) & { + using U = std::remove_cvref_t>; + static_assert(detail::is_expected_specialization::value, + "and_then: F must return a specialization of expected"); + static_assert(std::is_same_v, + "and_then: F must return expected with the same error_type"); + if (has_val_) + return std::invoke(std::forward(f), *val_); + return U(unexpect, unex_.error()); +} + +template +template + requires std::is_constructible_v +constexpr auto expected::and_then(F&& f) && { + using U = std::remove_cvref_t>; + static_assert(detail::is_expected_specialization::value, + "and_then: F must return a specialization of expected"); + static_assert(std::is_same_v, + "and_then: F must return expected with the same error_type"); + if (has_val_) + return std::invoke(std::forward(f), *val_); + return U(unexpect, std::move(unex_).error()); +} + +template +template + requires std::is_constructible_v +constexpr auto expected::and_then(F&& f) const& { + using U = std::remove_cvref_t>; + static_assert(detail::is_expected_specialization::value, + "and_then: F must return a specialization of expected"); + static_assert(std::is_same_v, + "and_then: F must return expected with the same error_type"); + if (has_val_) + return std::invoke(std::forward(f), *val_); + return U(unexpect, unex_.error()); +} + +template +template + requires std::is_constructible_v +constexpr auto expected::and_then(F&& f) const&& { + using U = std::remove_cvref_t>; + static_assert(detail::is_expected_specialization::value, + "and_then: F must return a specialization of expected"); + static_assert(std::is_same_v, + "and_then: F must return expected with the same error_type"); + if (has_val_) + return std::invoke(std::forward(f), *val_); + return U(unexpect, std::move(unex_).error()); +} + +template +template +constexpr auto expected::or_else(F&& f) & { + using G = std::remove_cvref_t>; + static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); + static_assert(std::is_same_v, + "or_else: F must return expected with the same value_type"); + if (has_val_) + return G(*val_); + return std::invoke(std::forward(f), unex_.error()); +} + +template +template +constexpr auto expected::or_else(F&& f) && { + using G = std::remove_cvref_t>; + static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); + static_assert(std::is_same_v, + "or_else: F must return expected with the same value_type"); + if (has_val_) + return G(*val_); + return std::invoke(std::forward(f), std::move(unex_).error()); +} + +template +template +constexpr auto expected::or_else(F&& f) const& { + using G = std::remove_cvref_t>; + static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); + static_assert(std::is_same_v, + "or_else: F must return expected with the same value_type"); + if (has_val_) + return G(*val_); + return std::invoke(std::forward(f), unex_.error()); +} + +template +template +constexpr auto expected::or_else(F&& f) const&& { + using G = std::remove_cvref_t>; + static_assert(detail::is_expected_specialization::value, "or_else: F must return a specialization of expected"); + static_assert(std::is_same_v, + "or_else: F must return expected with the same value_type"); + if (has_val_) + return G(*val_); + return std::invoke(std::forward(f), std::move(unex_).error()); +} + +template +template + requires std::is_constructible_v +constexpr auto expected::transform(F&& f) & { + using U = std::remove_cv_t>; + if constexpr (!std::is_void_v) { + static_assert(!std::is_array_v, "transform: U must not be an array type"); + static_assert(!std::is_same_v, std::in_place_t>, "transform: U must not be in_place_t"); + static_assert(!std::is_same_v, unexpect_t>, "transform: U must not be unexpect_t"); + static_assert(!detail::is_unexpected_specialization>::value, + "transform: U must not be a specialization of unexpected"); + } + if constexpr (std::is_void_v) { + if (has_val_) + std::invoke(std::forward(f), *val_); + if (has_val_) + return expected(); + return expected(unexpect, unex_.error()); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, unex_.error()); + } +} + +template +template + requires std::is_constructible_v +constexpr auto expected::transform(F&& f) && { + using U = std::remove_cv_t>; + if constexpr (!std::is_void_v) { + static_assert(!std::is_array_v, "transform: U must not be an array type"); + static_assert(!std::is_same_v, std::in_place_t>, "transform: U must not be in_place_t"); + static_assert(!std::is_same_v, unexpect_t>, "transform: U must not be unexpect_t"); + static_assert(!detail::is_unexpected_specialization>::value, + "transform: U must not be a specialization of unexpected"); + } + if constexpr (std::is_void_v) { + if (has_val_) + std::invoke(std::forward(f), *val_); + if (has_val_) + return expected(); + return expected(unexpect, std::move(unex_).error()); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, std::move(unex_).error()); + } +} + +template +template + requires std::is_constructible_v +constexpr auto expected::transform(F&& f) const& { + using U = std::remove_cv_t>; + if constexpr (!std::is_void_v) { + static_assert(!std::is_array_v, "transform: U must not be an array type"); + static_assert(!std::is_same_v, std::in_place_t>, "transform: U must not be in_place_t"); + static_assert(!std::is_same_v, unexpect_t>, "transform: U must not be unexpect_t"); + static_assert(!detail::is_unexpected_specialization>::value, + "transform: U must not be a specialization of unexpected"); + } + if constexpr (std::is_void_v) { + if (has_val_) + std::invoke(std::forward(f), *val_); + if (has_val_) + return expected(); + return expected(unexpect, unex_.error()); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, unex_.error()); + } +} + +template +template + requires std::is_constructible_v +constexpr auto expected::transform(F&& f) const&& { + using U = std::remove_cv_t>; + if constexpr (!std::is_void_v) { + static_assert(!std::is_array_v, "transform: U must not be an array type"); + static_assert(!std::is_same_v, std::in_place_t>, "transform: U must not be in_place_t"); + static_assert(!std::is_same_v, unexpect_t>, "transform: U must not be unexpect_t"); + static_assert(!detail::is_unexpected_specialization>::value, + "transform: U must not be a specialization of unexpected"); + } + if constexpr (std::is_void_v) { + if (has_val_) + std::invoke(std::forward(f), *val_); + if (has_val_) + return expected(); + return expected(unexpect, std::move(unex_).error()); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, std::move(unex_).error()); + } +} + +template +template +constexpr auto expected::transform_error(F&& f) & { + using G = std::remove_cv_t>; + static_assert(std::is_object_v, "transform_error: G must be an object type"); + static_assert(!std::is_array_v, "transform_error: G must not be an array type"); + static_assert(std::is_same_v>, "transform_error: G must not be cv-qualified"); + static_assert(!detail::is_unexpected_specialization::value, + "transform_error: G must not be a specialization of unexpected"); + if (has_val_) + return expected(*val_); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); +} + +template +template +constexpr auto expected::transform_error(F&& f) && { + using G = std::remove_cv_t>; + static_assert(std::is_object_v, "transform_error: G must be an object type"); + static_assert(!std::is_array_v, "transform_error: G must not be an array type"); + static_assert(std::is_same_v>, "transform_error: G must not be cv-qualified"); + static_assert(!detail::is_unexpected_specialization::value, + "transform_error: G must not be a specialization of unexpected"); + if (has_val_) + return expected(*val_); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); +} + +template +template +constexpr auto expected::transform_error(F&& f) const& { + using G = std::remove_cv_t>; + static_assert(std::is_object_v, "transform_error: G must be an object type"); + static_assert(!std::is_array_v, "transform_error: G must not be an array type"); + static_assert(std::is_same_v>, "transform_error: G must not be cv-qualified"); + static_assert(!detail::is_unexpected_specialization::value, + "transform_error: G must not be a specialization of unexpected"); + if (has_val_) + return expected(*val_); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); +} + +template +template +constexpr auto expected::transform_error(F&& f) const&& { + using G = std::remove_cv_t>; + static_assert(std::is_object_v, "transform_error: G must be an object type"); + static_assert(!std::is_array_v, "transform_error: G must not be an array type"); + static_assert(std::is_same_v>, "transform_error: G must not be cv-qualified"); + static_assert(!detail::is_unexpected_specialization::value, + "transform_error: G must not be a specialization of unexpected"); + if (has_val_) + return expected(*val_); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); +} + } // namespace expected } // namespace beman diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index b9d2773..70f86d3 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -1,6 +1,9 @@ # tests/beman/expected/CMakeLists.txt -*-cmake-*- # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +find_package(Catch2 3 REQUIRED) +list(APPEND CMAKE_MODULE_PATH "${Catch2_SOURCE_DIR}/extras") + add_executable(beman.expected.tests.expected) target_sources( beman.expected.tests.expected @@ -14,9 +17,12 @@ target_sources( expected_constraints.test.cpp expected_trivial.test.cpp expected_monadic_constraints.test.cpp + expected_ref.test.cpp + expected_ref_constraints.test.cpp expected_ref_e.test.cpp + expected_ref_both.test.cpp expected_void_ref_e.test.cpp - todo.test.cpp + expected_review_corrections.test.cpp ) target_link_libraries( beman.expected.tests.expected @@ -30,8 +36,10 @@ catch_discover_tests(beman.expected.tests.expected) # Negative compile tests (WILL_FAIL = compile failure is the expected outcome) # ============================================================================= -# Helper macro to reduce repetition -macro(add_fail_test name source) +# Helper macro: negative compile test that must fail with a specific diagnostic. +# The 'reason' is a regex matched against compiler output — confirms the failure +# is the intended static_assert / constraint, not an unrelated build error. +macro(add_fail_test name source reason) add_library(beman.expected.tests.${name} OBJECT) target_sources(beman.expected.tests.${name} PRIVATE ${source}) target_link_libraries(beman.expected.tests.${name} PRIVATE beman::expected) @@ -45,60 +53,253 @@ macro(add_fail_test name source) ${CMAKE_COMMAND} --build "${CMAKE_BINARY_DIR}" --target beman.expected.tests.${name} --config $ ) - set_tests_properties(${name} PROPERTIES WILL_FAIL TRUE) + set_tests_properties(${name} PROPERTIES PASS_REGULAR_EXPRESSION "${reason}") endmacro() # Step 1 — unexpected ill-formed instantiations [expected.un.general] -add_fail_test(unexpected_array_fail unexpected_array_fail.cpp) -add_fail_test(unexpected_cvref_fail unexpected_cvref_fail.cpp) -add_fail_test(unexpected_self_fail unexpected_self_fail.cpp) +add_fail_test(unexpected_array_fail unexpected_array_fail.cpp + "E must not be an array type" +) +add_fail_test(unexpected_cvref_fail unexpected_cvref_fail.cpp + "E must not be cv-qualified" +) +add_fail_test(unexpected_self_fail unexpected_self_fail.cpp + "E must not be a specialization of unexpected" +) # Step 3 — expected ill-formed T/E [expected.object.general] -add_fail_test(expected_t_ref_fail expected_t_ref_fail.cpp) -add_fail_test(expected_t_array_fail expected_t_array_fail.cpp) - -# expected — error-reference specialization (via unexpected) -add_fail_test(expected_ref_e_const_lvalue_fail expected_ref_e_const_lvalue_fail.cpp) -add_fail_test(expected_ref_e_temporary_error_fail expected_ref_e_temporary_error_fail.cpp) -add_fail_test(expected_ref_e_and_then_wrong_error_type_fail expected_ref_e_and_then_wrong_error_type_fail.cpp) -add_fail_test(expected_ref_e_e_array_fail expected_ref_e_e_array_fail.cpp) -add_fail_test(expected_ref_e_or_else_wrong_value_type_fail expected_ref_e_or_else_wrong_value_type_fail.cpp) -add_fail_test(expected_ref_e_t_array_fail expected_ref_e_t_array_fail.cpp) -add_fail_test(expected_ref_e_t_inplace_fail expected_ref_e_t_inplace_fail.cpp) -add_fail_test(expected_ref_e_t_unexpect_fail expected_ref_e_t_unexpect_fail.cpp) -add_fail_test(expected_ref_e_t_unexpected_fail expected_ref_e_t_unexpected_fail.cpp) -add_fail_test(expected_ref_e_transform_error_ref_result_fail expected_ref_e_transform_error_ref_result_fail.cpp) -add_fail_test(expected_ref_e_assign_unexpected_fail expected_ref_e_assign_unexpected_fail.cpp) -add_fail_test(expected_ref_e_construct_from_unexpected_fail expected_ref_e_construct_from_unexpected_fail.cpp) +add_fail_test(expected_t_ref_fail expected_t_ref_fail.cpp + "no default constructor|use of deleted function|call to deleted|attempting to reference" +) +# Note: expected_e_ref_fail removed — expected is now valid via expected +add_fail_test(expected_t_array_fail expected_t_array_fail.cpp + "T must not be an array type" +) # Step 3 — expected emplace Mandates: nothrow_constructible_v -add_fail_test(expected_emplace_throwing_fail expected_emplace_throwing_fail.cpp) +add_fail_test(expected_emplace_throwing_fail expected_emplace_throwing_fail.cpp + "is_nothrow_constructible|no matching overloaded function found" +) # Step 4 — expected ill-formed E [expected.void.general] -add_fail_test(expected_void_array_fail expected_void_array_fail.cpp) - -# expected — void + error-reference specialization (via unexpected) -add_fail_test(expected_void_ref_e_const_lvalue_fail expected_void_ref_e_const_lvalue_fail.cpp) -add_fail_test(expected_void_ref_e_no_value_or_fail expected_void_ref_e_no_value_or_fail.cpp) -add_fail_test(expected_void_ref_e_temporary_fail expected_void_ref_e_temporary_fail.cpp) -add_fail_test(expected_void_ref_e_assign_unexpected_fail expected_void_ref_e_assign_unexpected_fail.cpp) -add_fail_test(expected_void_ref_e_construct_from_unexpected_fail expected_void_ref_e_construct_from_unexpected_fail.cpp) +# Note: expected_void_ref_fail.cpp is removed — expected is now valid via +# the expected specialization added in Step 10. +add_fail_test(expected_void_array_fail expected_void_array_fail.cpp + "E must not be an array type" +) # Step 5 — monadic Mandates [expected.object.monadic] -add_fail_test(and_then_wrong_error_type_fail and_then_wrong_error_type_fail.cpp) -add_fail_test(and_then_not_expected_fail and_then_not_expected_fail.cpp) -add_fail_test(or_else_wrong_value_type_fail or_else_wrong_value_type_fail.cpp) -add_fail_test(transform_error_ref_result_fail transform_error_ref_result_fail.cpp) +add_fail_test(and_then_wrong_error_type_fail and_then_wrong_error_type_fail.cpp + "F must return expected with the same error_type" +) +add_fail_test(and_then_not_expected_fail and_then_not_expected_fail.cpp + "F must return a specialization of expected" +) +add_fail_test(or_else_wrong_value_type_fail or_else_wrong_value_type_fail.cpp + "F must return expected with the same value_type" +) +add_fail_test(transform_error_ref_result_fail transform_error_ref_result_fail.cpp + "G must be an object type" +) # Step 6 — void monadic Mandates [expected.void.monadic] -add_fail_test(void_and_then_wrong_error_type_fail void_and_then_wrong_error_type_fail.cpp) -add_fail_test(void_or_else_wrong_value_type_fail void_or_else_wrong_value_type_fail.cpp) +add_fail_test(void_and_then_wrong_error_type_fail void_and_then_wrong_error_type_fail.cpp + "F must return expected with the same error_type" +) +add_fail_test(void_or_else_wrong_value_type_fail void_or_else_wrong_value_type_fail.cpp + "F must return expected with the same value_type" +) # Fix 1 — constraint 23.6: value ctor blocked for T=bool, U=expected specialization -add_fail_test(expected_bool_value_ctor_from_expected_fail expected_bool_value_ctor_from_expected_fail.cpp) +add_fail_test(expected_bool_value_ctor_from_expected_fail expected_bool_value_ctor_from_expected_fail.cpp + "no matching function|no matching constructor|no overloaded function could convert" +) # Fix 4 — Mandates: T must not be a specialization of unexpected -add_fail_test(expected_unexpected_value_type_fail expected_unexpected_value_type_fail.cpp) +add_fail_test(expected_unexpected_value_type_fail expected_unexpected_value_type_fail.cpp + "T must not be a specialization of unexpected" +) + +# Step 7 — expected reference specialization: constructor constraints +add_fail_test(expected_ref_temporary_fail expected_ref_temporary_fail.cpp + "binding a temporary to T& creates a dangling reference|use of deleted function|call to deleted|no matching function|no matching constructor|no overloaded function could convert|attempting to reference" +) +add_fail_test(expected_ref_no_default_fail expected_ref_no_default_fail.cpp + "no default constructor|use of deleted function|call to deleted|attempting to reference" +) +add_fail_test(expected_ref_inplace_value_fail expected_ref_inplace_value_fail.cpp + "no in_place value constructor|use of deleted function|call to deleted|attempting to reference" +) + +# Step 8 — expected reference error: dangling prevention +add_fail_test(expected_ref_e_temporary_error_fail expected_ref_e_temporary_error_fail.cpp + "cannot bind to non-const E&|use of deleted function|call to deleted|attempting to reference" +) +add_fail_test(expected_ref_e_const_lvalue_fail expected_ref_e_const_lvalue_fail.cpp + "cannot bind to non-const E&|use of deleted function|call to deleted|attempting to reference" +) + +# Step 8 — expected: no constructor or assignment from unexpected +# (would bind E& to storage inside the temporary unexpected — dangling) +add_fail_test(expected_ref_e_construct_from_unexpected_fail + expected_ref_e_construct_from_unexpected_fail.cpp + "no constructor from unexpected|use of deleted function|call to deleted|invokes a deleted function|attempting to reference" +) +add_fail_test(expected_ref_e_assign_unexpected_fail + expected_ref_e_assign_unexpected_fail.cpp + "no assignment from unexpected|use of deleted function|call to deleted|selected deleted operator|attempting to reference" +) + +# Step 7/8 parity — expected or_else value_type mandate (added in Step 8 review) +add_fail_test(expected_ref_or_else_wrong_value_type_fail expected_ref_or_else_wrong_value_type_fail.cpp + "F must return expected with the same value_type" +) + +# Step 9 — expected dangling prevention and no-default constraint +add_fail_test(expected_ref_both_temporary_val_fail expected_ref_both_temporary_val_fail.cpp + "binding a temporary to T& creates a dangling reference|use of deleted function|call to deleted|no matching function|no matching constructor|no overloaded function could convert|attempting to reference" +) +add_fail_test(expected_ref_both_temporary_err_fail expected_ref_both_temporary_err_fail.cpp + "cannot bind to non-const E&|use of deleted function|call to deleted|attempting to reference" +) +add_fail_test(expected_ref_both_no_default_fail expected_ref_both_no_default_fail.cpp + "no default constructor|use of deleted function|call to deleted|attempting to reference" +) + +# Step 9 — expected: no constructor or assignment from unexpected +# (would bind E& to storage inside the temporary unexpected — dangling) +add_fail_test(expected_ref_both_construct_from_unexpected_fail + expected_ref_both_construct_from_unexpected_fail.cpp + "no constructor from unexpected|use of deleted function|call to deleted|invokes a deleted function|attempting to reference" +) +add_fail_test(expected_ref_both_assign_unexpected_fail + expected_ref_both_assign_unexpected_fail.cpp + "no assignment from unexpected|use of deleted function|call to deleted|selected deleted operator|attempting to reference" +) + +# Step 9 — expected: or_else value_type mandate +add_fail_test(expected_ref_both_or_else_wrong_value_type_fail + expected_ref_both_or_else_wrong_value_type_fail.cpp + "F must return expected with the same value_type" +) + +# Step 7 — expected Mandates: E constraints +add_fail_test(expected_ref_e_ref_fail expected_ref_e_ref_fail.cpp + "E must not be an rvalue reference" +) +add_fail_test(expected_ref_e_void_fail expected_ref_e_void_fail.cpp + "E must not be void" +) +add_fail_test(expected_ref_e_array_fail expected_ref_e_array_fail.cpp + "E must not be an array type" +) +add_fail_test(expected_ref_e_cv_fail expected_ref_e_cv_fail.cpp + "E must not be cv-qualified" +) + +# Step 7 — expected monadic mandates +add_fail_test(expected_ref_and_then_wrong_error_type_fail + expected_ref_and_then_wrong_error_type_fail.cpp + "F must return expected with the same error_type" +) +add_fail_test(expected_ref_transform_error_ref_result_fail + expected_ref_transform_error_ref_result_fail.cpp + "G must be an object type" +) + +# Step 8 — expected Mandates: T and E type constraints +add_fail_test(expected_ref_e_t_array_fail + expected_ref_e_t_array_fail.cpp + "T must not be an array type" +) +add_fail_test(expected_ref_e_e_array_fail + expected_ref_e_e_array_fail.cpp + "E must not be an array type" +) +add_fail_test(expected_ref_e_t_inplace_fail + expected_ref_e_t_inplace_fail.cpp + "T must not be in_place_t" +) +add_fail_test(expected_ref_e_t_unexpect_fail + expected_ref_e_t_unexpect_fail.cpp + "T must not be unexpect_t" +) +add_fail_test(expected_ref_e_t_unexpected_fail + expected_ref_e_t_unexpected_fail.cpp + "T must not be a specialization of unexpected" +) + +# Step 8 — expected monadic mandates +add_fail_test(expected_ref_e_and_then_wrong_error_type_fail + expected_ref_e_and_then_wrong_error_type_fail.cpp + "F must return expected with the same error_type" +) +add_fail_test(expected_ref_e_or_else_wrong_value_type_fail + expected_ref_e_or_else_wrong_value_type_fail.cpp + "F must return expected with the same value_type" +) +add_fail_test(expected_ref_e_transform_error_ref_result_fail + expected_ref_e_transform_error_ref_result_fail.cpp + "G must be an object type" +) + +# Step 9 — expected Mandates: T and E type constraints +add_fail_test(expected_ref_both_t_array_fail + expected_ref_both_t_array_fail.cpp + "T must not be an array type" +) +add_fail_test(expected_ref_both_e_array_fail + expected_ref_both_e_array_fail.cpp + "E must not be an array type" +) +add_fail_test(expected_ref_both_t_inplace_fail + expected_ref_both_t_inplace_fail.cpp + "T must not be in_place_t" +) +add_fail_test(expected_ref_both_t_unexpect_fail + expected_ref_both_t_unexpect_fail.cpp + "T must not be unexpect_t" +) +add_fail_test(expected_ref_both_t_unexpected_fail + expected_ref_both_t_unexpected_fail.cpp + "T must not be a specialization of unexpected" +) +add_fail_test(expected_ref_both_inplace_value_fail + expected_ref_both_inplace_value_fail.cpp + "no in_place value constructor|use of deleted function|call to deleted|attempting to reference" +) + +# Step 9 — expected monadic mandates +add_fail_test(expected_ref_both_and_then_wrong_error_type_fail + expected_ref_both_and_then_wrong_error_type_fail.cpp + "F must return expected with the same error_type" +) +add_fail_test(expected_ref_both_transform_error_ref_result_fail + expected_ref_both_transform_error_ref_result_fail.cpp + "G must be an object type" +) + +# Step 10 — expected dangling prevention and no value_or +add_fail_test(expected_void_ref_e_temporary_fail + expected_void_ref_e_temporary_fail.cpp + "cannot bind to non-const E&|use of deleted function|call to deleted|attempting to reference" +) +add_fail_test(expected_void_ref_e_const_lvalue_fail + expected_void_ref_e_const_lvalue_fail.cpp + "cannot bind to non-const E&|use of deleted function|call to deleted|attempting to reference" +) +add_fail_test(expected_void_ref_e_no_value_or_fail + expected_void_ref_e_no_value_or_fail.cpp + "no value_or for void|use of deleted function|call to deleted|attempting to reference" +) +add_fail_test(expected_void_ref_e_construct_from_unexpected_fail + expected_void_ref_e_construct_from_unexpected_fail.cpp + "no constructor from unexpected|use of deleted function|call to deleted|invokes a deleted function|attempting to reference" +) +add_fail_test(expected_void_ref_e_assign_unexpected_fail + expected_void_ref_e_assign_unexpected_fail.cpp + "no assignment from unexpected|use of deleted function|call to deleted|invokes a deleted function|selected deleted operator|attempting to reference" +) # ============================================================================= # Hardened precondition tests (compiled with -DBEMAN_EXPECTED_HARDENED) diff --git a/tests/beman/expected/and_then_not_expected_fail.cpp b/tests/beman/expected/and_then_not_expected_fail.cpp index cb64eca..4c46086 100644 --- a/tests/beman/expected/and_then_not_expected_fail.cpp +++ b/tests/beman/expected/and_then_not_expected_fail.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // NEGATIVE: and_then Mandates U is a specialization of expected +// EXPECT: "F must return a specialization of expected" #include void test() { beman::expected::expected e(1); diff --git a/tests/beman/expected/and_then_wrong_error_type_fail.cpp b/tests/beman/expected/and_then_wrong_error_type_fail.cpp index 92297e7..f0e527d 100644 --- a/tests/beman/expected/and_then_wrong_error_type_fail.cpp +++ b/tests/beman/expected/and_then_wrong_error_type_fail.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // NEGATIVE: and_then Mandates U::error_type == E -// F returns expected but E is std::string — ill-formed +// EXPECT: "F must return expected with the same error_type" #include #include void test() { diff --git a/tests/beman/expected/bad_expected_access.test.cpp b/tests/beman/expected/bad_expected_access.test.cpp index fd0d07a..4ea2bf2 100644 --- a/tests/beman/expected/bad_expected_access.test.cpp +++ b/tests/beman/expected/bad_expected_access.test.cpp @@ -108,3 +108,21 @@ TEST_CASE("bad_expected_access: accessible via base reference", "[BadExpec const expt::bad_expected_access& base = ex; CHECK(base.what() != nullptr); } + +TEST_CASE("bad_expected_access: move constructor", "[BadExpectedAccessTest]") { + expt::bad_expected_access orig("test error"); + expt::bad_expected_access moved(std::move(orig)); + CHECK(moved.error() == "test error"); +} + +TEST_CASE("bad_expected_access: rvalue error accessor (string move)", "[BadExpectedAccessTest]") { + expt::bad_expected_access e("val"); + std::string s = std::move(e).error(); + CHECK(s == "val"); +} + +TEST_CASE("bad_expected_access: const rvalue error accessor (string move)", "[BadExpectedAccessTest]") { + const expt::bad_expected_access e("val"); + std::string s = std::move(e).error(); + CHECK(s == "val"); +} diff --git a/tests/beman/expected/expected.test.cpp b/tests/beman/expected/expected.test.cpp index cac52e2..c22fcfd 100644 --- a/tests/beman/expected/expected.test.cpp +++ b/tests/beman/expected/expected.test.cpp @@ -6,6 +6,8 @@ #include +#include "testing/types.hpp" + #include #include #include @@ -338,6 +340,13 @@ TEST_CASE("expected: assign from unexpected&& when has value (state change)", "[ CHECK(e.error() == "fail"); } +TEST_CASE("expected: assign from unexpected&& when already has error", "[ExpectedTest]") { + expt::expected e(expt::unexpected("first")); + e = expt::unexpected("second"); + CHECK_FALSE(e.has_value()); + CHECK(e.error() == "second"); +} + // ============================================================================= // emplace // ============================================================================= @@ -799,3 +808,408 @@ TEST_CASE("expected: value() ref-qualification return types", "[ExpectedTest]") static_assert(std::is_same_v&&>().value()), int&&>); static_assert(std::is_same_v&&>().value()), const int&&>); } + +// ============================================================================= +// Additional coverage tests (using testing helper types and rvalue paths) +// ============================================================================= +using expt::expected; +using expt::unexpect; +using expt::unexpected; +using namespace beman::expected::testing; + +// --- value() throw from rvalue and const rvalue --- +TEST_CASE("value() rvalue throws with moved error", "[ExpectedTest]") { + expected e(unexpect, "rval-err"); + CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); +} + +TEST_CASE("value() const rvalue throws", "[ExpectedTest]") { + const expected e(unexpect, "crval-err"); + CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); +} + +// --- error_or rvalue overloads --- +TEST_CASE("error_or rvalue uses default when has value", "[ExpectedTest]") { + expected e(42); + std::string s = std::move(e).error_or("fallback"); + CHECK(s == "fallback"); +} + +// --- unexpect_t constructor with initializer_list --- +TEST_CASE("unexpect_t constructor with initializer_list", "[ExpectedTest]") { + expected> e(unexpect, {1, 2, 3}); + REQUIRE(!e.has_value()); + CHECK(e.error() == std::vector{1, 2, 3}); +} + +// --- in_place_t constructor with initializer_list --- +TEST_CASE("in_place_t constructor with initializer_list", "[ExpectedTest]") { + expected, int> e(std::in_place, {4, 5, 6}); + REQUIRE(e.has_value()); + CHECK(*e == std::vector{4, 5, 6}); +} + +// --- emplace on error-state (destroy error, construct value) --- +TEST_CASE("emplace on error state transitions to value", "[ExpectedTest]") { + expected e(unexpect, "err"); + e.emplace(42); + REQUIRE(e.has_value()); + CHECK(*e == 42); +} + +// --- Cross-type equality (expected vs expected) --- +TEST_CASE("cross-type equality different error types", "[ExpectedTest]") { + expected a(unexpect, 1); + expected b(unexpect, 1L); + CHECK(a == b); +} + +TEST_CASE("cross-type equality error vs value", "[ExpectedTest]") { + expected a(42); + expected b(unexpect, 0L); + CHECK_FALSE(a == b); +} + +// --------------------------------------------------------------------------- +// expected: non-trivial destructor, copy/move ctor, assignment +// --------------------------------------------------------------------------- + +TEST_CASE("traced: default construct (value state)", "[ExpectedTest][traced]") { + expected e(std::in_place, 42); + REQUIRE(e.has_value()); + CHECK(e->val == 42); +} + +TEST_CASE("traced: error construct", "[ExpectedTest][traced]") { + expected e(unexpect, 7); + REQUIRE(!e.has_value()); + CHECK(e.error().val == 7); +} + +TEST_CASE("traced: copy construct value state", "[ExpectedTest][traced]") { + expected a(std::in_place, 10); + expected b(a); + REQUIRE(b.has_value()); + CHECK(b->val == 10); +} + +TEST_CASE("traced: copy construct error state", "[ExpectedTest][traced]") { + expected a(unexpect, 20); + expected b(a); + REQUIRE(!b.has_value()); + CHECK(b.error().val == 20); +} + +TEST_CASE("traced: move construct value state", "[ExpectedTest][traced]") { + expected a(std::in_place, 10); + expected b(std::move(a)); + REQUIRE(b.has_value()); + CHECK(b->val == 10); +} + +TEST_CASE("traced: move construct error state", "[ExpectedTest][traced]") { + expected a(unexpect, 20); + expected b(std::move(a)); + REQUIRE(!b.has_value()); + CHECK(b.error().val == 20); +} + +TEST_CASE("traced: copy assign value-to-value", "[ExpectedTest][traced]") { + expected a(std::in_place, 1); + expected b(std::in_place, 2); + b = a; + CHECK(b->val == 1); +} + +TEST_CASE("traced: copy assign error-to-error", "[ExpectedTest][traced]") { + expected a(unexpect, 3); + expected b(unexpect, 4); + b = a; + CHECK(b.error().val == 3); +} + +TEST_CASE("traced: copy assign error-to-value (state change)", "[ExpectedTest][traced]") { + expected a(unexpect, 5); + expected b(std::in_place, 6); + b = a; + REQUIRE(!b.has_value()); + CHECK(b.error().val == 5); +} + +TEST_CASE("traced: copy assign value-to-error (state change)", "[ExpectedTest][traced]") { + expected a(std::in_place, 7); + expected b(unexpect, 8); + b = a; + REQUIRE(b.has_value()); + CHECK(b->val == 7); +} + +TEST_CASE("traced: move assign error-to-error", "[ExpectedTest][traced]") { + expected a(unexpect, 9); + expected b(unexpect, 10); + b = std::move(a); + CHECK(b.error().val == 9); +} + +TEST_CASE("traced: move assign error-to-value (state change)", "[ExpectedTest][traced]") { + expected a(unexpect, 11); + expected b(std::in_place, 12); + b = std::move(a); + REQUIRE(!b.has_value()); + CHECK(b.error().val == 11); +} + +TEST_CASE("traced: move assign value-to-error (state change)", "[ExpectedTest][traced]") { + expected a(std::in_place, 13); + expected b(unexpect, 14); + b = std::move(a); + REQUIRE(b.has_value()); + CHECK(b->val == 13); +} + +TEST_CASE("traced: assign from unexpected const&", "[ExpectedTest][traced]") { + expected e(std::in_place, 1); + unexpected u(traced(99)); + e = u; + REQUIRE(!e.has_value()); + CHECK(e.error().val == 99); +} + +TEST_CASE("traced: assign from unexpected&&", "[ExpectedTest][traced]") { + expected e(std::in_place, 1); + e = unexpected(traced(77)); + REQUIRE(!e.has_value()); + CHECK(e.error().val == 77); +} + +TEST_CASE("traced: emplace on error state", "[ExpectedTest][traced]") { + expected e(unexpect, 1); + e.emplace(42); + REQUIRE(e.has_value()); + CHECK(e->val == 42); +} + +TEST_CASE("traced: destructor runs for value state", "[ExpectedTest][traced]") { + { + expected e(std::in_place, 1); + } +} + +TEST_CASE("traced: destructor runs for error state", "[ExpectedTest][traced]") { + { + expected e(unexpect, 1); + } +} + +// --- value_or / error_or rvalue with non-trivial types --- +TEST_CASE("traced: value_or rvalue returns value", "[ExpectedTest][traced]") { + expected e(std::in_place, 5); + traced t = std::move(e).value_or(traced(0)); + CHECK(t.val == 5); +} + +TEST_CASE("traced: error_or const lvalue returns error", "[ExpectedTest][traced]") { + const expected e(unexpect, 7); + traced t = e.error_or(traced(0)); + CHECK(t.val == 7); +} + +TEST_CASE("traced: error_or rvalue returns error", "[ExpectedTest][traced]") { + expected e(unexpect, 7); + traced t = std::move(e).error_or(traced(0)); + CHECK(t.val == 7); +} + +TEST_CASE("traced: error_or rvalue returns default when value", "[ExpectedTest][traced]") { + expected e(42); + traced t = std::move(e).error_or(traced(99)); + CHECK(t.val == 99); +} + +// --------------------------------------------------------------------------- +// init_list_type: in_place_t / unexpect_t with initializer_list +// --------------------------------------------------------------------------- + +TEST_CASE("init_list: in_place_t with initializer_list", "[ExpectedTest][init_list]") { + expected e(std::in_place, {1, 2, 3}); + REQUIRE(e.has_value()); + CHECK(e->sum == 6); + CHECK(e->count == 3); +} + +TEST_CASE("init_list: in_place_t with initializer_list and extra arg", "[ExpectedTest][init_list]") { + expected e(std::in_place, {1, 2, 3}, 100); + REQUIRE(e.has_value()); + CHECK(e->sum == 106); +} + +TEST_CASE("init_list: unexpect_t with initializer_list", "[ExpectedTest][init_list]") { + expected e(unexpect, {10, 20, 30}); + REQUIRE(!e.has_value()); + CHECK(e.error().sum == 60); + CHECK(e.error().count == 3); +} + +TEST_CASE("init_list: emplace with initializer_list on value state", "[ExpectedTest][init_list]") { + expected e(std::in_place, {1}); + e.emplace({4, 5, 6}); + REQUIRE(e.has_value()); + CHECK(e->sum == 15); +} + +TEST_CASE("init_list: emplace with initializer_list on error state", "[ExpectedTest][init_list]") { + expected e(unexpect, 0); + e.emplace({7, 8}); + REQUIRE(e.has_value()); + CHECK(e->sum == 15); +} + +// --------------------------------------------------------------------------- +// Converting constructors: expected from expected +// --------------------------------------------------------------------------- + +TEST_CASE("converting: copy construct value state", "[ExpectedTest][converting]") { + expected a(std::in_place, narrowed(5)); + expected b(a); + REQUIRE(b.has_value()); + CHECK(b->val == 5); +} + +TEST_CASE("converting: copy construct error state", "[ExpectedTest][converting]") { + expected a(unexpect, narrowed(7)); + expected b(a); + REQUIRE(!b.has_value()); + CHECK(b.error().val == 7); +} + +TEST_CASE("converting: move construct value state", "[ExpectedTest][converting]") { + expected a(std::in_place, narrowed(5)); + expected b(std::move(a)); + REQUIRE(b.has_value()); + CHECK(b->val == 5); +} + +TEST_CASE("converting: move construct error state", "[ExpectedTest][converting]") { + expected a(unexpect, narrowed(7)); + expected b(std::move(a)); + REQUIRE(!b.has_value()); + CHECK(b.error().val == 7); +} + +// --------------------------------------------------------------------------- +// Cross-type equality with eq_a / eq_b +// --------------------------------------------------------------------------- + +TEST_CASE("cross-eq: expected == expected", "[ExpectedTest][cross_eq]") { + expected a(unexpect, eq_a(1)); + expected b(unexpect, eq_b(1)); + CHECK(a == b); + + expected c(42); + CHECK_FALSE(a == c); +} + +// --------------------------------------------------------------------------- +// Constraint verification with derived types +// --------------------------------------------------------------------------- + +TEST_CASE("constraint: from_expected is derived from expected", "[ExpectedTest][constraint]") { + static_assert(std::is_base_of_v, from_expected>); + from_expected fe(42); + CHECK(fe.has_value()); + CHECK(*fe == 42); +} + +TEST_CASE("constraint: from_unexpected is derived from unexpected", "[ExpectedTest][constraint]") { + static_assert(std::is_base_of_v, from_unexpected>); + from_unexpected fu(7); + CHECK(fu.error() == 7); +} + +// --------------------------------------------------------------------------- +// expected, E>: nested expected as value type +// --------------------------------------------------------------------------- + +TEST_CASE("nested: expected, std::string> value construct", "[ExpectedTest][nested]") { + expected inner(42); + expected, std::string> outer(inner); + REQUIRE(outer.has_value()); + REQUIRE(outer->has_value()); + CHECK(**outer == 42); +} + +TEST_CASE("nested: expected, std::string> error construct", "[ExpectedTest][nested]") { + expected, std::string> outer(unexpect, "err"); + REQUIRE(!outer.has_value()); + CHECK(outer.error() == "err"); +} + +TEST_CASE("nested: expected, std::string> in_place value construct", "[ExpectedTest][nested]") { + expected, std::string> outer(std::in_place, 99); + REQUIRE(outer.has_value()); + REQUIRE(outer->has_value()); + CHECK(**outer == 99); +} + +TEST_CASE("nested: expected, std::string> in_place error-inner", "[ExpectedTest][nested]") { + expected, std::string> outer(std::in_place, unexpect, 7); + REQUIRE(outer.has_value()); + REQUIRE(!outer->has_value()); + CHECK(outer->error() == 7); +} + +TEST_CASE("nested: expected, int> copy construct value", "[ExpectedTest][nested]") { + expected, int> a(expected(5)); + expected, int> b(a); + REQUIRE(b.has_value()); + CHECK(**b == 5); +} + +TEST_CASE("nested: expected, int> move construct value", "[ExpectedTest][nested]") { + expected, int> a(expected(5)); + expected, int> b(std::move(a)); + REQUIRE(b.has_value()); + CHECK(**b == 5); +} + +TEST_CASE("nested: expected, int> monadic and_then", "[ExpectedTest][nested]") { + expected, int> e(expected(5)); + auto r = e.and_then([](expected& inner) -> expected { return *inner * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 10); +} + +// --------------------------------------------------------------------------- +// expected>: unexpected as error type +// --------------------------------------------------------------------------- + +// Note: both expected> and expected, E> are +// ill-formed by Mandates. These are tested by the _fail.cpp negative tests. +// Instead, test with from_expected (derived from expected) as value type +// and from_unexpected (derived from unexpected) as error type — these are +// NOT specializations of expected/unexpected, so they bypass the Mandates +// while still exercising constructor disambiguation. + +TEST_CASE("nested: expected holds expected-derived value", "[ExpectedTest][nested]") { + expected e(std::in_place, 42); + REQUIRE(e.has_value()); + CHECK(e->has_value()); + CHECK(**e == 42); +} + +TEST_CASE("nested: expected error state", "[ExpectedTest][nested]") { + expected e(unexpect, "err"); + REQUIRE(!e.has_value()); + CHECK(e.error() == "err"); +} + +TEST_CASE("nested: expected copy and move", "[ExpectedTest][nested]") { + expected a(std::in_place, 5); + expected b(a); + REQUIRE(b.has_value()); + CHECK(**b == 5); + + expected c(std::move(a)); + REQUIRE(c.has_value()); + CHECK(**c == 5); +} diff --git a/tests/beman/expected/expected_bool_value_ctor_from_expected_fail.cpp b/tests/beman/expected/expected_bool_value_ctor_from_expected_fail.cpp index c0d905f..de37c28 100644 --- a/tests/beman/expected/expected_bool_value_ctor_from_expected_fail.cpp +++ b/tests/beman/expected/expected_bool_value_ctor_from_expected_fail.cpp @@ -1,8 +1,9 @@ // tests/beman/expected/expected_bool_value_ctor_from_expected_fail.cpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -// Negative compile test (WILL_FAIL): expected cannot be constructed -// from expected via the value ctor when the converting ctor is not viable. +// NEGATIVE: expected cannot be constructed from expected via +// the value ctor when the converting ctor is not viable. +// EXPECT: "no matching function" // // Constraint 23.6: if T is cv bool, remove_cvref_t must not be a // specialization of expected. This blocks the value ctor from selecting diff --git a/tests/beman/expected/expected_emplace_throwing_fail.cpp b/tests/beman/expected/expected_emplace_throwing_fail.cpp index d7f4b31..75f2653 100644 --- a/tests/beman/expected/expected_emplace_throwing_fail.cpp +++ b/tests/beman/expected/expected_emplace_throwing_fail.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE COMPILE TEST: emplace requires nothrow_constructible_v -// Calling emplace with a type whose constructor might throw is ill-formed. +// NEGATIVE: emplace requires nothrow_constructible_v +// EXPECT: "is_nothrow_constructible" #include struct ThrowingCtor { diff --git a/tests/beman/expected/expected_monadic.test.cpp b/tests/beman/expected/expected_monadic.test.cpp index d19cc69..d44014e 100644 --- a/tests/beman/expected/expected_monadic.test.cpp +++ b/tests/beman/expected/expected_monadic.test.cpp @@ -6,6 +6,8 @@ #include +#include "testing/types.hpp" + #include #include @@ -344,3 +346,253 @@ TEST_CASE("transform_error: all 4 ref qualifications compile", "[expected_monadi (void)(std::move(e).transform_error(f)); (void)(std::move(std::as_const(e)).transform_error(f)); } + +// ============================================================================= +// Additional coverage tests: rvalue/const-rvalue monadic overloads +// ============================================================================= +using namespace beman::expected::testing; + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state (short-circuit) --- +TEST_CASE("and_then rvalue on error short-circuits", "[expected_monadic]") { + expected e(unexpect, "err"); + bool called = false; + auto r = std::move(e).and_then([&](int) -> expected { + called = true; + return 0; + }); + CHECK(!called); + REQUIRE(!r.has_value()); + CHECK(r.error() == "err"); +} + +TEST_CASE("and_then const rvalue on error short-circuits", "[expected_monadic]") { + const expected e(unexpect, "err"); + bool called = false; + auto r = std::move(e).and_then([&](int) -> expected { + called = true; + return 0; + }); + CHECK(!called); + REQUIRE(!r.has_value()); + CHECK(r.error() == "err"); +} + +// --- Monadic: or_else rvalue/const-rvalue on VALUE state (short-circuit) --- +TEST_CASE("or_else rvalue on value short-circuits", "[expected_monadic]") { + expected e("val"); + bool called = false; + auto r = std::move(e).or_else([&](int) -> expected { + called = true; + return "x"; + }); + CHECK(!called); + REQUIRE(r.has_value()); +} + +TEST_CASE("or_else const rvalue on value short-circuits", "[expected_monadic]") { + const expected e(42); + bool called = false; + auto r = std::move(e).or_else([&](int) -> expected { + called = true; + return 0; + }); + CHECK(!called); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +// --- Monadic: transform rvalue/const-rvalue on ERROR state (short-circuit) --- +TEST_CASE("transform rvalue on error short-circuits", "[expected_monadic]") { + expected e(unexpect, "err"); + auto r = std::move(e).transform([](int v) { return v * 2; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == "err"); +} + +TEST_CASE("transform const rvalue on error short-circuits", "[expected_monadic]") { + const expected e(unexpect, "err"); + auto r = std::move(e).transform([](int v) { return v * 2; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == "err"); +} + +// --- Monadic: transform_error rvalue/const-rvalue on VALUE state (short-circuit) --- +TEST_CASE("transform_error rvalue on value short-circuits", "[expected_monadic]") { + expected e("val"); + auto r = std::move(e).transform_error([](int v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("transform_error const rvalue on value short-circuits", "[expected_monadic]") { + const expected e(42); + auto r = std::move(e).transform_error([](int v) { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +// --- Monadic: transform const-rvalue on VALUE state (the actual call path) --- +TEST_CASE("transform const rvalue on value calls F", "[expected_monadic]") { + const expected e(5); + auto r = std::move(e).transform([](int v) { return v * 3; }); + REQUIRE(r.has_value()); + CHECK(*r == 15); +} + +// --- Monadic: transform_error const-rvalue on ERROR state (the actual call path) --- +TEST_CASE("transform_error const rvalue on error calls F", "[expected_monadic]") { + const expected e(unexpect, 7); + auto r = std::move(e).transform_error([](int v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 8); +} + +// --------------------------------------------------------------------------- +// traced monadic: all 4 overloads × value + error paths +// --------------------------------------------------------------------------- + +TEST_CASE("traced: and_then lvalue value path", "[expected_monadic][traced]") { + expected e(std::in_place, 5); + auto r = e.and_then( + [](traced& v) -> expected { return expected(std::in_place, v.val * 2); }); + REQUIRE(r.has_value()); + CHECK(r->val == 10); +} + +TEST_CASE("traced: and_then lvalue error path", "[expected_monadic][traced]") { + expected e(unexpect, 5); + auto r = + e.and_then([](traced&) -> expected { return expected(std::in_place, 0); }); + REQUIRE(!r.has_value()); + CHECK(r.error().val == 5); +} + +TEST_CASE("traced: and_then const lvalue error path", "[expected_monadic][traced]") { + const expected e(unexpect, 5); + auto r = e.and_then( + [](const traced&) -> expected { return expected(std::in_place, 0); }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("traced: or_else lvalue value path", "[expected_monadic][traced]") { + expected e(std::in_place, 5); + auto r = e.or_else([](traced&) -> expected { return expected(unexpect, 0); }); + REQUIRE(r.has_value()); + CHECK(r->val == 5); +} + +TEST_CASE("traced: or_else lvalue error path", "[expected_monadic][traced]") { + expected e(unexpect, 3); + auto r = e.or_else( + [](traced& v) -> expected { return expected(std::in_place, v.val * 10); }); + REQUIRE(r.has_value()); + CHECK(r->val == 30); +} + +TEST_CASE("traced: or_else const lvalue value path", "[expected_monadic][traced]") { + const expected e(std::in_place, 5); + auto r = + e.or_else([](const traced&) -> expected { return expected(unexpect, 0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("traced: or_else rvalue error path", "[expected_monadic][traced]") { + expected e(unexpect, 3); + auto r = std::move(e).or_else( + [](traced&& v) -> expected { return expected(std::in_place, v.val); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("traced: or_else const rvalue value path", "[expected_monadic][traced]") { + const expected e(std::in_place, 5); + auto r = std::move(e).or_else( + [](const traced&) -> expected { return expected(unexpect, 0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("traced: transform lvalue value path", "[expected_monadic][traced]") { + expected e(std::in_place, 3); + auto r = e.transform([](traced& v) { return traced(v.val + 1); }); + REQUIRE(r.has_value()); + CHECK(r->val == 4); +} + +TEST_CASE("traced: transform lvalue error path", "[expected_monadic][traced]") { + expected e(unexpect, 3); + auto r = e.transform([](traced&) { return traced(0); }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("traced: transform rvalue value path", "[expected_monadic][traced]") { + expected e(std::in_place, 3); + auto r = std::move(e).transform([](traced&& v) { return traced(v.val + 1); }); + REQUIRE(r.has_value()); + CHECK(r->val == 4); +} + +TEST_CASE("traced: transform const lvalue error path", "[expected_monadic][traced]") { + const expected e(unexpect, 3); + auto r = e.transform([](const traced&) { return traced(0); }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("traced: transform const rvalue value path", "[expected_monadic][traced]") { + const expected e(std::in_place, 3); + auto r = std::move(e).transform([](const traced& v) { return traced(v.val + 1); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("traced: transform to void lvalue", "[expected_monadic][traced]") { + expected e(std::in_place, 1); + auto r = e.transform([](traced&) {}); + REQUIRE(r.has_value()); +} + +TEST_CASE("traced: transform_error lvalue value path", "[expected_monadic][traced]") { + expected e(std::in_place, 5); + auto r = e.transform_error([](traced&) { return traced(0); }); + REQUIRE(r.has_value()); + CHECK(r->val == 5); +} + +TEST_CASE("traced: transform_error lvalue error path", "[expected_monadic][traced]") { + expected e(unexpect, 5); + auto r = e.transform_error([](traced& v) { return traced(v.val + 1); }); + REQUIRE(!r.has_value()); + CHECK(r.error().val == 6); +} + +TEST_CASE("traced: transform_error rvalue value path", "[expected_monadic][traced]") { + expected e(std::in_place, 5); + auto r = std::move(e).transform_error([](traced&&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("traced: transform_error rvalue error path", "[expected_monadic][traced]") { + expected e(unexpect, 5); + auto r = std::move(e).transform_error([](traced&& v) { return traced(v.val + 1); }); + REQUIRE(!r.has_value()); + CHECK(r.error().val == 6); +} + +TEST_CASE("traced: transform_error const lvalue value path", "[expected_monadic][traced]") { + const expected e(std::in_place, 5); + auto r = e.transform_error([](const traced&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("traced: transform_error const rvalue value path", "[expected_monadic][traced]") { + const expected e(std::in_place, 5); + auto r = std::move(e).transform_error([](const traced&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +// --- value() throw with non-trivial E --- +TEST_CASE("traced: value() lvalue throws", "[expected_monadic][traced]") { + expected e(unexpect, 42); + CHECK_THROWS_AS(e.value(), beman::expected::bad_expected_access); +} + +TEST_CASE("traced: value() const rvalue throws", "[expected_monadic][traced]") { + const expected e(unexpect, 42); + CHECK_THROWS_AS(std::move(e).value(), beman::expected::bad_expected_access); +} diff --git a/tests/beman/expected/expected_ref.test.cpp b/tests/beman/expected/expected_ref.test.cpp new file mode 100644 index 0000000..26f6775 --- /dev/null +++ b/tests/beman/expected/expected_ref.test.cpp @@ -0,0 +1,890 @@ +// tests/beman/expected/expected_ref.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include + +#include "testing/types.hpp" + +#include +#include + +using namespace beman::expected; + +// ============================================================================= +// Type-level static assertions +// ============================================================================= + +static_assert(std::is_constructible_v, int&>); +static_assert(!std::is_default_constructible_v>); +static_assert(std::is_copy_constructible_v>); +static_assert(std::is_move_constructible_v>); + +static_assert(std::is_same_v>().operator->()), int*>); +static_assert(std::is_same_v>()), int&>); +static_assert(std::is_same_v>().value()), int&>); + +// const expected still returns T* / T& (shallow const) +static_assert(std::is_same_v>().operator->()), int*>); +static_assert(std::is_same_v>()), int&>); + +// Triviality: when E is trivial, copy/move/assign/destroy should be trivial +static_assert(std::is_trivially_copy_constructible_v>); +static_assert(std::is_trivially_move_constructible_v>); +static_assert(std::is_trivially_copy_assignable_v>); +static_assert(std::is_trivially_move_assignable_v>); +static_assert(std::is_trivially_destructible_v>); + +// Non-trivial E: still constructible/assignable but not trivially +static_assert(std::is_copy_constructible_v>); +static_assert(std::is_move_constructible_v>); +static_assert(!std::is_trivially_copy_constructible_v>); +static_assert(!std::is_trivially_move_constructible_v>); +static_assert(!std::is_trivially_destructible_v>); + +// ============================================================================= +// Construction +// ============================================================================= + +TEST_CASE("expected: construct from lvalue reference", "[expected_ref]") { + int x = 42; + expected e(x); + REQUIRE(e.has_value()); + CHECK(&*e == &x); + CHECK(*e == 42); +} + +TEST_CASE("expected: construct from unexpected", "[expected_ref]") { + expected e = unexpected(7); + REQUIRE(!e.has_value()); + CHECK(e.error() == 7); +} + +TEST_CASE("expected: construct from unexpect_t in-place error", "[expected_ref]") { + expected e(unexpect, "err"); + REQUIRE(!e.has_value()); + CHECK(e.error() == "err"); +} + +TEST_CASE("expected: copy construct (copies pointer)", "[expected_ref]") { + int x = 1; + expected a(x); + expected b = a; + REQUIRE(b.has_value()); + CHECK(&*b == &x); +} + +TEST_CASE("expected: move construct (copies pointer)", "[expected_ref]") { + int x = 2; + expected a(x); + expected b = std::move(a); + REQUIRE(b.has_value()); + CHECK(&*b == &x); +} + +TEST_CASE("expected: copy construct error state", "[expected_ref]") { + expected a(unexpect, "oops"); + expected b = a; + REQUIRE(!b.has_value()); + CHECK(b.error() == "oops"); +} + +TEST_CASE("expected: copy construct value state (non-trivial E)", "[expected_ref]") { + int x = 42; + expected a(x); + expected b = a; + REQUIRE(b.has_value()); + CHECK(&*b == &x); +} + +TEST_CASE("expected: construct from derived expected", "[expected_ref]") { + struct Base { + virtual ~Base() = default; + int v; + }; + struct Derived : Base { + Derived(int i) { v = i; } + }; + + Derived d{99}; + expected src(d); + expected dst = src; + REQUIRE(dst.has_value()); + CHECK(dst->v == 99); + CHECK(&*dst == static_cast(&d)); +} + +// ============================================================================= +// Rebind semantics on assignment +// ============================================================================= + +TEST_CASE("expected: rebind reference on assignment from lvalue", "[expected_ref]") { + int x = 1, y = 2; + expected e(x); + e = y; + CHECK(&*e == &y); + CHECK(*e == 2); + CHECK(x == 1); +} + +TEST_CASE("expected: rebind does NOT assign through reference", "[expected_ref]") { + int x = 100, y = 200; + expected e(x); + e = y; + CHECK(x == 100); + CHECK(*e == 200); +} + +TEST_CASE("expected: assign from unexpected transitions to error state", "[expected_ref]") { + int x = 5; + expected e(x); + e = unexpected(99); + REQUIRE(!e.has_value()); + CHECK(e.error() == 99); + CHECK(x == 5); +} + +TEST_CASE("expected: assign lvalue rebinds from error state", "[expected_ref]") { + int x = 7; + expected e = unexpected(1); + e = x; + REQUIRE(e.has_value()); + CHECK(&*e == &x); +} + +TEST_CASE("expected: copy assignment value-value rebinds", "[expected_ref]") { + int x = 1, y = 2; + expected a(x), b(y); + a = b; + CHECK(&*a == &y); + CHECK(x == 1); +} + +TEST_CASE("expected: copy assignment error-error copies error", "[expected_ref]") { + expected a = unexpected(1); + expected b = unexpected(2); + a = b; + CHECK(a.error() == 2); +} + +TEST_CASE("expected: copy assignment value-to-error", "[expected_ref]") { + int x = 5; + expected a(x); + expected b = unexpected(42); + a = b; + REQUIRE(!a.has_value()); + CHECK(a.error() == 42); +} + +TEST_CASE("expected: copy assignment error-to-value", "[expected_ref]") { + int x = 5; + expected a = unexpected(42); + expected b(x); + a = b; + REQUIRE(a.has_value()); + CHECK(&*a == &x); +} + +TEST_CASE("expected: move assignment value-to-value (non-trivial E)", "[expected_ref]") { + int x1 = 1, x2 = 2; + expected a(x1), b(x2); + a = std::move(b); + REQUIRE(a.has_value()); + CHECK(&*a == &x2); +} + +TEST_CASE("expected: move assignment value-to-error (non-trivial E)", "[expected_ref]") { + int x = 5; + expected a(x); + expected b(unexpect, "moved-err"); + a = std::move(b); + REQUIRE(!a.has_value()); + CHECK(a.error() == "moved-err"); +} + +TEST_CASE("expected: move assignment error-to-value (non-trivial E)", "[expected_ref]") { + int x = 5; + expected a(unexpect, "old-err"); + expected b(x); + a = std::move(b); + REQUIRE(a.has_value()); + CHECK(&*a == &x); +} + +TEST_CASE("expected: assign from unexpected&& when already has error", "[expected_ref]") { + expected e = unexpected(1); + e = unexpected(2); + REQUIRE(!e.has_value()); + CHECK(e.error() == 2); +} + +// ============================================================================= +// Shallow const +// ============================================================================= + +TEST_CASE("expected: shallow const allows mutation of referent", "[expected_ref]") { + int x = 10; + const expected e(x); + *e = 20; + CHECK(x == 20); +} + +TEST_CASE("expected: operator-> on const returns T*", "[expected_ref]") { + int x = 5; + const expected e(x); + static_assert(std::is_same_v()), int*>); + *e.operator->() = 99; + CHECK(x == 99); +} + +// ============================================================================= +// Observers +// ============================================================================= + +TEST_CASE("expected: operator* returns T&", "[expected_ref]") { + int x = 42; + expected e(x); + static_assert(std::is_same_v); + *e = 99; + CHECK(x == 99); +} + +TEST_CASE("expected: operator-> returns T*", "[expected_ref]") { + struct S { + int v; + }; + S s{7}; + expected e(s); + CHECK(e->v == 7); + e->v = 99; + CHECK(s.v == 99); +} + +TEST_CASE("expected: value() returns T& or throws", "[expected_ref]") { + int x = 1; + expected e(x); + static_assert(std::is_same_v); + CHECK(e.value() == 1); + e.value() = 2; + CHECK(x == 2); +} + +TEST_CASE("expected: value() throws bad_expected_access on error", "[expected_ref]") { + expected e = unexpected(5); + REQUIRE_THROWS_AS(e.value(), beman::expected::bad_expected_access); +} + +TEST_CASE("expected: error() returns error", "[expected_ref]") { + expected e = unexpected(42); + CHECK(e.error() == 42); +} + +TEST_CASE("expected: value_or returns referred value when has value", "[expected_ref]") { + int x = 42; + expected e(x); + int result = e.value_or(0); + CHECK(result == 42); +} + +TEST_CASE("expected: value_or returns fallback when has error", "[expected_ref]") { + expected e = unexpected(0); + int result = e.value_or(99); + CHECK(result == 99); +} + +TEST_CASE("expected: error_or returns error when has error", "[expected_ref]") { + expected e = unexpected(42); + CHECK(e.error_or(0) == 42); +} + +TEST_CASE("expected: error_or returns default when has value", "[expected_ref]") { + int x = 5; + expected e(x); + CHECK(e.error_or(99) == 99); +} + +TEST_CASE("expected: bool conversion", "[expected_ref]") { + int x = 1; + expected val(x); + expected err = unexpected(0); + CHECK(static_cast(val)); + CHECK(!static_cast(err)); +} + +// ============================================================================= +// emplace +// ============================================================================= + +TEST_CASE("expected: emplace rebinds from value", "[expected_ref]") { + int x = 1, y = 2; + expected e(x); + e.emplace(y); + CHECK(&*e == &y); + CHECK(x == 1); +} + +TEST_CASE("expected: emplace rebinds from error", "[expected_ref]") { + int x = 5; + expected e = unexpected(42); + e.emplace(x); + REQUIRE(e.has_value()); + CHECK(&*e == &x); +} + +// ============================================================================= +// Swap +// ============================================================================= + +TEST_CASE("expected: swap value-value rebinds pointers", "[expected_ref]") { + int x = 1, y = 2; + expected a(x), b(y); + a.swap(b); + CHECK(&*a == &y); + CHECK(&*b == &x); + CHECK(x == 1); + CHECK(y == 2); +} + +TEST_CASE("expected: swap value-error", "[expected_ref]") { + int x = 1; + expected a(x), b(unexpect, 99); + a.swap(b); + REQUIRE(!a.has_value()); + REQUIRE(b.has_value()); + CHECK(a.error() == 99); + CHECK(&*b == &x); +} + +TEST_CASE("expected: swap error-error", "[expected_ref]") { + expected a(unexpect, 1), b(unexpect, 2); + a.swap(b); + CHECK(a.error() == 2); + CHECK(b.error() == 1); +} + +TEST_CASE("expected: friend swap", "[expected_ref]") { + int x = 10, y = 20; + expected a(x), b(y); + swap(a, b); + CHECK(&*a == &y); + CHECK(&*b == &x); +} + +// ============================================================================= +// Equality +// ============================================================================= + +TEST_CASE("expected: equality with expected", "[expected_ref]") { + int x = 5, y = 5, z = 6; + expected a(x), b(y), c(z); + CHECK(a == b); + CHECK(!(a == c)); +} + +TEST_CASE("expected: equality with value type", "[expected_ref]") { + int x = 42; + expected e(x); + CHECK(e == 42); + CHECK(!(e == 99)); +} + +TEST_CASE("expected: equality with unexpected", "[expected_ref]") { + expected e = unexpected(7); + CHECK(e == unexpected(7)); + CHECK(!(e == unexpected(8))); +} + +TEST_CASE("expected: inequality value vs error", "[expected_ref]") { + int x = 5; + expected val(x); + expected err = unexpected(5); + CHECK(!(val == err)); +} + +// ============================================================================= +// Monadic operations +// ============================================================================= + +TEST_CASE("expected: and_then passes T& to callable", "[expected_ref]") { + int x = 5; + expected e(x); + auto r = e.and_then([](int& v) -> expected { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 10); +} + +TEST_CASE("expected: and_then on error forwards error", "[expected_ref]") { + expected e = unexpected(42); + auto r = e.and_then([](int& v) -> expected { return v * 2; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 42); +} + +TEST_CASE("expected: transform passes T& to callable", "[expected_ref]") { + int x = 3; + expected e(x); + auto r = e.transform([](int& v) { return v + 1; }); + REQUIRE(r.has_value()); + CHECK(*r == 4); +} + +TEST_CASE("expected: transform on error forwards error", "[expected_ref]") { + expected e = unexpected(99); + auto r = e.transform([](int& v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 99); +} + +TEST_CASE("expected: or_else passes error to callable", "[expected_ref]") { + expected e = unexpected(99); + int x = 0; + auto r = e.or_else([&](int v) -> expected { + x = v; + return unexpected(v); + }); + CHECK(x == 99); + REQUIRE(!r.has_value()); + CHECK(r.error() == 99); +} + +TEST_CASE("expected: or_else on value returns value", "[expected_ref]") { + int x = 42; + expected e(x); + auto r = e.or_else([](int) -> expected { return unexpected(0); }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("expected: transform_error transforms error", "[expected_ref]") { + expected e = unexpected(5); + auto r = e.transform_error([](int v) -> std::string { return std::to_string(v); }); + REQUIRE(!r.has_value()); + CHECK(r.error() == "5"); +} + +TEST_CASE("expected: transform_error on value preserves value", "[expected_ref]") { + int x = 42; + expected e(x); + auto r = e.transform_error([](int v) -> std::string { return std::to_string(v); }); + REQUIRE(r.has_value()); + CHECK(*r == 42); + CHECK(&*r == &x); +} + +TEST_CASE("expected: transform to void", "[expected_ref]") { + int x = 5; + int out = 0; + expected e(x); + auto r = e.transform([&](int& v) { out = v; }); + REQUIRE(r.has_value()); + CHECK(out == 5); +} + +TEST_CASE("expected: transform to void on error - propagates", "[expected_ref]") { + expected e(unexpect, 5); + bool called = false; + auto r = e.transform([&](int&) { called = true; }); + CHECK(!called); + REQUIRE(!r.has_value()); + CHECK(r.error() == 5); +} + +TEST_CASE("expected: const transform to void on error - propagates", "[expected_ref]") { + const expected e(unexpect, 5); + bool called = false; + auto r = e.transform([&](int&) { called = true; }); + CHECK(!called); + REQUIRE(!r.has_value()); + CHECK(r.error() == 5); +} + +// ============================================================================= +// const-qualified monadic operations +// ============================================================================= + +TEST_CASE("expected: const and_then", "[expected_ref]") { + int x = 10; + const expected e(x); + auto r = e.and_then([](int& v) -> expected { return v + 1; }); + REQUIRE(r.has_value()); + CHECK(*r == 11); +} + +TEST_CASE("expected: const transform", "[expected_ref]") { + int x = 3; + const expected e(x); + auto r = e.transform([](int& v) { return v * 3; }); + REQUIRE(r.has_value()); + CHECK(*r == 9); +} + +TEST_CASE("expected: const transform on error - propagates", "[expected_ref]") { + const expected e(unexpect, 5); + bool called = false; + auto r = e.transform([&](int&) { + called = true; + return 0; + }); + CHECK(!called); + REQUIRE(!r.has_value()); + CHECK(r.error() == 5); +} + +TEST_CASE("expected: const transform to void - calls F", "[expected_ref]") { + int x = 3; + int out = 0; + const expected e(x); + auto r = e.transform([&](int& v) { out = v; }); + REQUIRE(r.has_value()); + CHECK(out == 3); +} + +// ============================================================================= +// rvalue-qualified monadic operations +// ============================================================================= + +TEST_CASE("expected: rvalue and_then", "[expected_ref]") { + int x = 5; + expected e(x); + auto r = std::move(e).and_then([](int& v) -> expected { return v * 3; }); + REQUIRE(r.has_value()); + CHECK(*r == 15); +} + +TEST_CASE("expected: rvalue transform_error moves error", "[expected_ref]") { + expected e(unexpect, "hello"); + auto r = std::move(e).transform_error([](std::string&& s) -> std::string { return s + " world"; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == "hello world"); +} + +// ============================================================================= +// Additional coverage tests: T& rvalue/const-rvalue monadic and traced paths +// ============================================================================= +using namespace beman::expected::testing; + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- +TEST_CASE("and_then rvalue on error short-circuits", "[expected_ref]") { + expected e(unexpect, "err"); + auto r = std::move(e).and_then([](int& v) -> expected { return v; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == "err"); +} + +TEST_CASE("and_then const rvalue on error short-circuits", "[expected_ref]") { + const expected e(unexpect, "err"); + auto r = std::move(e).and_then([](int& v) -> expected { return v; }); + REQUIRE(!r.has_value()); +} + +// --- Monadic: and_then const-rvalue on VALUE state --- +TEST_CASE("and_then const rvalue on value calls F", "[expected_ref]") { + int x = 5; + const expected e(x); + auto r = std::move(e).and_then([](int& v) -> expected { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 10); +} + +// --- Monadic: or_else rvalue/const-rvalue on VALUE state --- +TEST_CASE("or_else rvalue on value short-circuits", "[expected_ref]") { + int x = 42; + expected e(x); + auto r = std::move(e).or_else([](int) -> expected { + static int dummy = 0; + return expected(dummy); + }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("or_else const rvalue on value short-circuits", "[expected_ref]") { + int x = 42; + const expected e(x); + auto r = std::move(e).or_else([](int) -> expected { + static int dummy = 0; + return expected(dummy); + }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +// --- Monadic: or_else rvalue/const-rvalue on ERROR state --- +TEST_CASE("or_else rvalue on error calls F", "[expected_ref]") { + expected e(unexpect, 3); + auto r = std::move(e).or_else([](int v) -> expected { + static int result = 0; + result = v * 10; + return expected(result); + }); + REQUIRE(r.has_value()); + CHECK(*r == 30); +} + +TEST_CASE("or_else const rvalue on error calls F", "[expected_ref]") { + const expected e(unexpect, 3); + auto r = std::move(e).or_else([](int v) -> expected { + static int result = 0; + result = v * 10; + return expected(result); + }); + REQUIRE(r.has_value()); + CHECK(*r == 30); +} + +// --- Monadic: transform rvalue/const-rvalue on ERROR state --- +TEST_CASE("transform rvalue on error short-circuits", "[expected_ref]") { + expected e(unexpect, "err"); + auto r = std::move(e).transform([](int& v) { return v * 2; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == "err"); +} + +TEST_CASE("transform const rvalue on error short-circuits", "[expected_ref]") { + const expected e(unexpect, "err"); + auto r = std::move(e).transform([](int& v) { return v * 2; }); + REQUIRE(!r.has_value()); +} + +// --- Monadic: transform const-rvalue on VALUE state --- +TEST_CASE("transform const rvalue on value calls F", "[expected_ref]") { + int x = 4; + const expected e(x); + auto r = std::move(e).transform([](int& v) { return v + 1; }); + REQUIRE(r.has_value()); + CHECK(*r == 5); +} + +// --- Monadic: transform_error rvalue/const-rvalue --- +TEST_CASE("transform_error rvalue on value short-circuits", "[expected_ref]") { + int x = 42; + expected e(x); + auto r = std::move(e).transform_error([](int v) { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("transform_error const rvalue on value short-circuits", "[expected_ref]") { + int x = 42; + const expected e(x); + auto r = std::move(e).transform_error([](int v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("transform_error const rvalue on error calls F", "[expected_ref]") { + const expected e(unexpect, 7); + auto r = std::move(e).transform_error([](int v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 8); +} + +TEST_CASE("transform_error const lvalue on error calls F", "[expected_ref]") { + const expected e(unexpect, 7); + auto r = e.transform_error([](int v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 8); +} + +// --- value() throw paths --- +TEST_CASE("value() throws on error state", "[expected_ref]") { + expected e(unexpect, 42); + CHECK_THROWS_AS(e.value(), beman::expected::bad_expected_access); +} + +// --- Cross-type equality --- +TEST_CASE("equality error vs value", "[expected_ref]") { + int x = 1; + expected a(x); + expected b(unexpect, 0); + CHECK_FALSE(a == b); +} + +// --------------------------------------------------------------------------- +// expected with non-trivial E: lvalue monadic overloads +// --------------------------------------------------------------------------- + +TEST_CASE("ref: and_then lvalue value path", "[expected_ref][traced]") { + int x = 5; + expected e(x); + auto r = e.and_then([](int& v) -> expected { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 10); +} + +TEST_CASE("ref: and_then lvalue error path", "[expected_ref][traced]") { + expected e(unexpect, 5); + auto r = e.and_then([](int&) -> expected { return 0; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("ref: and_then rvalue error path", "[expected_ref][traced]") { + expected e(unexpect, 5); + auto r = std::move(e).and_then([](int&) -> expected { return 0; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("ref: and_then const lvalue error path", "[expected_ref][traced]") { + const expected e(unexpect, 5); + auto r = e.and_then([](int&) -> expected { return 0; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("ref: and_then const rvalue error path", "[expected_ref][traced]") { + const expected e(unexpect, 5); + auto r = std::move(e).and_then([](int&) -> expected { return 0; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("ref: or_else lvalue value path", "[expected_ref][traced]") { + int x = 5; + expected e(x); + auto r = e.or_else([](traced&) -> expected { + static int dummy = 0; + return expected(dummy); + }); + REQUIRE(r.has_value()); + CHECK(*r == 5); +} + +TEST_CASE("ref: or_else rvalue value path", "[expected_ref][traced]") { + int x = 5; + expected e(x); + auto r = std::move(e).or_else([](traced&&) -> expected { + static int dummy = 0; + return expected(dummy); + }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: or_else const rvalue value path", "[expected_ref][traced]") { + int x = 5; + const expected e(x); + auto r = std::move(e).or_else([](const traced&) -> expected { + static int dummy = 0; + return expected(dummy); + }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform lvalue value and error paths", "[expected_ref][traced]") { + int x = 4; + expected e(x); + auto rv = e.transform([](int& v) { return v + 1; }); + REQUIRE(rv.has_value()); + CHECK(*rv == 5); + + expected e2(unexpect, 1); + auto re = e2.transform([](int&) { return 0; }); + REQUIRE(!re.has_value()); +} + +TEST_CASE("ref: transform rvalue value path", "[expected_ref][traced]") { + int x = 4; + expected e(x); + auto r = std::move(e).transform([](int& v) { return v + 1; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform const rvalue value path", "[expected_ref][traced]") { + int x = 4; + const expected e(x); + auto r = std::move(e).transform([](int& v) { return v + 1; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform to void lvalue", "[expected_ref][traced]") { + int x = 1; + expected e(x); + auto r = e.transform([](int&) {}); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error lvalue value path", "[expected_ref][traced]") { + int x = 5; + expected e(x); + auto r = e.transform_error([](traced&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error rvalue value path", "[expected_ref][traced]") { + int x = 5; + expected e(x); + auto r = std::move(e).transform_error([](traced&&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error const lvalue value path", "[expected_ref][traced]") { + int x = 5; + const expected e(x); + auto r = e.transform_error([](const traced&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error const rvalue value path", "[expected_ref][traced]") { + int x = 5; + const expected e(x); + auto r = std::move(e).transform_error([](const traced&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error lvalue error path", "[expected_ref][traced]") { + expected e(unexpect, 5); + auto r = e.transform_error([](traced& v) { return traced(v.val + 1); }); + REQUIRE(!r.has_value()); + CHECK(r.error().val == 6); +} + +TEST_CASE("ref: copy ctor error state", "[expected_ref][traced]") { + expected a(unexpect, 10); + expected b(a); + REQUIRE(!b.has_value()); + CHECK(b.error().val == 10); +} + +TEST_CASE("ref: converting copy ctor error state", "[expected_ref][traced]") { + expected a(unexpect, narrowed(7)); + expected b(a); + REQUIRE(!b.has_value()); + CHECK(b.error().val == 7); +} + +TEST_CASE("ref: assign unexpected to value state", "[expected_ref][traced]") { + int x = 5; + expected e(x); + e = unexpected(traced(42)); + REQUIRE(!e.has_value()); + CHECK(e.error().val == 42); +} + +TEST_CASE("ref: move assign error-to-error", "[expected_ref][traced]") { + expected a(unexpect, 1); + expected b(unexpect, 2); + b = std::move(a); + CHECK(b.error().val == 1); +} + +TEST_CASE("ref: swap value and error state", "[expected_ref][traced]") { + int x = 5; + expected a(x); + expected b(unexpect, 7); + a.swap(b); + REQUIRE(!a.has_value()); + CHECK(a.error().val == 7); + REQUIRE(b.has_value()); + CHECK(*b == 5); +} + +TEST_CASE("ref: equality both errors", "[expected_ref][traced]") { + expected a(unexpect, 1); + expected b(unexpect, 1); + CHECK(a == b); +} + +TEST_CASE("ref: equality error vs value", "[expected_ref][traced]") { + int x = 1; + expected a(x); + expected b(unexpect, 0); + CHECK_FALSE(a == b); +} diff --git a/tests/beman/expected/expected_ref_and_then_wrong_error_type_fail.cpp b/tests/beman/expected/expected_ref_and_then_wrong_error_type_fail.cpp new file mode 100644 index 0000000..1a708bf --- /dev/null +++ b/tests/beman/expected/expected_ref_and_then_wrong_error_type_fail.cpp @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: and_then on expected must return expected with same error_type +// EXPECT: "F must return expected with the same error_type" +#include +#include +void test() { + int x = 0; + beman::expected::expected e(x); + // F returns expected but error_type must be int + e.and_then([](int& v) -> beman::expected::expected { + return beman::expected::unexpected("err"); + }); +} diff --git a/tests/beman/expected/expected_ref_both.test.cpp b/tests/beman/expected/expected_ref_both.test.cpp new file mode 100644 index 0000000..3f58da9 --- /dev/null +++ b/tests/beman/expected/expected_ref_both.test.cpp @@ -0,0 +1,686 @@ +// tests/beman/expected/expected_ref_both.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include + +#include "testing/types.hpp" + +#include +#include + +using namespace beman::expected; + +// ============================================================================= +// Type-level static assertions +// ============================================================================= + +// No default constructor — T& cannot be default-initialized +static_assert(!std::is_default_constructible_v>); + +// Constructible from lvalue (value side) +static_assert(std::is_constructible_v, int&>); + +// Copy/move constructible +static_assert(std::is_copy_constructible_v>); +static_assert(std::is_move_constructible_v>); + +// Fully trivial: both sides are pointers — trivially copyable/movable/destructible +static_assert(std::is_trivially_copy_constructible_v>); +static_assert(std::is_trivially_move_constructible_v>); +static_assert(std::is_trivially_copy_assignable_v>); +static_assert(std::is_trivially_move_assignable_v>); +static_assert(std::is_trivially_destructible_v>); + +// operator-> returns T* (shallow const) +static_assert(std::is_same_v>().operator->()), int*>); +static_assert(std::is_same_v>().operator->()), int*>); + +// operator* returns T& (shallow const) +static_assert(std::is_same_v>()), int&>); +static_assert(std::is_same_v>()), int&>); + +// value() returns T& (shallow const) +static_assert(std::is_same_v>().value()), int&>); +static_assert(std::is_same_v>().value()), int&>); + +// error() returns E& (shallow const) +static_assert(std::is_same_v>().error()), int&>); +static_assert(std::is_same_v>().error()), int&>); + +// Cannot construct from temporary value (T& rvalue deleted) +static_assert(!std::is_constructible_v, int&&>); + +// Cannot construct from temporary error (rvalue or any type creating a temp E) +static_assert(!std::is_constructible_v, unexpect_t, int&&>); +// Cross-type temporary: float would create a temp double when binding const double& +static_assert(!std::is_constructible_v, unexpect_t, float>); +// Lvalue of same type is fine +static_assert(std::is_constructible_v, unexpect_t, const double&>); + +// Converting construction from expected +static_assert(std::is_constructible_v, const expected&>); + +// ============================================================================= +// Construction — value side +// ============================================================================= + +TEST_CASE("expected: construct from lvalue reference (value)", "[expected_ref_both]") { + int x = 42; + expected e(x); + REQUIRE(e.has_value()); + CHECK(&*e == &x); + CHECK(*e == 42); +} + +TEST_CASE("expected: operator-> returns T*", "[expected_ref_both]") { + std::string s = "hello"; + expected e(s); + REQUIRE(e.has_value()); + CHECK(e->size() == 5); +} + +// ============================================================================= +// Construction — error side +// ============================================================================= + +TEST_CASE("expected: construct from error lvalue reference", "[expected_ref_both]") { + int err = 7; + expected e(unexpect, err); + REQUIRE(!e.has_value()); + CHECK(&e.error() == &err); + CHECK(e.error() == 7); +} + +// ============================================================================= +// Copy and move construction +// ============================================================================= + +TEST_CASE("expected: copy construct preserves value pointer", "[expected_ref_both]") { + int x = 42; + expected a(x); + expected b = a; + REQUIRE(b.has_value()); + CHECK(&*b == &x); +} + +TEST_CASE("expected: copy construct preserves error pointer", "[expected_ref_both]") { + int err = 5; + expected a(unexpect, err); + expected b = a; + REQUIRE(!b.has_value()); + CHECK(&b.error() == &err); +} + +TEST_CASE("expected: move construct preserves value pointer", "[expected_ref_both]") { + int x = 42; + expected a(x); + expected b = std::move(a); + REQUIRE(b.has_value()); + CHECK(&*b == &x); +} + +TEST_CASE("expected: move construct preserves error pointer", "[expected_ref_both]") { + int err = 5; + expected a(unexpect, err); + expected b = std::move(a); + REQUIRE(!b.has_value()); + CHECK(&b.error() == &err); +} + +// ============================================================================= +// Value rebind semantics +// ============================================================================= + +TEST_CASE("expected: value rebind via copy assignment", "[expected_ref_both]") { + int x1 = 1, x2 = 2; + expected a(x1); + expected b(x2); + a = b; + REQUIRE(a.has_value()); + CHECK(&*a == &x2); + // x1 unchanged — rebind, not assign-through + CHECK(x1 == 1); +} + +TEST_CASE("expected: rebind does NOT assign through value reference", "[expected_ref_both]") { + int x1 = 10, x2 = 20; + expected a(x1); + expected b(x2); + a = b; + CHECK(x1 == 10); // x1 unchanged + CHECK(*a == 20); +} + +TEST_CASE("expected: value rebind operator=(U&&)", "[expected_ref_both]") { + int x1 = 1, x2 = 99; + expected e(x1); + e = x2; + REQUIRE(e.has_value()); + CHECK(&*e == &x2); + CHECK(x1 == 1); // x1 unchanged +} + +TEST_CASE("expected: transition from error to value via rebind", "[expected_ref_both]") { + int x = 42, err = 5; + expected e(unexpect, err); + REQUIRE(!e.has_value()); + e = x; + REQUIRE(e.has_value()); + CHECK(&*e == &x); +} + +// ============================================================================= +// Error rebind semantics +// ============================================================================= + +TEST_CASE("expected: error rebind via copy assignment", "[expected_ref_both]") { + int e1 = 1, e2 = 2; + expected a(unexpect, e1); + expected b(unexpect, e2); + a = b; + REQUIRE(!a.has_value()); + CHECK(&a.error() == &e2); + // e1 unchanged — rebind, not assign-through + CHECK(e1 == 1); +} + +TEST_CASE("expected: transition from value to error via copy assignment", "[expected_ref_both]") { + int x = 42, err = 5; + expected a(x); + expected b(unexpect, err); + a = b; + REQUIRE(!a.has_value()); + CHECK(&a.error() == &err); +} + +// Safe alternative to e = unexpected(err): move-assign from a named expected. +// No operator=(unexpected) exists for expected — it would bind E& +// to temporary storage creating a dangling reference. +TEST_CASE("expected: rebind error via move-assign from temporary expected", "[expected_ref_both]") { + int x = 1, new_err = 7; + expected e(x); + e = expected(unexpect, new_err); + REQUIRE(!e.has_value()); + CHECK(&e.error() == &new_err); +} + +// ============================================================================= +// emplace +// ============================================================================= + +TEST_CASE("expected: emplace rebinds value reference", "[expected_ref_both]") { + int x1 = 1, x2 = 99; + expected e(x1); + int& r = e.emplace(x2); + REQUIRE(e.has_value()); + CHECK(&r == &x2); + CHECK(&*e == &x2); + CHECK(x1 == 1); // x1 unchanged +} + +TEST_CASE("expected: emplace from error state", "[expected_ref_both]") { + int x = 42, err = 5; + expected e(unexpect, err); + e.emplace(x); + REQUIRE(e.has_value()); + CHECK(&*e == &x); +} + +// ============================================================================= +// Shallow const on both sides +// ============================================================================= + +TEST_CASE("expected: shallow const allows mutation of value referent", "[expected_ref_both]") { + int x = 10; + const expected e(x); + *e = 20; + CHECK(x == 20); +} + +TEST_CASE("expected: shallow const allows mutation of error referent", "[expected_ref_both]") { + int err = 10; + const expected e(unexpect, err); + e.error() = 20; + CHECK(err == 20); +} + +// ============================================================================= +// Observers +// ============================================================================= + +TEST_CASE("expected: operator*() returns T& (mutation visible)", "[expected_ref_both]") { + int x = 42; + expected e(x); + *e = 99; + CHECK(x == 99); +} + +TEST_CASE("expected: value() returns T& (throws on error)", "[expected_ref_both]") { + int x = 42; + expected e(x); + static_assert(std::is_same_v); + CHECK(&e.value() == &x); +} + +TEST_CASE("expected: value() throws bad_expected_access on error", "[expected_ref_both]") { + int err = 5; + expected e(unexpect, err); + REQUIRE_THROWS_AS(e.value(), beman::expected::bad_expected_access); +} + +TEST_CASE("expected: error() returns E& (mutation visible)", "[expected_ref_both]") { + int err = 7; + expected e(unexpect, err); + static_assert(std::is_same_v); + e.error() = 99; + CHECK(err == 99); +} + +TEST_CASE("expected: value_or returns T by value", "[expected_ref_both]") { + int x = 42, err = 0; + expected a(x); + expected b(unexpect, err); + CHECK(a.value_or(0) == 42); + CHECK(b.value_or(99) == 99); +} + +TEST_CASE("expected: error_or returns E by value", "[expected_ref_both]") { + int err = 7, x = 0; + expected a(unexpect, err); + expected b(x); + CHECK(a.error_or(0) == 7); + CHECK(b.error_or(99) == 99); +} + +// ============================================================================= +// Swap +// ============================================================================= + +TEST_CASE("expected: swap value-value rebinds pointers", "[expected_ref_both]") { + int x1 = 1, x2 = 2; + expected a(x1), b(x2); + a.swap(b); + CHECK(&*a == &x2); + CHECK(&*b == &x1); + CHECK(x1 == 1); // values unchanged — just rebind + CHECK(x2 == 2); +} + +TEST_CASE("expected: swap error-error rebinds pointers", "[expected_ref_both]") { + int e1 = 1, e2 = 2; + expected a(unexpect, e1), b(unexpect, e2); + a.swap(b); + CHECK(&a.error() == &e2); + CHECK(&b.error() == &e1); +} + +TEST_CASE("expected: swap value-error", "[expected_ref_both]") { + int x = 42, err = 5; + expected a(x), b(unexpect, err); + a.swap(b); + REQUIRE(!a.has_value()); + REQUIRE(b.has_value()); + CHECK(&a.error() == &err); + CHECK(&*b == &x); +} + +TEST_CASE("expected: swap error-value", "[expected_ref_both]") { + int x = 42, err = 5; + expected a(unexpect, err), b(x); + a.swap(b); + REQUIRE(a.has_value()); + REQUIRE(!b.has_value()); + CHECK(&*a == &x); + CHECK(&b.error() == &err); +} + +// ============================================================================= +// Equality +// ============================================================================= + +TEST_CASE("expected: equality of two value-holding instances", "[expected_ref_both]") { + int x1 = 42, x2 = 42; + expected a(x1), b(x2); + CHECK(a == b); +} + +TEST_CASE("expected: inequality when values differ", "[expected_ref_both]") { + int x1 = 1, x2 = 2; + expected a(x1), b(x2); + CHECK(!(a == b)); +} + +TEST_CASE("expected: equality with value type", "[expected_ref_both]") { + int x = 42; + expected e(x); + CHECK(e == 42); + CHECK(!(e == 99)); +} + +TEST_CASE("expected: equality with unexpected (compares error values)", "[expected_ref_both]") { + int err = 7; + expected e(unexpect, err); + CHECK(e == unexpected(7)); + CHECK(!(e == unexpected(8))); +} + +TEST_CASE("expected: value vs error always unequal", "[expected_ref_both]") { + int x = 0, err = 0; + expected a(x), b(unexpect, err); + CHECK(!(a == b)); +} + +// ============================================================================= +// Monadic operations +// ============================================================================= + +TEST_CASE("expected: and_then works on value side", "[expected_ref_both]") { + int x = 5; + expected e(x); + auto r = e.and_then([](int& v) -> expected { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 10); +} + +TEST_CASE("expected: and_then propagates error ref", "[expected_ref_both]") { + int err = 3; + expected e(unexpect, err); + auto r = e.and_then([](int& v) -> expected { return v; }); + REQUIRE(!r.has_value()); + CHECK(&r.error() == &err); +} + +TEST_CASE("expected: or_else receives E& and can inspect error", "[expected_ref_both]") { + int err = 3; + expected e(unexpect, err); + int result_val = 0; + auto r = e.or_else([&result_val](int& v) -> expected { + result_val = v * 10; + return result_val; + }); + REQUIRE(r.has_value()); + CHECK(*r == 30); +} + +TEST_CASE("expected: or_else propagates value ref", "[expected_ref_both]") { + int x = 42; + expected e(x); + int other_err = 0; + auto r = + e.or_else([&other_err](int&) -> expected { return expected(unexpect, other_err); }); + REQUIRE(r.has_value()); + CHECK(&*r == &x); +} + +TEST_CASE("expected: transform transforms value", "[expected_ref_both]") { + int x = 5; + expected e(x); + auto r = e.transform([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 10); +} + +TEST_CASE("expected: transform propagates error ref", "[expected_ref_both]") { + int err = 5; + expected e(unexpect, err); + auto r = e.transform([](int& v) { return v * 2; }); + REQUIRE(!r.has_value()); + CHECK(&r.error() == &err); +} + +TEST_CASE("expected: transform_error transforms E&", "[expected_ref_both]") { + int err = 5; + expected e(unexpect, err); + auto r = e.transform_error([](int& v) -> std::string { return std::to_string(v); }); + REQUIRE(!r.has_value()); + CHECK(r.error() == "5"); +} + +TEST_CASE("expected: transform_error propagates value ref", "[expected_ref_both]") { + int x = 42; + expected e(x); + auto r = e.transform_error([](int& v) -> std::string { return std::to_string(v); }); + REQUIRE(r.has_value()); + CHECK(&*r == &x); +} + +// ============================================================================= +// Converting construction from expected +// ============================================================================= + +TEST_CASE("expected: converting copy construct with value", "[expected_ref_both]") { + int x = 42; + expected src(x); + expected e(src); + REQUIRE(e.has_value()); + CHECK(&*e == &x); +} + +TEST_CASE("expected: converting copy construct with error", "[expected_ref_both]") { + int err = 7; + expected src(unexpect, err); + expected e(src); + REQUIRE(!e.has_value()); + CHECK(&e.error() == &err); +} + +// ============================================================================= +// Dangling prevention — verify lvalue bindings work +// ============================================================================= + +TEST_CASE("expected: lvalue value reference compiles and is addressable", "[expected_ref_both]") { + int x = 1; + expected e(x); + CHECK(&*e == &x); +} + +TEST_CASE("expected: lvalue error reference compiles and is addressable", "[expected_ref_both]") { + int err = 1; + expected e(unexpect, err); + CHECK(&e.error() == &err); +} + +// ============================================================================= +// Additional coverage tests: T&,E& rvalue/const-rvalue monadic paths +// ============================================================================= +using namespace beman::expected::testing; + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- +TEST_CASE("and_then rvalue on error short-circuits", "[expected_ref_both]") { + int err = 5; + expected e(unexpect, err); + auto r = std::move(e).and_then([](int&) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("and_then const rvalue on error short-circuits", "[expected_ref_both]") { + int err = 5; + const expected e(unexpect, err); + auto r = std::move(e).and_then([](int&) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(!r.has_value()); +} + +// --- Monadic: or_else rvalue/const-rvalue on VALUE state --- +TEST_CASE("or_else rvalue on value short-circuits", "[expected_ref_both]") { + int x = 42; + expected e(x); + auto r = std::move(e).or_else([](int&) -> expected { + static int dummy = 0; + return expected(dummy); + }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("or_else const rvalue on value short-circuits", "[expected_ref_both]") { + int x = 42; + const expected e(x); + auto r = std::move(e).or_else([](int&) -> expected { + static int dummy = 0; + return expected(dummy); + }); + REQUIRE(r.has_value()); +} + +// --- Monadic: transform rvalue/const-rvalue on ERROR state --- +TEST_CASE("transform rvalue on error short-circuits", "[expected_ref_both]") { + int err = 3; + expected e(unexpect, err); + auto r = std::move(e).transform([](int& v) { return v * 2; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("transform const rvalue on error short-circuits", "[expected_ref_both]") { + int err = 3; + const expected e(unexpect, err); + auto r = std::move(e).transform([](int& v) { return v * 2; }); + REQUIRE(!r.has_value()); +} + +// --- Monadic: transform_error rvalue/const-rvalue --- +TEST_CASE("transform_error rvalue on value short-circuits", "[expected_ref_both]") { + int x = 42; + expected e(x); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("transform_error const rvalue on value short-circuits", "[expected_ref_both]") { + int x = 42; + const expected e(x); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("transform_error rvalue on error calls F", "[expected_ref_both]") { + int err = 5; + expected e(unexpect, err); + auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 6); +} + +TEST_CASE("transform_error const rvalue on error calls F", "[expected_ref_both]") { + int err = 5; + const expected e(unexpect, err); + auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 6); +} + +// --- Cross-type equality --- +TEST_CASE("equality error vs value", "[expected_ref_both]") { + int x = 1, err = 0; + expected a(x); + expected b(unexpect, err); + CHECK_FALSE(a == b); +} + +// --------------------------------------------------------------------------- +// expected: lvalue monadic paths +// --------------------------------------------------------------------------- + +TEST_CASE("ref: and_then lvalue value and error paths", "[expected_ref_both][traced]") { + int x = 5, err = 3; + expected ev(x); + auto rv = ev.and_then([](int& v) -> expected { + static int dummy = 0; + dummy = v * 2; + return dummy; + }); + REQUIRE(rv.has_value()); + + expected ee(unexpect, err); + auto re = ee.and_then([](int&) -> expected { + static int dummy = 0; + return expected(unexpect, dummy); + }); + REQUIRE(!re.has_value()); +} + +TEST_CASE("ref: or_else lvalue value and error paths", "[expected_ref_both][traced]") { + int x = 5, err = 3; + expected ev(x); + auto rv = ev.or_else([](int&) -> expected { + static int dummy = 0; + return expected(dummy); + }); + REQUIRE(rv.has_value()); + + expected ee(unexpect, err); + auto re = ee.or_else([](int& v) -> expected { + static int result = 0; + result = v * 10; + return expected(result); + }); + REQUIRE(re.has_value()); +} + +TEST_CASE("ref: transform lvalue value and error paths", "[expected_ref_both][traced]") { + int x = 4, err = 3; + expected ev(x); + auto rv = ev.transform([](int& v) { return v + 1; }); + REQUIRE(rv.has_value()); + CHECK(*rv == 5); + + expected ee(unexpect, err); + auto re = ee.transform([](int&) { return 0; }); + REQUIRE(!re.has_value()); +} + +TEST_CASE("ref: transform rvalue value path", "[expected_ref_both][traced]") { + int x = 4; + expected e(x); + auto r = std::move(e).transform([](int& v) { return v + 1; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform const rvalue value path", "[expected_ref_both][traced]") { + int x = 4; + const expected e(x); + auto r = std::move(e).transform([](int& v) { return v + 1; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error lvalue value and error paths", "[expected_ref_both][traced]") { + int x = 5, err = 7; + expected ev(x); + auto rv = ev.transform_error([](int&) { return 0; }); + REQUIRE(rv.has_value()); + + expected ee(unexpect, err); + auto re = ee.transform_error([](int& v) { return v + 1; }); + REQUIRE(!re.has_value()); + CHECK(re.error() == 8); +} + +TEST_CASE("ref: transform_error rvalue value path", "[expected_ref_both][traced]") { + int x = 5; + expected e(x); + auto r = std::move(e).transform_error([](int&) { return 0; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error const rvalue value path", "[expected_ref_both][traced]") { + int x = 5; + const expected e(x); + auto r = std::move(e).transform_error([](int&) { return 0; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: equality both errors same value", "[expected_ref_both][traced]") { + int e1 = 1, e2 = 1; + expected a(unexpect, e1); + expected b(unexpect, e2); + CHECK(a == b); +} diff --git a/tests/beman/expected/expected_ref_both_and_then_wrong_error_type_fail.cpp b/tests/beman/expected/expected_ref_both_and_then_wrong_error_type_fail.cpp new file mode 100644 index 0000000..672b8df --- /dev/null +++ b/tests/beman/expected/expected_ref_both_and_then_wrong_error_type_fail.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: and_then on expected must return expected with same error_type (E&) +// EXPECT: "F must return expected with the same error_type" +#include +void test() { + int x = 0; + beman::expected::expected e(x); + // F returns expected but error_type must be int& + e.and_then([](int& v) -> beman::expected::expected { return v; }); +} diff --git a/tests/beman/expected/expected_ref_both_assign_unexpected_fail.cpp b/tests/beman/expected/expected_ref_both_assign_unexpected_fail.cpp new file mode 100644 index 0000000..2dd24f5 --- /dev/null +++ b/tests/beman/expected/expected_ref_both_assign_unexpected_fail.cpp @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected cannot be assigned from unexpected. +// No operator=(unexpected...) exists: binding E& to temporary unexpected +// storage would create a dangling reference. Copy-assign from another +// expected(unexpect, new_err) to rebind the error reference safely. +// EXPECT: "no matching function" +#include +using namespace beman::expected; +void test() { + int x = 1; + expected e(x); + e = unexpected(7); // must not compile +} diff --git a/tests/beman/expected/expected_ref_both_construct_from_unexpected_fail.cpp b/tests/beman/expected/expected_ref_both_construct_from_unexpected_fail.cpp new file mode 100644 index 0000000..81c057a --- /dev/null +++ b/tests/beman/expected/expected_ref_both_construct_from_unexpected_fail.cpp @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected cannot be constructed from unexpected. +// A value-typed unexpected stores G by value; binding E& to it would create a +// dangling reference when the unexpected object is destroyed. Construction from a +// reference-typed unexpected (which holds an external object) is allowed. +// EXPECT: "no matching function" +#include +using namespace beman::expected; +void test() { + expected e = unexpected(7); // must not compile +} diff --git a/tests/beman/expected/expected_ref_both_e_array_fail.cpp b/tests/beman/expected/expected_ref_both_e_array_fail.cpp new file mode 100644 index 0000000..60275ef --- /dev/null +++ b/tests/beman/expected/expected_ref_both_e_array_fail.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: E must not be an array type in expected +// EXPECT: "E must not be an array type" +#include +void test() { + using arr = int[3]; + int x = 0; + beman::expected::expected e(x); +} diff --git a/tests/beman/expected/expected_ref_both_inplace_value_fail.cpp b/tests/beman/expected/expected_ref_both_inplace_value_fail.cpp new file mode 100644 index 0000000..9a5f69f --- /dev/null +++ b/tests/beman/expected/expected_ref_both_inplace_value_fail.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected has no in_place_t value constructor +// EXPECT: "no matching function" +#include +#include +void test() { + int err = 0; + beman::expected::expected e(std::in_place, 42); +} diff --git a/tests/beman/expected/expected_ref_both_no_default_fail.cpp b/tests/beman/expected/expected_ref_both_no_default_fail.cpp new file mode 100644 index 0000000..8faf5f6 --- /dev/null +++ b/tests/beman/expected/expected_ref_both_no_default_fail.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// Negative compile test: expected has no default constructor. +#include + +using namespace beman::expected; + +void test() { + expected e; // no default constructor — must not compile +} diff --git a/tests/beman/expected/expected_ref_both_or_else_wrong_value_type_fail.cpp b/tests/beman/expected/expected_ref_both_or_else_wrong_value_type_fail.cpp new file mode 100644 index 0000000..6ac1584 --- /dev/null +++ b/tests/beman/expected/expected_ref_both_or_else_wrong_value_type_fail.cpp @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: or_else on expected must return expected with same value_type (T&) +// EXPECT: "F must return expected with the same value_type" +#include +namespace { +using E = beman::expected::expected; +using G = beman::expected::expected; // value_type double& != int& +} // namespace +void test() { + double d = 0; + int err = 0; + E e = E(beman::expected::unexpect, err); + // F returns G (value_type == double&), but e has value_type == int& + e.or_else([&](int&) -> G { return d; }); +} diff --git a/tests/beman/expected/expected_ref_both_t_array_fail.cpp b/tests/beman/expected/expected_ref_both_t_array_fail.cpp new file mode 100644 index 0000000..2a2a303 --- /dev/null +++ b/tests/beman/expected/expected_ref_both_t_array_fail.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: T must not be an array type in expected +// EXPECT: "T must not be an array type" +#include +void test() { + using arr = int[3]; + int err = 0; + beman::expected::expected e(beman::expected::unexpect, err); +} diff --git a/tests/beman/expected/expected_ref_both_t_inplace_fail.cpp b/tests/beman/expected/expected_ref_both_t_inplace_fail.cpp new file mode 100644 index 0000000..1de2d0f --- /dev/null +++ b/tests/beman/expected/expected_ref_both_t_inplace_fail.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: T must not be in_place_t in expected +// EXPECT: "T must not be in_place_t" +#include +#include +void test() { + std::in_place_t ip{}; + int err = 0; + beman::expected::expected e(ip); +} diff --git a/tests/beman/expected/expected_ref_both_t_unexpect_fail.cpp b/tests/beman/expected/expected_ref_both_t_unexpect_fail.cpp new file mode 100644 index 0000000..ee424ab --- /dev/null +++ b/tests/beman/expected/expected_ref_both_t_unexpect_fail.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: T must not be unexpect_t in expected +// EXPECT: "T must not be unexpect_t" +#include +void test() { + beman::expected::unexpect_t u{}; + int err = 0; + beman::expected::expected e(u); +} diff --git a/tests/beman/expected/expected_ref_both_t_unexpected_fail.cpp b/tests/beman/expected/expected_ref_both_t_unexpected_fail.cpp new file mode 100644 index 0000000..6c2655a --- /dev/null +++ b/tests/beman/expected/expected_ref_both_t_unexpected_fail.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: T must not be a specialization of unexpected in expected +// EXPECT: "T must not be a specialization of unexpected" +#include +void test() { + beman::expected::unexpected u(1); + int err = 0; + beman::expected::expected&, int&> e(u); +} diff --git a/tests/beman/expected/expected_ref_both_temporary_err_fail.cpp b/tests/beman/expected/expected_ref_both_temporary_err_fail.cpp new file mode 100644 index 0000000..1e7bc57 --- /dev/null +++ b/tests/beman/expected/expected_ref_both_temporary_err_fail.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// Negative compile test: binding a temporary to E& in expected must fail. +#include + +using namespace beman::expected; + +void test() { + expected e(unexpect, 42); // binds temporary int to E& — must not compile +} diff --git a/tests/beman/expected/expected_ref_both_temporary_val_fail.cpp b/tests/beman/expected/expected_ref_both_temporary_val_fail.cpp new file mode 100644 index 0000000..06da155 --- /dev/null +++ b/tests/beman/expected/expected_ref_both_temporary_val_fail.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// Negative compile test: binding a temporary to T& in expected must fail. +#include + +using namespace beman::expected; + +void test() { + expected e(42); // binds temporary int to T& — must not compile +} diff --git a/tests/beman/expected/expected_ref_both_transform_error_ref_result_fail.cpp b/tests/beman/expected/expected_ref_both_transform_error_ref_result_fail.cpp new file mode 100644 index 0000000..d283262 --- /dev/null +++ b/tests/beman/expected/expected_ref_both_transform_error_ref_result_fail.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: transform_error on expected must return object type G, not reference +// EXPECT: "G must be an object type" +#include +void test() { + int err = 0; + beman::expected::expected e(beman::expected::unexpect, err); + // G = int& (reference type, not object type) + e.transform_error([](int& v) -> int& { return v; }); +} diff --git a/tests/beman/expected/expected_ref_constraints.test.cpp b/tests/beman/expected/expected_ref_constraints.test.cpp new file mode 100644 index 0000000..9805c73 --- /dev/null +++ b/tests/beman/expected/expected_ref_constraints.test.cpp @@ -0,0 +1,259 @@ +// tests/beman/expected/expected_ref_constraints.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Beman-only: tests SFINAE behavior of constraints on expected. + +#include +#include + +#include + +#include +#include + +using namespace beman::expected; + +// --------------------------------------------------------------------------- +// Helper types +// --------------------------------------------------------------------------- + +struct MoveOnly { + MoveOnly() = default; + MoveOnly(MoveOnly&&) = default; + MoveOnly& operator=(MoveOnly&&) = default; + MoveOnly(const MoveOnly&) = delete; + MoveOnly& operator=(const MoveOnly&) = delete; +}; + +struct NotSwappable { + NotSwappable() = default; + NotSwappable(NotSwappable&&) = default; + NotSwappable& operator=(NotSwappable&&) = default; + NotSwappable(const NotSwappable&) = default; + NotSwappable& operator=(const NotSwappable&) = default; + friend void swap(NotSwappable&, NotSwappable&) = delete; +}; + +// Concept detectors +template +concept has_and_then = requires(F f) { std::declval().and_then(f); }; + +template +concept has_or_else = requires(F f) { std::declval().or_else(f); }; + +template +concept has_transform = requires(F f) { std::declval().transform(f); }; + +template +concept has_transform_error = requires(F f) { std::declval().transform_error(f); }; + +template +concept has_swap = requires(X a, X b) { a.swap(b); }; + +template +concept has_emplace = requires(X x, U u) { x.emplace(u); }; + +template +concept has_emplace_from = requires { std::declval().emplace(std::declval()); }; + +template +concept has_value_or = requires(X x, U u) { x.value_or(u); }; + +// =========================================================================== +// C2/C3: Copy/move constructors require E to be copy/move constructible +// =========================================================================== + +static_assert(!std::is_copy_constructible_v>, + "expected must not be copy-constructible"); +static_assert(std::is_move_constructible_v>, + "expected must be move-constructible"); + +static_assert(std::is_copy_constructible_v>, "expected must be copy-constructible"); +static_assert(std::is_move_constructible_v>, "expected must be move-constructible"); + +// =========================================================================== +// A1/A2: Copy/move assignment require E to be copy/move constructible+assignable +// =========================================================================== + +static_assert(!std::is_copy_assignable_v>, + "expected must not be copy-assignable"); +static_assert(std::is_move_assignable_v>, "expected must be move-assignable"); + +static_assert(std::is_copy_assignable_v>, "expected must be copy-assignable"); +static_assert(std::is_move_assignable_v>, "expected must be move-assignable"); + +// =========================================================================== +// C5/C6: Value ctor excludes expected self-type and unexpected specializations +// =========================================================================== + +// unexpected must route to the unexpected ctor, not the value ctor. +// Verify by constructing — if the value ctor were selected, it would try to +// bind int& to an unexpected, which would fail differently. +TEST_CASE("ref constraint: unexpected routes to unexpected ctor", "[ref_constraints]") { + expected e(unexpected(42)); + REQUIRE(!e.has_value()); + CHECK(e.error() == 42); +} + +// =========================================================================== +// C7: Value ctor requires is_constructible_v +// =========================================================================== + +// Cannot construct expected from a string — int& is not bindable to string +static_assert(!std::is_constructible_v, std::string&>, + "expected must not be constructible from string&"); + +// Can construct expected from int& +static_assert(std::is_constructible_v, int&>, + "expected must be constructible from int&"); + +// =========================================================================== +// C9/C10: Converting ctor from expected +// =========================================================================== + +// Cannot convert expected to expected (string& not bindable to int&) +static_assert(!std::is_constructible_v, const expected&>, + "expected must not be constructible from expected"); + +// Can convert expected to expected (same types) +static_assert(std::is_constructible_v, const expected&>, + "expected must be constructible from expected"); + +// Cannot convert when E is not constructible from G +struct Unconstructible { + Unconstructible() = default; + Unconstructible(int) = delete; +}; + +static_assert(!std::is_constructible_v, const expected&>, + "expected not constructible from expected — E(const G&) fails"); + +// =========================================================================== +// A3/A4/A5: Value assignment constraints +// =========================================================================== + +// Value assignment from int& works +static_assert(std::is_assignable_v&, int&>, "expected must be assignable from int&"); + +// Value assignment from unrelated type that can't bind to int& is blocked +static_assert(!std::is_assignable_v&, std::string&>, + "expected must not be assignable from string&"); + +// =========================================================================== +// A6: unexpected assignment requires constructible+assignable E +// =========================================================================== + +static_assert(std::is_assignable_v&, unexpected>, + "expected must be assignable from unexpected"); + +// =========================================================================== +// S1: swap requires E swappable and move-constructible +// =========================================================================== + +static_assert(has_swap>, "expected must be swappable"); + +static_assert(!has_swap>, "expected must not be swappable"); + +// =========================================================================== +// E1: emplace requires constructible and no dangling +// =========================================================================== + +static_assert(has_emplace, int&>, "expected must support emplace from int&"); + +// emplace from an rvalue int — should be blocked (can't bind int& to rvalue) +static_assert(!has_emplace_from, int>, + "expected must not support emplace from rvalue int"); + +// =========================================================================== +// V1: value_or requires is_object_v && !is_array_v (LWG4304) +// =========================================================================== + +static_assert(has_value_or, int>, "expected must support value_or"); + +// Function type: int(int) is not an object type +// Note: expected would require T = int(int), which is a function type. +// We can't easily form expected due to other constraints, so we verify +// the positive case works and trust the requires clause. + +// =========================================================================== +// MC1/MC2: and_then/transform constrained by E constructibility +// =========================================================================== + +using RefMoveOnlyErr = expected; +[[maybe_unused]] auto ref_dummy_and_then = [](int&) { return expected(); }; + +static_assert(!has_and_then, + "and_then lvalue must be constrained out when E is move-only"); +static_assert(has_and_then, + "and_then rvalue must be available when E is move-constructible"); +static_assert(!has_and_then, + "and_then const lvalue must be constrained out when E is move-only"); + +[[maybe_unused]] auto ref_dummy_transform = [](int&) { return 0; }; + +static_assert(!has_transform, + "transform lvalue must be constrained out when E is move-only"); +static_assert(has_transform, + "transform rvalue must be available when E is move-constructible"); +static_assert(!has_transform, + "transform const lvalue must be constrained out when E is move-only"); + +// =========================================================================== +// MC3/MC4: or_else/transform_error have no constraints — always available +// =========================================================================== + +[[maybe_unused]] auto ref_dummy_or_else = [](MoveOnly&&) -> expected { + static int x = 0; + return expected(x); +}; + +static_assert(has_or_else, + "or_else must be available — no constraints on value constructibility for T&"); + +[[maybe_unused]] auto ref_dummy_transform_error = [](MoveOnly&&) { return 0; }; + +static_assert(has_transform_error, + "transform_error must be available — no constraints for T& specialization"); + +// =========================================================================== +// Positive: all operations available for normal types +// =========================================================================== + +using NormalRef = expected; + +[[maybe_unused]] auto ref_normal_and_then = [](int&) { return expected(42); }; +[[maybe_unused]] auto ref_normal_or_else = [](int) -> expected { + static int x = 0; + return expected(x); +}; +[[maybe_unused]] auto ref_normal_transform = [](int&) { return 42; }; +[[maybe_unused]] auto ref_normal_transform_err = [](int) { return 42; }; + +static_assert(has_and_then); +static_assert(has_and_then); +static_assert(has_and_then); +static_assert(has_and_then); + +static_assert(has_or_else); +static_assert(has_transform); +static_assert(has_transform_error); + +// =========================================================================== +// Runtime tests for constraint-gated paths +// =========================================================================== + +TEST_CASE("ref constraint: rvalue and_then works with move-only error", "[ref_constraints]") { + int x = 42; + expected e(x); + auto r = std::move(e).and_then([](int& v) { return expected(v + 1); }); + REQUIRE(r.has_value()); + CHECK(*r == 43); +} + +TEST_CASE("ref constraint: rvalue transform works with move-only error", "[ref_constraints]") { + int x = 42; + expected e(x); + auto r = std::move(e).transform([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 84); +} diff --git a/tests/beman/expected/expected_ref_e_array_fail.cpp b/tests/beman/expected/expected_ref_e_array_fail.cpp new file mode 100644 index 0000000..503d817 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_array_fail.cpp @@ -0,0 +1,9 @@ +// tests/beman/expected/expected_ref_e_array_fail.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected where E is an array type is ill-formed +// EXPECT: "E must not be an array type" +#include +void test() { + int x = 0; + beman::expected::expected e(x); // must not compile +} diff --git a/tests/beman/expected/expected_ref_e_cv_fail.cpp b/tests/beman/expected/expected_ref_e_cv_fail.cpp new file mode 100644 index 0000000..ce0b69a --- /dev/null +++ b/tests/beman/expected/expected_ref_e_cv_fail.cpp @@ -0,0 +1,9 @@ +// tests/beman/expected/expected_ref_e_cv_fail.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected where E is cv-qualified is ill-formed +// EXPECT: "E must not be cv-qualified" +#include +void test() { + int x = 0; + beman::expected::expected e(x); // must not compile +} diff --git a/tests/beman/expected/expected_ref_e_ref_fail.cpp b/tests/beman/expected/expected_ref_e_ref_fail.cpp new file mode 100644 index 0000000..db7f2ac --- /dev/null +++ b/tests/beman/expected/expected_ref_e_ref_fail.cpp @@ -0,0 +1,9 @@ +// tests/beman/expected/expected_ref_e_ref_fail.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected — rvalue reference as E in expected must fail +// EXPECT: "E must not be an rvalue reference" +#include +void test() { + int x = 0; + beman::expected::expected e(x); // E is rvalue ref — must not compile +} diff --git a/tests/beman/expected/expected_ref_e_void_fail.cpp b/tests/beman/expected/expected_ref_e_void_fail.cpp new file mode 100644 index 0000000..00efaca --- /dev/null +++ b/tests/beman/expected/expected_ref_e_void_fail.cpp @@ -0,0 +1,9 @@ +// tests/beman/expected/expected_ref_e_void_fail.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected where E is void is ill-formed +// EXPECT: "E must not be void" +#include +void test() { + int x = 0; + beman::expected::expected e(x); // must not compile +} diff --git a/tests/beman/expected/expected_ref_inplace_value_fail.cpp b/tests/beman/expected/expected_ref_inplace_value_fail.cpp new file mode 100644 index 0000000..0c4d8d0 --- /dev/null +++ b/tests/beman/expected/expected_ref_inplace_value_fail.cpp @@ -0,0 +1,9 @@ +// tests/beman/expected/expected_ref_inplace_value_fail.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected has no in_place_t value constructor +// EXPECT: "no matching function" +#include +void test() { + int x = 5; + beman::expected::expected e(std::in_place, x); // must not compile +} diff --git a/tests/beman/expected/expected_ref_no_default_fail.cpp b/tests/beman/expected/expected_ref_no_default_fail.cpp new file mode 100644 index 0000000..78c0154 --- /dev/null +++ b/tests/beman/expected/expected_ref_no_default_fail.cpp @@ -0,0 +1,8 @@ +// tests/beman/expected/expected_ref_no_default_fail.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected has no default constructor +// EXPECT: "delete" +#include +void test() { + beman::expected::expected e; // must not compile +} diff --git a/tests/beman/expected/expected_ref_or_else_wrong_value_type_fail.cpp b/tests/beman/expected/expected_ref_or_else_wrong_value_type_fail.cpp new file mode 100644 index 0000000..c257c7c --- /dev/null +++ b/tests/beman/expected/expected_ref_or_else_wrong_value_type_fail.cpp @@ -0,0 +1,15 @@ +// tests/beman/expected/expected_ref_or_else_wrong_value_type_fail.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: or_else on expected must return expected with same value_type +// EXPECT: "F must return expected with the same value_type" +#include +namespace { +using E = beman::expected::expected; +using G = beman::expected::expected; +} // namespace +void test() { + double d = 0; + E e = beman::expected::unexpected(0); + // F returns G (value_type == double&), but e has value_type == int& + e.or_else([&](int) -> G { return d; }); +} diff --git a/tests/beman/expected/expected_ref_temporary_fail.cpp b/tests/beman/expected/expected_ref_temporary_fail.cpp new file mode 100644 index 0000000..907053d --- /dev/null +++ b/tests/beman/expected/expected_ref_temporary_fail.cpp @@ -0,0 +1,8 @@ +// tests/beman/expected/expected_ref_temporary_fail.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: constructing expected from a temporary must not compile +// EXPECT: "no matching function|delete" +#include +void test() { + beman::expected::expected e(42); // 42 is a temporary +} diff --git a/tests/beman/expected/expected_ref_transform_error_ref_result_fail.cpp b/tests/beman/expected/expected_ref_transform_error_ref_result_fail.cpp new file mode 100644 index 0000000..290a389 --- /dev/null +++ b/tests/beman/expected/expected_ref_transform_error_ref_result_fail.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: transform_error on expected must return object type G, not reference +// EXPECT: "G must be an object type" +#include +void test() { + int x = 0; + beman::expected::expected e(x); + // G = int& (reference type, not object type) + e.transform_error([](int& v) -> int& { return v; }); +} diff --git a/tests/beman/expected/expected_review_corrections.test.cpp b/tests/beman/expected/expected_review_corrections.test.cpp new file mode 100644 index 0000000..1852f75 --- /dev/null +++ b/tests/beman/expected/expected_review_corrections.test.cpp @@ -0,0 +1,262 @@ +// tests/beman/expected/expected_review_corrections.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Regression tests for the PR #57 review corrections (findings F3-F7). +// F1/F2/F8 were addressed separately; the construction rule below is the +// Option-B resolution of F6 (accommodate the safe unexpected use case). + +#include + +#include + +#include +#include + +using namespace beman::expected; + +// ============================================================================= +// F6 (Option B) — construction from unexpected for reference E. +// +// Allowed when G is itself a reference (the source holds a reference to an +// external object, so binding E& cannot dangle). Deleted when G is a value +// type (referent lives inside the wrapper) or would drop const. +// ============================================================================= + +// Allowed: reference G, across all three specializations. +static_assert(std::is_constructible_v, unexpected>); +static_assert(std::is_constructible_v, unexpected>); +static_assert(std::is_constructible_v, unexpected>); + +// Allowed: binding a more-const reference (const int& <- int&). +static_assert(std::is_constructible_v, unexpected>); +static_assert(std::is_constructible_v, unexpected>); + +// Deleted: value G would dangle once a temporary unexpected is destroyed. +static_assert(!std::is_constructible_v, unexpected>); +static_assert(!std::is_constructible_v, unexpected>); +static_assert(!std::is_constructible_v, unexpected>); + +// Deleted: dropping const (int& <- const int&) is not constructible. +static_assert(!std::is_constructible_v, unexpected>); +static_assert(!std::is_constructible_v, unexpected>); + +// Value-E construction is unaffected. +static_assert(std::is_constructible_v, unexpected>); + +TEST_CASE("reference-error construction from unexpected binds an external object", "[ref][unexpected]") { + int err = 41; + + // A temporary unexpected is fine: it only holds a pointer to err. + expected a{unexpected(err)}; + expected b{unexpected(err)}; + expected c{unexpected(err)}; + + REQUIRE(&a.error() == &err); + REQUIRE(&b.error() == &err); + REQUIRE(&c.error() == &err); + + err = 99; // mutating the external object is visible through the stored reference + REQUIRE(a.error() == 99); + REQUIRE(b.error() == 99); + REQUIRE(c.error() == 99); +} + +// Rebinding assignment from unexpected — allowed for reference E only when G is a reference +// (rebinds the error pointer to an external object; never dangles). Value G and const-drop are +// statically rejected, mirroring construction. +static_assert(std::is_assignable_v&, unexpected>); +static_assert(std::is_assignable_v&, unexpected>); +static_assert(std::is_assignable_v&, unexpected>); +static_assert(std::is_assignable_v&, unexpected>); // more-const OK +static_assert(!std::is_assignable_v&, unexpected>); // value G: deleted +static_assert(!std::is_assignable_v&, unexpected>); // const drop: no overload + +TEST_CASE("rebinding assignment from unexpected repoints the error reference", "[ref][unexpected][assign]") { + int g1 = 1, g2 = 2; + + SECTION("error -> error rebind (expected)") { + expected e{unexpect, g1}; + e = unexpected(g2); + REQUIRE(&e.error() == &g2); // rebound to g2 + REQUIRE(g1 == 1); // previously-referenced object untouched + g2 = 42; + REQUIRE(e.error() == 42); // sees the new referent + } + SECTION("value -> error transition (expected)") { + expected e{7}; + e = unexpected(g2); + REQUIRE_FALSE(e.has_value()); + REQUIRE(&e.error() == &g2); + } + SECTION("both references (expected)") { + int target = 5; + expected e{target}; + e = unexpected(g2); + REQUIRE_FALSE(e.has_value()); + REQUIRE(&e.error() == &g2); + } + SECTION("void value (expected)") { + expected e{}; // value state + e = unexpected(g2); + REQUIRE_FALSE(e.has_value()); + REQUIRE(&e.error() == &g2); + } +} + +// ============================================================================= +// F4 / F5 — value constructor and emplace are noexcept only when the reference +// bind cannot throw, so a throwing conversion propagates instead of terminating. +// ============================================================================= + +namespace { +struct ThrowingRef { + operator int&() const { throw 42; } +}; +} // namespace + +static_assert(std::is_nothrow_constructible_v, int&>); +static_assert(!std::is_nothrow_constructible_v, ThrowingRef>); + +TEST_CASE("value constructor propagates a throwing reference conversion", "[ref][noexcept]") { + int caught = 0; + try { + expected e{ThrowingRef{}}; + (void)e; + } catch (int v) { + caught = v; + } + REQUIRE(caught == 42); +} + +TEST_CASE("emplace propagates a throwing reference conversion", "[ref][emplace][noexcept]") { + int target = 5; + expected e{target}; + int caught = 0; + try { + e.emplace(ThrowingRef{}); + } catch (int v) { + caught = v; + } + REQUIRE(caught == 42); + REQUIRE(e.has_value()); // still holds the original value; emplace did not corrupt state + REQUIRE(&e.value() == &target); +} + +// ============================================================================= +// F3 — assignment that rebinds through a throwing conversion must leave the +// error state intact and destroy the error exactly once (no double-destroy). +// ============================================================================= + +namespace { +int g_dtor_count = 0; +struct CountingErr { + int value; + explicit CountingErr(int v) : value(v) {} + CountingErr(const CountingErr&) = default; + ~CountingErr() { ++g_dtor_count; } +}; +} // namespace + +TEST_CASE("throwing rebind assignment does not double-destroy the error", "[ref][assign][exception-safety]") { + CountingErr seed{7}; + int caught = 0; + { + expected e{unexpect, seed}; + g_dtor_count = 0; // count only destructions of e's stored error from here on + try { + e = ThrowingRef{}; // was-error branch: rebind throws + } catch (int v) { + caught = v; + } + REQUIRE(caught == 42); + REQUIRE_FALSE(e.has_value()); // error state preserved + REQUIRE(e.error().value == 7); + REQUIRE(g_dtor_count == 0); // not destroyed yet + } + REQUIRE(g_dtor_count == 1); // destroyed exactly once, at end of scope +} + +// ============================================================================= +// F7 — error_or is SFINAE-friendly (constraints in a requires-clause, not a +// runtime static_assert). +// ============================================================================= + +namespace { +template +concept has_error_or = requires(Ex e, Arg a) { e.error_or(a); }; +struct NotConvertible {}; +} // namespace + +static_assert(has_error_or&, const char*>); +static_assert(!has_error_or&, NotConvertible>); + +// ============================================================================= +// Shallow conversion — converting from an rvalue reference-holding expected to a +// value-holding expected must NOT move-steal the external referent (the value +// category comes from the shallow accessor, via *std::move(rhs), not a naked +// std::move of the referent). Mirrors optional's behavior. +// ============================================================================= + +TEST_CASE("converting from an rvalue reference expected does not steal the referent", "[ref][convert][shallow]") { + SECTION("value side: expected <- expected&&") { + std::string s = "bar"; + expected r{s}; + expected o{std::move(r)}; + REQUIRE(s == "bar"); // referent untouched (copied, not moved) + REQUIRE(o.value() == "bar"); + } + SECTION("value side, assignment") { + std::string s = "bar"; + expected r{s}; + expected o{std::in_place, "x"}; + o = std::move(r); + REQUIRE(s == "bar"); + REQUIRE(o.value() == "bar"); + } + SECTION("error side: expected <- expected&&") { + std::string s = "bar"; + expected r{unexpect, s}; + expected o{std::move(r)}; + REQUIRE(s == "bar"); + REQUIRE(o.error() == "bar"); + } +} + +TEST_CASE("constructing/assigning a value error from unexpected does not steal the referent", + "[unexpected][convert][shallow]") { + SECTION("construction") { + std::string s = "bar"; + expected o{unexpected(s)}; + REQUIRE(s == "bar"); // external referent copied, not moved + REQUIRE(o.error() == "bar"); + } + SECTION("assignment") { + std::string s = "bar"; + expected o{unexpect, "x"}; + o = unexpected(s); + REQUIRE(s == "bar"); + REQUIRE(o.error() == "bar"); + } +} + +TEST_CASE("constructing a value error from an rvalue unexpected still moves", "[unexpected][move]") { + unexpected u{"baz"}; + expected o{std::move(u)}; + REQUIRE(u.error().empty()); // owned error genuinely moved out + REQUIRE(o.error() == "baz"); +} + +TEST_CASE("converting from an rvalue value expected still moves", "[convert][move]") { + SECTION("value side") { + expected a{std::in_place, "bar"}; + expected b{std::move(a)}; + REQUIRE(a.value().empty()); // genuinely moved-from + REQUIRE(b.value() == "bar"); + } + SECTION("error side") { + expected a{unexpect, "baz"}; + expected b{std::move(a)}; + REQUIRE(a.error().empty()); + REQUIRE(b.error() == "baz"); + } +} diff --git a/tests/beman/expected/expected_t_array_fail.cpp b/tests/beman/expected/expected_t_array_fail.cpp index 3a2276e..7c58285 100644 --- a/tests/beman/expected/expected_t_array_fail.cpp +++ b/tests/beman/expected/expected_t_array_fail.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE COMPILE TEST: expected is ill-formed [expected.object.general] para 2 -// T must not be an array type. +// NEGATIVE: expected is ill-formed [expected.object.general] para 2 +// EXPECT: "T must not be an array type" #include beman::expected::expected e; // must not compile diff --git a/tests/beman/expected/expected_t_ref_fail.cpp b/tests/beman/expected/expected_t_ref_fail.cpp index 7b53b3c..d221aff 100644 --- a/tests/beman/expected/expected_t_ref_fail.cpp +++ b/tests/beman/expected/expected_t_ref_fail.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE COMPILE TEST: expected is ill-formed [expected.object.general] para 2 -// T must not be a reference type. +// NEGATIVE: expected now matches the reference specialization which has no default ctor +// EXPECT: "delete" #include beman::expected::expected e; // must not compile diff --git a/tests/beman/expected/expected_unexpected_value_type_fail.cpp b/tests/beman/expected/expected_unexpected_value_type_fail.cpp index 5bc3834..05a1571 100644 --- a/tests/beman/expected/expected_unexpected_value_type_fail.cpp +++ b/tests/beman/expected/expected_unexpected_value_type_fail.cpp @@ -1,7 +1,8 @@ // tests/beman/expected/expected_unexpected_value_type_fail.cpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// Negative compile test: T must not be a specialization of unexpected. +// NEGATIVE: T must not be a specialization of unexpected +// EXPECT: "T must not be a specialization of unexpected" #include diff --git a/tests/beman/expected/expected_void.test.cpp b/tests/beman/expected/expected_void.test.cpp index 741e1f8..9f035e2 100644 --- a/tests/beman/expected/expected_void.test.cpp +++ b/tests/beman/expected/expected_void.test.cpp @@ -6,6 +6,8 @@ #include +#include "testing/types.hpp" + #include #include #include @@ -353,3 +355,80 @@ TEST_CASE("expected: cross-type equality", "[expected_void]") { expected d(unexpect, 5L); CHECK(c == d); } + +// ============================================================================= +// Additional coverage tests: rvalue/const-rvalue paths and traced types +// ============================================================================= +using namespace beman::expected::testing; + +// --- value() throw paths for void --- +TEST_CASE("value() rvalue throws", "[expected_void]") { + expected e(unexpect, 42); + CHECK_THROWS_AS(std::move(e).value(), bad_expected_access); +} + +TEST_CASE("value() const rvalue throws", "[expected_void]") { + const expected e(unexpect, 42); + CHECK_THROWS_AS(std::move(e).value(), bad_expected_access); +} + +// --- void unexpect_t with init-list --- +TEST_CASE("unexpect_t constructor with initializer_list", "[expected_void]") { + expected> e(unexpect, {1, 2, 3}); + REQUIRE(!e.has_value()); + CHECK(e.error() == std::vector{1, 2, 3}); +} + +// --- void move-assignment error-to-value and value-to-error --- +TEST_CASE("move-assign error to value state", "[expected_void]") { + expected a; + expected b(unexpect, "e"); + a = std::move(b); + REQUIRE(!a.has_value()); + CHECK(a.error() == "e"); +} + +TEST_CASE("move-assign value to error state", "[expected_void]") { + expected a(unexpect, "e"); + expected b; + a = std::move(b); + REQUIRE(a.has_value()); +} + +// --- void cross-type equality --- +TEST_CASE("cross-type equality with expected", "[expected_void]") { + expected a(unexpect, 1); + expected b(unexpect, 1L); + CHECK(a == b); + expected c; + CHECK_FALSE(a == c); +} + +// --------------------------------------------------------------------------- +// expected: void specialization with non-trivial E +// --------------------------------------------------------------------------- + +TEST_CASE("void: unexpect_t constructor", "[expected_void][traced]") { + expected e(unexpect, 42); + REQUIRE(!e.has_value()); + CHECK(e.error().val == 42); +} + +TEST_CASE("void: move construct error state", "[expected_void][traced]") { + expected a(unexpect, 10); + expected b(std::move(a)); + REQUIRE(!b.has_value()); + CHECK(b.error().val == 10); +} + +TEST_CASE("void: move assign error-to-error", "[expected_void][traced]") { + expected a(unexpect, 1); + expected b(unexpect, 2); + b = std::move(a); + CHECK(b.error().val == 1); +} + +TEST_CASE("void: value() rvalue throws", "[expected_void][traced]") { + expected e(unexpect, 42); + CHECK_THROWS_AS(std::move(e).value(), bad_expected_access); +} diff --git a/tests/beman/expected/expected_void_array_fail.cpp b/tests/beman/expected/expected_void_array_fail.cpp index ba792d4..d0a34f3 100644 --- a/tests/beman/expected/expected_void_array_fail.cpp +++ b/tests/beman/expected/expected_void_array_fail.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// Negative compile test: expected where E is an array is ill-formed. +// NEGATIVE: expected where E is an array is ill-formed +// EXPECT: "E must not be an array type" #include beman::expected::expected x; // should fail diff --git a/tests/beman/expected/expected_void_monadic.test.cpp b/tests/beman/expected/expected_void_monadic.test.cpp index d1ac10b..173ab7c 100644 --- a/tests/beman/expected/expected_void_monadic.test.cpp +++ b/tests/beman/expected/expected_void_monadic.test.cpp @@ -6,6 +6,8 @@ #include +#include "testing/types.hpp" + #include using namespace beman::expected; @@ -125,6 +127,16 @@ TEST_CASE("transform void: F returns void - expected()", "[expected_voi CHECK(count == 1); } +TEST_CASE("transform void: has error, F returns void - propagates", "[expected_void_monadic]") { + expected e(unexpect, 5); + int count = 0; + auto r = e.transform([&]() { ++count; }); + static_assert(std::is_same_v>); + CHECK(count == 0); + REQUIRE(!r.has_value()); + CHECK(r.error() == 5); +} + TEST_CASE("transform void: rvalue overload", "[expected_void_monadic]") { expected e; auto r = std::move(e).transform([]() -> std::string { return "done"; }); @@ -193,3 +205,242 @@ TEST_CASE("void and_then: all ref qualifications compile", "[expected_void_monad (void)(std::move(e).and_then(f)); (void)(std::move(std::as_const(e)).and_then(f)); } + +// ============================================================================= +// Additional coverage tests: rvalue/const-rvalue monadic overloads for void +// ============================================================================= +using namespace beman::expected::testing; + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- +TEST_CASE("and_then rvalue on error short-circuits", "[expected_void_monadic]") { + expected e(unexpect, "err"); + bool called = false; + auto r = std::move(e).and_then([&]() -> expected { + called = true; + return {}; + }); + CHECK(!called); + REQUIRE(!r.has_value()); +} + +TEST_CASE("and_then const rvalue on error short-circuits", "[expected_void_monadic]") { + const expected e(unexpect, "err"); + bool called = false; + auto r = std::move(e).and_then([&]() -> expected { + called = true; + return {}; + }); + CHECK(!called); + REQUIRE(!r.has_value()); +} + +// --- Monadic: and_then rvalue/const-rvalue on VALUE state (actual call) --- +TEST_CASE("and_then rvalue on value calls F", "[expected_void_monadic]") { + expected e; + bool called = false; + auto r = std::move(e).and_then([&]() -> expected { + called = true; + return 42; + }); + CHECK(called); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("and_then const rvalue on value calls F", "[expected_void_monadic]") { + const expected e; + auto r = std::move(e).and_then([]() -> expected { return 99; }); + REQUIRE(r.has_value()); + CHECK(*r == 99); +} + +// --- Monadic: or_else rvalue/const-rvalue on VALUE state --- +TEST_CASE("or_else rvalue on value short-circuits", "[expected_void_monadic]") { + expected e; + bool called = false; + auto r = std::move(e).or_else([&](int) -> expected { + called = true; + return {}; + }); + CHECK(!called); + REQUIRE(r.has_value()); +} + +TEST_CASE("or_else const rvalue on value short-circuits", "[expected_void_monadic]") { + const expected e; + auto r = std::move(e).or_else([](int) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("or_else rvalue on error calls F", "[expected_void_monadic]") { + expected e(unexpect, 5); + auto r = std::move(e).or_else([](int v) -> expected { return unexpected(v + 1); }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 6); +} + +TEST_CASE("or_else const rvalue on error calls F", "[expected_void_monadic]") { + const expected e(unexpect, 5); + auto r = std::move(e).or_else([](int v) -> expected { return unexpected(v + 1); }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 6); +} + +// --- Monadic: transform rvalue/const-rvalue on ERROR state --- +TEST_CASE("transform rvalue on error short-circuits", "[expected_void_monadic]") { + expected e(unexpect, "err"); + auto r = std::move(e).transform([]() { return 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == "err"); +} + +TEST_CASE("transform const rvalue on error short-circuits", "[expected_void_monadic]") { + const expected e(unexpect, "err"); + auto r = std::move(e).transform([]() { return 1; }); + REQUIRE(!r.has_value()); +} + +// --- Monadic: transform rvalue/const-rvalue on VALUE state --- +TEST_CASE("transform rvalue on value calls F", "[expected_void_monadic]") { + expected e; + auto r = std::move(e).transform([]() { return 42; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("transform const rvalue on value calls F", "[expected_void_monadic]") { + const expected e; + auto r = std::move(e).transform([]() { return 7; }); + REQUIRE(r.has_value()); + CHECK(*r == 7); +} + +// --- Monadic: transform_error rvalue/const-rvalue --- +TEST_CASE("transform_error rvalue on value short-circuits", "[expected_void_monadic]") { + expected e; + auto r = std::move(e).transform_error([](int v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("transform_error rvalue on error calls F", "[expected_void_monadic]") { + expected e(unexpect, 5); + auto r = std::move(e).transform_error([](int v) { return v * 2; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 10); +} + +TEST_CASE("transform_error const rvalue on error calls F", "[expected_void_monadic]") { + const expected e(unexpect, 3); + auto r = std::move(e).transform_error([](int v) { return v + 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == 4); +} + +// --------------------------------------------------------------------------- +// void monadic tests +// --------------------------------------------------------------------------- + +TEST_CASE("void: and_then lvalue error path", "[expected_void_monadic][traced]") { + expected e(unexpect, 5); + auto r = e.and_then([]() -> expected { return {}; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("void: and_then const lvalue error path", "[expected_void_monadic][traced]") { + const expected e(unexpect, 5); + auto r = e.and_then([]() -> expected { return {}; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("void: or_else lvalue value path", "[expected_void_monadic][traced]") { + expected e; + auto r = e.or_else([](traced&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: or_else rvalue value path", "[expected_void_monadic][traced]") { + expected e; + auto r = std::move(e).or_else([](traced&&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: or_else const rvalue value path", "[expected_void_monadic][traced]") { + const expected e; + auto r = std::move(e).or_else([](const traced&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: transform lvalue value path", "[expected_void_monadic][traced]") { + expected e; + auto r = e.transform([]() { return traced(1); }); + REQUIRE(r.has_value()); + CHECK(r->val == 1); +} + +TEST_CASE("void: transform lvalue error path", "[expected_void_monadic][traced]") { + expected e(unexpect, 5); + auto r = e.transform([]() { return traced(0); }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("void: transform rvalue value path", "[expected_void_monadic][traced]") { + expected e; + auto r = std::move(e).transform([]() { return traced(2); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: transform rvalue error path", "[expected_void_monadic][traced]") { + expected e(unexpect, 5); + auto r = std::move(e).transform([]() { return traced(0); }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("void: transform const rvalue value path", "[expected_void_monadic][traced]") { + const expected e; + auto r = std::move(e).transform([]() { return traced(3); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: transform const rvalue error path", "[expected_void_monadic][traced]") { + const expected e(unexpect, 5); + auto r = std::move(e).transform([]() { return traced(0); }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("void: transform to void lvalue", "[expected_void_monadic][traced]") { + expected e; + auto r = e.transform([]() {}); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: transform_error lvalue value path", "[expected_void_monadic][traced]") { + expected e; + auto r = e.transform_error([](traced&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: transform_error lvalue error path", "[expected_void_monadic][traced]") { + expected e(unexpect, 5); + auto r = e.transform_error([](traced& v) { return traced(v.val + 1); }); + REQUIRE(!r.has_value()); + CHECK(r.error().val == 6); +} + +TEST_CASE("void: transform_error rvalue value path", "[expected_void_monadic][traced]") { + expected e; + auto r = std::move(e).transform_error([](traced&&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: transform_error const rvalue value path", "[expected_void_monadic][traced]") { + const expected e; + auto r = std::move(e).transform_error([](const traced&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +// --- void unexpect_t with init-list --- +TEST_CASE("void: unexpect_t with initializer_list", "[expected_void_monadic][init_list]") { + expected e(unexpect, {1, 2, 3}); + REQUIRE(!e.has_value()); + CHECK(e.error().sum == 6); +} diff --git a/tests/beman/expected/or_else_wrong_value_type_fail.cpp b/tests/beman/expected/or_else_wrong_value_type_fail.cpp index 6ae3cb4..65dd3cc 100644 --- a/tests/beman/expected/or_else_wrong_value_type_fail.cpp +++ b/tests/beman/expected/or_else_wrong_value_type_fail.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // NEGATIVE: or_else Mandates G::value_type == T +// EXPECT: "F must return expected with the same value_type" #include void test() { beman::expected::expected e(beman::expected::unexpect, 1); diff --git a/tests/beman/expected/todo.test.cpp b/tests/beman/expected/todo.test.cpp deleted file mode 100644 index 2f8a066..0000000 --- a/tests/beman/expected/todo.test.cpp +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#include -#include -#include - -TEST_CASE("todo", "[TodoTest]") { - const bool todo = true; - CHECK(todo); -} diff --git a/tests/beman/expected/transform_error_ref_result_fail.cpp b/tests/beman/expected/transform_error_ref_result_fail.cpp index 16355d5..5e5d878 100644 --- a/tests/beman/expected/transform_error_ref_result_fail.cpp +++ b/tests/beman/expected/transform_error_ref_result_fail.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // NEGATIVE: transform_error Mandates G is valid for unexpected (not a reference) +// EXPECT: "G must be an object type" #include void test() { beman::expected::expected e(beman::expected::unexpect, 1); diff --git a/tests/beman/expected/unexpected_array_fail.cpp b/tests/beman/expected/unexpected_array_fail.cpp index a353e04..a843ef0 100644 --- a/tests/beman/expected/unexpected_array_fail.cpp +++ b/tests/beman/expected/unexpected_array_fail.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE COMPILE TEST: unexpected is ill-formed [expected.un.general] para 2 -// E must not be an array type. +// NEGATIVE: unexpected is ill-formed [expected.un.general] para 2 +// EXPECT: "E must not be an array type" #include beman::expected::unexpected u(std::in_place); // must not compile diff --git a/tests/beman/expected/unexpected_cvref_fail.cpp b/tests/beman/expected/unexpected_cvref_fail.cpp index 1c2dfca..3dc9b92 100644 --- a/tests/beman/expected/unexpected_cvref_fail.cpp +++ b/tests/beman/expected/unexpected_cvref_fail.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE COMPILE TEST: unexpected is ill-formed [expected.un.general] para 2 -// E must not be cv-qualified. +// NEGATIVE: unexpected is ill-formed [expected.un.general] para 2 +// EXPECT: "E must not be cv-qualified" #include beman::expected::unexpected u(42); // must not compile diff --git a/tests/beman/expected/unexpected_self_fail.cpp b/tests/beman/expected/unexpected_self_fail.cpp index df99558..7c712d1 100644 --- a/tests/beman/expected/unexpected_self_fail.cpp +++ b/tests/beman/expected/unexpected_self_fail.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE COMPILE TEST: unexpected> is ill-formed [expected.un.general] para 2 -// E must not be a specialization of unexpected. +// NEGATIVE: unexpected> is ill-formed [expected.un.general] para 2 +// EXPECT: "E must not be a specialization of unexpected" #include using namespace beman::expected; diff --git a/tests/beman/expected/void_and_then_wrong_error_type_fail.cpp b/tests/beman/expected/void_and_then_wrong_error_type_fail.cpp index cfa1490..5308dbc 100644 --- a/tests/beman/expected/void_and_then_wrong_error_type_fail.cpp +++ b/tests/beman/expected/void_and_then_wrong_error_type_fail.cpp @@ -1,5 +1,6 @@ -// NEGATIVE: and_then on void expected mandates U::error_type == E // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: and_then on void expected mandates U::error_type == E +// EXPECT: "F must return expected with the same error_type" #include void test() { beman::expected::expected e; diff --git a/tests/beman/expected/void_or_else_wrong_value_type_fail.cpp b/tests/beman/expected/void_or_else_wrong_value_type_fail.cpp index 98d6999..13c2d1b 100644 --- a/tests/beman/expected/void_or_else_wrong_value_type_fail.cpp +++ b/tests/beman/expected/void_or_else_wrong_value_type_fail.cpp @@ -1,5 +1,6 @@ -// NEGATIVE: or_else on void expected mandates G::value_type == void // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: or_else on void expected mandates G::value_type == void +// EXPECT: "F must return expected with the same value_type" #include void test() { beman::expected::expected e(beman::expected::unexpect, 1); From 33756376450220b197683b2b32f5333f878a1234 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 20:53:30 -0400 Subject: [PATCH 05/15] docs: add design/review guides, D4280R0 paper, and plan updates --- docs/human-design-review-guide.md | 265 ++++ docs/llm-code-review-guide.md | 199 +++ docs/plan/fix-review-corrections.md | 63 + docs/plan/handoff-next.md | 92 +- docs/plan/handoff.md | 114 +- docs/plan/index.md | 31 +- docs/plan/step10-expected-void-ref-e.md | 2 +- docs/plan/step7-expected-ref-t.md | 2 +- docs/plan/step8-expected-ref-e.md | 2 +- docs/plan/step9-expected-ref-both.md | 2 +- docs/plan/tests-overview.md | 128 +- papers/.gitignore | 25 + papers/D4280R0.tex | 386 +++++ papers/Makefile | 103 ++ papers/latexmkrc | 7 + papers/mybiblio.bib | 27 + papers/wg21-latex/.gitignore | 547 +++++++ papers/wg21-latex/Makefile | 126 ++ papers/wg21-latex/README.md | 17 + papers/wg21-latex/abstract.bst | 1345 +++++++++++++++++ papers/wg21-latex/back.tex | 171 +++ papers/wg21-latex/common.tex | 103 ++ papers/wg21-latex/config.tex | 16 + papers/wg21-latex/example-paper.tex | 147 ++ papers/wg21-latex/implementation.hpp | 382 +++++ papers/wg21-latex/intro.tex | 1093 ++++++++++++++ papers/wg21-latex/latexmkrc | 2 + papers/wg21-latex/ldiff.sh | 7 + papers/wg21-latex/mybiblio.bib | 69 + papers/wg21-latex/new-wording.tex | 1648 +++++++++++++++++++++ papers/wg21-latex/requirements.txt | 1 + papers/wg21-latex/std.tex | 89 ++ papers/wg21-latex/stdtex/layout.tex | 83 ++ papers/wg21-latex/stdtex/macros.tex | 803 ++++++++++ papers/wg21-latex/stdtex/paper_macros.tex | 45 + papers/wg21-latex/stdtex/styles.tex | 346 +++++ papers/wg21-latex/stdtex/tables.tex | 523 +++++++ papers/wg21-latex/xrefdelta.tex | 515 +++++++ 38 files changed, 9413 insertions(+), 113 deletions(-) create mode 100644 docs/human-design-review-guide.md create mode 100644 docs/llm-code-review-guide.md create mode 100644 docs/plan/fix-review-corrections.md create mode 100644 papers/.gitignore create mode 100644 papers/D4280R0.tex create mode 100644 papers/Makefile create mode 100644 papers/latexmkrc create mode 100644 papers/mybiblio.bib create mode 100644 papers/wg21-latex/.gitignore create mode 100644 papers/wg21-latex/Makefile create mode 100644 papers/wg21-latex/README.md create mode 100644 papers/wg21-latex/abstract.bst create mode 100644 papers/wg21-latex/back.tex create mode 100644 papers/wg21-latex/common.tex create mode 100644 papers/wg21-latex/config.tex create mode 100644 papers/wg21-latex/example-paper.tex create mode 100644 papers/wg21-latex/implementation.hpp create mode 100644 papers/wg21-latex/intro.tex create mode 100644 papers/wg21-latex/latexmkrc create mode 100755 papers/wg21-latex/ldiff.sh create mode 100644 papers/wg21-latex/mybiblio.bib create mode 100644 papers/wg21-latex/new-wording.tex create mode 100644 papers/wg21-latex/requirements.txt create mode 100644 papers/wg21-latex/std.tex create mode 100644 papers/wg21-latex/stdtex/layout.tex create mode 100644 papers/wg21-latex/stdtex/macros.tex create mode 100644 papers/wg21-latex/stdtex/paper_macros.tex create mode 100644 papers/wg21-latex/stdtex/styles.tex create mode 100644 papers/wg21-latex/stdtex/tables.tex create mode 100644 papers/wg21-latex/xrefdelta.tex diff --git a/docs/human-design-review-guide.md b/docs/human-design-review-guide.md new file mode 100644 index 0000000..619d3fa --- /dev/null +++ b/docs/human-design-review-guide.md @@ -0,0 +1,265 @@ +# Human Design Review Guide: `beman::expected` + +**Target Audience:** Human reviewers, WG21 participants, and anyone deciding whether this code should become standard library wording. + +## What This Is + +A complete implementation of `std::expected` (C++26) extended with six template specializations that allow `T` and/or `E` to be reference types, proposed for C++29. The reference semantics follow P2988 (`optional`): rebind on assignment, shallow const, dangling prevention via deleted constructors. + +**File count:** 3 headers, ~4400 lines of implementation, 15 positive test files (469 tests), 54 negative compile tests. + +**This guide is not a checklist.** It's a map of the decisions that shaped this code. Some are obviously correct. Some are defensible but debatable. Some might be wrong. Your job is to decide which is which. + +--- + +## Decision 1: Six Separate Specializations, Zero Abstraction + +### What was done + +The implementation provides six complete, independent partial specializations: + +| Specialization | Lines | Storage | +|----------------|-------|---------| +| `expected` | ~1100 | `union { T val_; E unex_; }` + bool | +| `expected` | ~800 | `union { E unex_; }` + bool | +| `expected` | ~700 | `T*` + `union { E unex_; }` + bool | +| `expected` | ~700 | `union { T val_; }` + `E*` + bool | +| `expected` | ~550 | `T*` + `E*` + bool | +| `expected` | ~450 | `E*` + bool | + +Each specialization is a self-contained class with its own constructors, assignment operators, observers, swap, equality, and monadic operations. There is no shared base class, no CRTP, no mixin, no macro that generates them. + +### Why this matters + +The total is ~4300 lines of template code in a single header. A factored implementation using a common base or policy template could plausibly cut this by 40-60%. The monadic operations alone account for 4 operations x 4 ref-qualified overloads x 6 specializations = 96 method definitions that share structural similarity. + +### The argument for the current approach + +- Standard library implementations (libstdc++, libc++, MSVC STL) use flat specializations for `optional` and `expected`. Inheritance-based factoring produces incomprehensible error messages. +- Each specialization can be read and audited independently against whatever future standard wording emerges. +- When constraints differ subtly between specializations (and they do — `T&` doesn't need `is_nothrow_move_constructible` checks, `E&` doesn't support `unexpected` construction), a shared base must either disable features via SFINAE or push complexity into the base, which hides it rather than removing it. + +### The argument against + +- **Maintenance burden.** A bug in `and_then` must be fixed in 6 places. A new monadic operation (hypothetical `or_transform`) must be added 24 times (4 overloads x 6 specializations). +- **Consistency risk.** Are all 6 specializations actually consistent? Small divergences creep in when hand-copying constraint clauses. The only way to verify is exhaustive side-by-side comparison. +- **Review fatigue.** A reviewer looking at 4300 lines of structurally similar code will inevitably skim. The 95th `requires` clause gets less scrutiny than the 5th. + +### What to decide + +Is the flat structure appropriate for a reference implementation intended to validate proposed wording? Or should this be refactored before being held up as "this is how it should look in a standard library"? + +--- + +## Decision 2: Rebind Semantics (Not Assign-Through) + +### What was done + +Assignment to `expected` rebinds the internal pointer to point at a different object. It does not assign through the reference to modify the original referent. + +```cpp +int a = 1, b = 2; +expected e(a); +e = expected(b); // e now points to b; a is unchanged +``` + +### Why this is the only defensible choice + +P2988 settled this for `optional` after years of debate. JeanHeyd Meneide's research demonstrated that assign-through semantics are a persistent source of bugs in practice, and every production optional-with-references implementation that tried assign-through was eventually abandoned or revised. The same logic applies to `expected`. + +### What to discuss anyway + +- **User expectation.** C++ programmers coming from `std::reference_wrapper` expect assign-through (that's what `reference_wrapper::operator=` does). This will surprise some users. +- **`emplace` also rebinds.** `e.emplace(new_ref)` rebinds, which is the only sensible behavior, but it differs from `expected::emplace(args...)` which constructs in-place. The name `emplace` is arguably misleading for reference types since nothing is being "emplaced" — a pointer is being reassigned. +- **Swap rebinds both sides.** Two `expected` objects swap their pointers, not the values they point to. This is consistent with rebind semantics but will surprise users who think of references as aliases. + +--- + +## Decision 3: Shallow Const + +### What was done + +`const expected` still returns a non-const `T&` from `operator*()`. The constness of the `expected` container does not propagate to the referent. + +### Why + +This matches `T* const` (const pointer to non-const T), `std::reference_wrapper`, and P2988 `optional`. A `const expected` is an expected whose *binding* cannot change, not an expected whose *referent* is immutable. + +### What to discuss + +- **`const_cast` concerns.** If someone passes a `const expected&` to a function, that function can modify the referent. This is correct behavior, but it may violate the expectations of API designers who use `const&` parameters to express "I won't modify your data." +- **`expected` exists as the alternative.** Users who want deep const must spell it. This is well-established in C++ but not universally known. + +--- + +## Decision 4: `unexpected` Construction for `E&` Specializations — Allowed Only When `G` Is a Reference + +### What was done + +For `expected`, `expected`, and `expected`, construction from `unexpected` is allowed **only when `G` is itself a reference** (i.e. from `unexpected`), and `= delete`d when `G` is a value type. Assignment from `unexpected` is still not offered for reference `E` (construction-only, for now). + +```cpp +int err = 42; +expected a(unexpect, err); // OK: unexpect_t ctor takes an lvalue +expected b(unexpected(err)); // OK: source holds a reference to external err +expected c(unexpected(42)); // ERROR: deleted — value G would dangle +``` + +### Why + +`unexpected` stores a *pointer* to an external object, so binding `E&` to its `error()` cannot dangle regardless of the source's value category (even a temporary `unexpected` refers to something external). `unexpected` for a *value* `G` owns its `G`, so binding `E&` to it dangles the moment the (typically temporary) `unexpected` is destroyed — that overload stays deleted. + +This supersedes the original blanket deletion ("Option A"): the earlier design deleted *all* `unexpected` construction for reference `E`, which also rejected the safe `unexpected` case. The current rule ("Option B") accommodates that safe case, consistent with the WG21 view that reasonably-safe uses should work. The dangling guard is `reference_constructs_from_temporary_v` (Decision 5), reliable now that GCC 13 — which provides the builtin — is the baseline. + +### What to discuss + +- **Assignment symmetry.** Rebinding *assignment* from `unexpected` (`e = unexpected(g)`) is now supported alongside construction, with the same reference-`G`-only rule (value `G` and const-drop are rejected). Its body repoints `unex_` via `construct_at` rather than `unex_.error() = …` (which would mutate the old pointee), and copies the pointer with no move — see the move-steal hazard in Decision 11. +- **The value-`G` cliff.** `expected(unexpected(g))` works; `expected(unexpected(42))` does not. The distinction (reference vs. value `G`) is principled but still a likely FAQ; the `= delete` diagnostic should name it. + +--- + +## Decision 5: Dangling Prevention via `reference_constructs_from_temporary_v` + +### What was done + +A `detail::reference_constructs_from_temporary_v` concept (lines 88-100 of `expected.hpp`) guards every constructor where `T&` or `E&` could bind to a temporary. When the concept detects a dangling risk, a deleted overload with a diagnostic message catches the call. + +### The subtlety + +`reference_constructs_from_temporary_v` is **false** — a non-const lvalue reference simply cannot bind to a temporary, so the concept says "no, this doesn't construct from a temporary." The construction fails anyway via SFINAE ("no matching function"), but the deleted overload with its nice message doesn't fire. + +The deleted overload with its diagnostic fires for cases like `const int&` binding to a prvalue `int`, where `reference_constructs_from_temporary_v` is **true**. + +### What to discuss + +- **Two failure modes, two diagnostics.** Binding `int&` to `42` gives "no matching function." Binding `const int&` to `42` gives the deleted message. Both are correct rejections, but the user experience differs. The negative tests handle this with regex alternation (`"binding a temporary|no matching function"`). Is this acceptable or should the overload set be restructured so the diagnostic is always the deleted message? +- **Compiler support.** `reference_constructs_from_temporary_v` is a C++23 feature. The implementation provides a fallback (line 88-100). Is the fallback correct for all edge cases? The trait relies on compiler built-ins (`__reference_constructs_from_temporary`) when available. + +--- + +## Decision 6: `= delete("message")` as a Diagnostic Strategy + +### What was done + +Every deleted operation carries a C++26 `= delete("message")` string explaining **what** is wrong and **what to do instead**: + +- `"expected: no default constructor; T& cannot be null"` +- `"expected: no constructor from unexpected; use (unexpect, lvalue_ref)"` +- `"expected: no value_or for void specialization"` + +### What to discuss + +- **C++26 feature.** `= delete("message")` is not available in C++23 or earlier. The README claims C++17+ support. This feature silently degrades — compilers that don't support it treat it as plain `= delete` — but the claim of C++17+ compatibility deserves scrutiny against the actual minimum language version needed for the rest of the code (`requires` clauses, concepts, `constexpr` union, etc.). +- **Message quality.** Are the messages actionable? Do they guide the user to the correct alternative? Review each message for clarity and accuracy. +- **Is this the right mechanism?** An alternative is `static_assert(false, "message")` inside a constrained-away-but-still-instantiable template. That would work in C++23 but is arguably worse. The `= delete("message")` approach is cleaner and should be preferred if C++26 is truly the floor. + +--- + +## Decision 7: `value_or` Deleted for `expected` + +### What was done + +`expected::value_or()` is `= delete("no value_or for void specialization")` rather than simply not being declared. + +### What to discuss + +- **Why not just omit it?** The primary `expected` doesn't declare `value_or` at all (there's no value to return). The `void, E&` specialization explicitly deletes it. This inconsistency should be intentional — is it? Does the delete give a better error message than "no member named 'value_or'"? +- **Precedent.** Does `expected` (the standard one) have `value_or`? If not, mimicking that by omission would be more consistent. + +--- + +## Decision 8: Monadic Operations on Reference Specializations + +### What was done + +All four monadic operations (`and_then`, `or_else`, `transform`, `transform_error`) are provided for all six specializations, with four ref-qualified overloads each (`&`, `const&`, `&&`, `const&&`). + +For reference specializations, the callable always receives `T&` (dereferenced pointer), regardless of the expected object's own value category. This means: + +```cpp +expected e(x); +auto f = [](int& r) { return expected(r); }; +std::move(e).and_then(f); // f still gets int&, not int&& +``` + +### What to discuss + +- **Is four ref-qualified overloads correct for reference types?** Since the stored value is a pointer, the expected object's value category doesn't affect how the referent is passed. All four overloads do the same thing for the value path. Should there be only one, or is the four-overload pattern maintained for API consistency? +- **Error forwarding for `E&` specializations.** When `and_then`'s callable is not invoked (error case), the error must be forwarded to the result. For `E&`, this always passes `E&` (dereferenced error pointer). The expected object's rvalue-ness doesn't move the error. Is this clearly documented? +- **Return type inference.** When `transform(f)` is called on `expected`, and `f` returns `U&`, does the result become `expected` or `expected`? This depends on how `std::invoke_result_t` deduces the return type. Verify the tests cover this case. + +--- + +## Decision 9: Test Architecture + +### What exists + +- 15 positive test files with 469 Catch2 `TEST_CASE` entries +- 54 negative compile tests (`_fail.cpp`) each with a `PASS_REGULAR_EXPRESSION` regex +- Separate constraint test files for primary and `T&` specializations +- Hardened precondition tests under `BEMAN_EXPECTED_HARDENED` + +### Gaps to discuss + +- **No constraint test files for `expected`, `expected`, or `expected`.** The primary template has `expected_constraints.test.cpp` and `expected` has `expected_ref_constraints.test.cpp`. The other three reference specializations have no dedicated constraint test file. Their SFINAE behavior is untested under `static_assert(!is_constructible_v<...>)` patterns. +- **No monadic constraint tests for reference specializations.** `expected_monadic_constraints.test.cpp` covers only the primary and void specializations with move-only error types. The same constraints apply to reference specializations but are untested. +- **No triviality tests for reference specializations.** `expected_trivial.test.cpp` covers only primary and void. Reference specializations (especially `T&, E&` and `void, E&`, which are pointer-only) should be trivially copyable, movable, and destructible unconditionally. This is not verified. +- **Monadic tests are embedded in general test files** rather than having dedicated `_monadic.test.cpp` files for reference specializations. This makes it harder to verify completeness. + +### What to decide + +Is the test suite sufficient for standardization review? The positive coverage is good — every operation has basic tests. But the systematic constraint/SFINAE testing that exists for the primary template is absent for three of the four reference specializations. This gap should be closed before submitting to WG21. + +--- + +## Decision 10: Single Header vs Multi-Header + +### What exists + +Everything is in `expected.hpp`. The `unexpected.hpp` and `bad_expected_access.hpp` are separate (matching the standard's `[expected.syn]` structure), but all six specializations live in one file. + +### What to discuss + +- **4400 lines in one file.** This is at the boundary of manageable. A reviewer can `grep` and navigate, but side-by-side comparison of specializations requires tooling. +- **The standard doesn't mandate file structure.** But for a reference implementation intended to demonstrate proposed wording, would splitting `expected_ref.hpp` (or similar) improve reviewability? +- **Compile times.** Every translation unit that includes `expected.hpp` parses all six specializations. For users who only need `expected`, this is wasted work. The module build path (`expected.cppm`) mitigates this but is opt-in and not the default. + +--- + +## Decision 11: Shallow Conversions Must Not Steal — the Move-Then-Deref Idiom + +### What was done + +Converting from a reference-holding `expected` (or `unexpected`) to a value-holding one **copies** the referent; it never moves out of it, even from an rvalue source. Given `std::string s = "bar"; expected r{s};`, both `expected{std::move(r)}` and `o = std::move(r)` leave `s == "bar"`. The internal idiom is **move the wrapper, then access** — `*std::move(rhs)`, `std::move(rhs).error()`, `std::move(e).error()` — never **access, then move** — `std::move(*rhs)`, `std::move(rhs.error())`, `std::move(e.error())`. + +### Why this matters + +`std::move(*rhs)` (deref-then-move) forces an rvalue onto whatever the accessor returns. For a reference specialization the accessor is *shallow*: it yields a reference to an **external** object the wrapper does not own, so the `std::move` steals from a caller's object that was only lent by reference. This is the exact `boost::optional` pitfall (`optional o; optional r{s}; o = std::move(r);` moved `s` out from under the caller). + +`*std::move(rhs)` (move-then-deref) delegates the value-category decision to the ref-qualified accessor, which is the one place that knows ownership: deep (`T&&`) for owned values, shallow (`T&`) for references. So genuine value moves still move; reference conversions copy. This is the same idiom `optional` uses (`construct(*std::move(rhs))`). + +### What to discuss / watch in review + +- **`std::move(*x)` and `std::move(x.value()/error())` are the anti-pattern**, and they are *not* locally distinguishable by eye from the safe form — both compile, and the difference surfaces only as a moved-from referent at runtime. Treat every `std::move()` as suspect; the safe forms wrap the whole object: `*std::move(x)`, `std::move(x).accessor()`. +- **The invariant it rests on:** rvalue accessors must stay shallow for reference specializations and deep for value ones. If someone makes `operator*() &&` / `error() &&` deep on a reference spec, the idiom silently breaks. Behavioral regression tests in `expected_review_corrections.test.cpp` cover both directions (referent survives; owned value still moves). + +--- + +## How to Approach Your Review + +When giving feedback, focus on the rationale behind these macro-level decisions rather than just line-by-line nitpicks. Consider the following when leaving comments: + +- **If something feels off:** What alternative design would you prefer, and what trade-offs does it bring? +- **If you agree with a constraint:** Did we actually implement it faithfully, or are there hidden loopholes? +- **Ergonomics:** Will standard C++ developers be able to understand the error messages and API bounds? + +Please don't review this code as if it were a typical pull request. Review it as proposed standard library wording rendered into C++. The core question is not just "does it compile and pass tests?" — it does. The question is: *"Should this be how `expected` works for every C++ programmer for the next 30 years?"* Your human perspective and architectural judgment are exactly what we need to answer that. + +--- + +## References & Citations + +- **P2988 (`std::optional`)**: [https://wg21.link/p2988](https://wg21.link/p2988) — The baseline proposal establishing rebind semantics and shallow const for reference wrappers. +- **P3168 (Give `std::optional` Range Support)**: [https://wg21.link/p3168](https://wg21.link/p3168) — Related context by JeanHeyd Meneide on the widespread failures of assign-through semantics in practice. +- **P2573 (`= delete("should have a reason");`)**: [https://wg21.link/p2573](https://wg21.link/p2573) — The C++26 feature enabling customized diagnostic messages on deleted functions. +- **P2255 (A type trait to detect reference binding to temporary)**: [https://wg21.link/p2255](https://wg21.link/p2255) — The C++23 feature (`reference_constructs_from_temporary_v`) used for dangling reference prevention. diff --git a/docs/llm-code-review-guide.md b/docs/llm-code-review-guide.md new file mode 100644 index 0000000..527f394 --- /dev/null +++ b/docs/llm-code-review-guide.md @@ -0,0 +1,199 @@ +# LLM Code Review Guide: `beman::expected` + +**Target Audience:** High-context Large Language Models acting as rigorous C++ code reviewers. +**Scope:** The entire `include/beman/expected/` header set — one 4400-line header (`expected.hpp`) plus two small companions (`unexpected.hpp`, `bad_expected_access.hpp`). + +## Mission + +You are reviewing a proposed **C++29** reference implementation of `std::expected` that extends the C++26 standard with reference specializations. The implementation targets standardization via the Beman Project. Your job is not to praise working code — it is to find defects, inconsistencies, specification violations, and latent footguns. "Looks good" is not an acceptable conclusion unless you can demonstrate why every clause was checked. + +--- + +## Specification Sources (Authoritative, In-Repo) + +| Source | Location | Use | +|--------|----------|-----| +| C++26 standard wording | `docs/standard/[expected].html` | Normative text for primary and void specializations | +| Standard (org-mode) | `docs/standard/expected.org` | Same content, machine-parseable | +| Standard (plain text) | `docs/standard/expected.txt` | Same content, grep-friendly | +| Conformance audit | `docs/conformance-audit.md` | Every clause checked with PASS/EXT/FIXED status | +| P2988 (optional\) | External: open-std.org/P2988 | Rebind semantics design source for reference specializations | +| beman/optional reference impl | `~/src/steve-downey/optional/main/` | Pattern source for T& storage and dangling prevention | + +### What You Have No Normative Source For + +**This is critical.** The reference specializations (`expected`, `expected`, `expected`, `expected`) have **no published standard wording**. They are an extension proposed for C++29. The only design authority is: + +1. Analogy to P2988's `optional` semantics (rebind, shallow const, dangling prevention) +2. The implementation's own `docs/plan/step7-*.md` through `step10-*.md` design docs +3. Consistency with the primary template's established patterns + +This means: for the primary template and void specialization, you can check clause-by-clause against `[expected.object.*]` and `[expected.void.*]`. For reference specializations, you must instead check **internal consistency** and **design-principle adherence**. Don't fabricate standard references that don't exist. + +--- + +## Review Protocol + +### Phase 1: Structural Inventory + +Before reading line-by-line, establish the shape: + +1. **Count specializations.** There should be exactly 6: ``, ``, ``, ``, ``, ``. Each should have complete API surface (constructors, assignment, observers, swap, equality, monadic ops). +2. **Identify the storage model** for each specialization. Primary uses union of T and E. Void uses union of E only. Reference specializations store pointers. Verify no specialization accidentally uses a union member for a reference type. +3. **Map the helper utilities.** `reinit_expected` (lines 65-84) handles destroy-and-reconstruct for value-type transitions. `reference_constructs_from_temporary_v` (lines 88-100) is the dangling-prevention concept. Verify these are used correctly and only where applicable. + +### Phase 2: Clause-by-Clause for Primary and Void + +For `expected` and `expected`, audit against the standard: + +#### Constraints vs Mandates + +The standard distinguishes: +- **Constraints** (`requires` clause on declaration): SFINAE-friendly, checked during overload resolution +- **Mandates** (`static_assert` inside body): hard error on instantiation, not SFINAE-friendly + +**Check for these specific misclassifications:** +- Is any Mandate implemented as a `requires` clause? (This would silently hide the error instead of diagnosing it) +- Is any Constraint implemented as a `static_assert`? (This would cause hard errors when SFINAE should apply) +- The conformance audit at `docs/conformance-audit.md` claims all clauses are correct — verify independently by sampling 3-5 constructors against `[expected.object.cons]` + +#### Explicit Conditions + +Every converting constructor uses `explicit(bool-expr)`. The standard specifies exactly which `is_convertible_v` checks determine explicitness. Verify: +- The logical structure matches (conjunction of negated `is_convertible_v` checks) +- The value categories in the `is_convertible_v` arguments match what the standard says (`const G&` vs `G` vs `G&&`) + +#### noexcept Specifications + +The implementation adds conditional `noexcept` on some constructors where the standard is silent. `docs/conformance-audit.md` marks these as "EXT" (conforming extension). Verify: +- These are genuinely conforming (strengthening, not weakening) +- They don't accidentally constrain the overload set via SFINAE on `noexcept` + +### Phase 3: Reference Specialization Consistency + +Since there's no standard wording, check: + +#### 1. API Surface Parity + +Each reference specialization should offer the **same user-facing operations** as the primary, adapted for reference semantics. Missing operations are bugs unless explicitly justified. Check: + +| Operation | `` | `` | `` | `` | `` | +|-----------|---------|-----------|-----------|------------|--------------| +| Default ctor | yes | **deleted** | yes | **deleted** | yes | +| Copy/move ctor | yes | yes | yes | yes | yes | +| Converting ctor | yes | yes (from U&) | yes | yes (from U&,G&) | yes | +| unexpected\ ctor | yes | yes | from `` only | from `` only | from `` only | +| unexpect_t ctor | yes | yes | yes | yes | yes | +| in_place_t ctor | yes | **deleted** | yes | **deleted** | N/A (void) | +| Value assignment | yes | rebind | yes | yes | N/A | +| unexpected assignment | yes | yes | **deleted** | **deleted** | N/A | +| emplace | yes | rebind | yes | rebind | emplace() (void) | +| operator* | T& | T& | T& | T& | void | +| operator-> | T* | T* | T* | T* | N/A | +| value() | throws | throws | throws | throws | throws | +| value_or | yes | yes | yes | yes | **deleted** | +| error_or | yes | yes | yes | yes | yes | +| Monadic (4 ops) | yes | yes | yes | yes | yes | +| swap | yes | yes | yes | yes | yes | +| equality | yes | yes | yes | yes | yes | + +Verify each cell. Pay special attention to the **deleted** entries — each should have a `= delete("message")` with a clear diagnostic and a corresponding negative compile test. + +For the `from only` cells: construction from `unexpected` is permitted for reference `E` **only when `G` is itself a reference** (`unexpected`, which holds a pointer to an external object). Construction from a value-typed `unexpected` stays `= delete`d (it would dangle), and rebinding *assignment* from `unexpected` is not offered for reference `E`. Verify: `is_constructible_v, unexpected>` is true, `is_constructible_v, unexpected>` is false, and `unexpected` → `int&` is rejected (const drop). + +#### 2. Dangling Prevention + +For every constructor or assignment that accepts a forwarding reference where the destination is `T&` or `E&`: +- Verify `!detail::reference_constructs_from_temporary_v` appears in the `requires` clause +- Verify a deleted overload exists for the case where temporaries would bind +- Verify the deleted overload's message is tested by a `_fail.cpp` negative test + +**Known subtlety:** For `expected` constructing from `42`, the constraint rejects via "no matching function" (SFINAE), not via the deleted overload. The deleted overload fires only when `reference_constructs_from_temporary_v` is true (e.g., `const int&` from `int`). Both paths prevent the dangerous construction, but the diagnostic differs. This is correct behavior. + +#### 3. Storage Layout + +- `expected`: pointer `T*` plus union `{ E unex_; }` plus `bool has_val_` +- `expected`: value `T` in union plus `E*` pointer plus `bool has_val_` +- `expected`: two pointers `T*`, `E*` plus `bool has_val_` +- `expected`: pointer `E*` plus `bool has_val_` + +Verify: +- No union contains a reference or pointer where the active member tracking could be wrong +- Assignment to reference specializations reassigns pointers (rebind), never destroys/reconstructs +- `reinit_expected` is NOT called for pointer-only transitions + +#### 4. Monadic Value Categories + +For each monadic operation across all specializations, verify: +- The callable receives the correct value category of the stored value +- For `T&` specializations: callable always receives `T&` (dereferenced pointer), regardless of the expected object's own value category +- For `E&` specializations: error forwarding passes `E&` (dereferenced pointer), never `E&&` +- Return type wrapping preserves the reference nature where appropriate + +### Phase 4: Cross-Cutting Checks + +#### constexpr Correctness + +Every function in this implementation should be `constexpr`. Verify: +- Union member activation via placement-new or `std::construct_at` is constexpr-valid +- Pointer dereference in reference specializations is constexpr-valid +- No `reinterpret_cast` or `void*` arithmetic that would fail constant evaluation + +#### Trivial Special Member Functions + +The primary and void specializations must be trivially copyable/movable/destructible when T and E are. Reference specializations (pointer-based) should be trivially everything unconditionally. Verify: +- `= default` paths exist for trivial cases +- The conditional dispatch between trivial and non-trivial paths is correct +- Reference specializations don't accidentally have non-trivial destructors + +#### Hardened Preconditions + +Under `#if defined(BEMAN_EXPECTED_HARDENED)`: +- `operator*` and `operator->` trap when `!has_value()` +- `error()` traps when `has_value()` +- Verify these guards exist in ALL specializations, not just the primary + +#### Value Category in Converting Operations (deref-then-move anti-pattern) + +Every converting constructor/assignment from `expected` or `unexpected` must build the target value/error by **moving the wrapper, then accessing** — `*std::move(rhs)`, `std::move(rhs).error()`, `std::move(e).error()` — never by **accessing, then moving** — `std::move(*rhs)`, `std::move(rhs.error())`, `std::move(e.error())`. + +The deref-then-move form is a defect for reference specializations: the accessor is shallow and returns a reference to an **external** object, and the `std::move` then steals from it — the `boost::optional` move-steal bug. Both forms compile and are **indistinguishable by eye**, so verify by behavior, not inspection: +- `std::string s="bar"; expected r{s}; expected o{std::move(r)};` must leave `s == "bar"` (copied, not stolen). Same for the error axis (`expected` → `expected`) and for `unexpected` sources, on both construction and assignment. +- A genuine value source (`expected`/`unexpected` rvalue) must **still move** (source left empty). + +Mechanical audit: grep the header for `std::move(*` and `std::move(.value()/error())` — every hit is a candidate defect; the safe forms wrap the whole object (`*std::move(x)`, `std::move(x).accessor()`). This rests on the invariant that rvalue accessors are shallow for reference specializations and deep for value ones (Phase 2 accessor checks). + +--- + +## Known Issues to Confirm or Refute + +These are areas where the implementation may have latent bugs. Investigate each: + +1. **`expected` value constructor ambiguity.** The implementation could not add a `= delete("message")` overload for the case where `expected` is constructed from another `expected` specialization (because the deleted overload conflicts with the converting constructor). The regex for `expected_bool_value_ctor_from_expected_fail` is still the generic "no matching function". Is this a real problem, or is the constraint sufficient? + +2. **Move-only error types in reference specializations.** The monadic constraint tests (`expected_monadic_constraints.test.cpp`) only cover the primary and void specializations. For `expected` with a move-only `E`: are `and_then` and `transform` correctly constrained on their lvalue overloads (which need `is_constructible_v`)? This is untested. + +3. **`expected` equality operators.** When comparing `expected == expected`, does the comparison dereference both sides correctly? Is there a test for comparing two reference-expected objects that point to the same underlying object? + +4. **`value_or` for reference specializations.** For `expected`, what does `value_or(default_ref)` return? A `T&`? A `T`? The return type semantics differ fundamentally from the primary template. Verify the return type is documented and tested. + +5. **Swap for `expected`.** Swapping two pointer-only expected objects should be trivial (swap pointers and bool). Verify no unnecessary destroy/reconstruct logic is invoked. + +--- + +## Output Format + +Structure your review as: + +``` +## [Specialization or Section Name] + +### Finding N: [severity: defect|concern|nit|question] +**Location:** file:line +**Standard reference:** [clause] (or "no normative source — checking internal consistency") +**Issue:** [description] +**Evidence:** [code quote or reasoning] +**Suggested fix:** [if applicable] +``` + +Do not pad with praise. Every section must contain at least one finding or an explicit "verified correct, N clauses checked" with clause references. diff --git a/docs/plan/fix-review-corrections.md b/docs/plan/fix-review-corrections.md new file mode 100644 index 0000000..3eba5d9 --- /dev/null +++ b/docs/plan/fix-review-corrections.md @@ -0,0 +1,63 @@ +# Fix: PR #57 Review Corrections + +**Branch:** `fix-review-corrections` +**Depends on:** `expected-over-references` (all 10 steps complete) +**Read first:** `docs/plan/handoff.md` + +--- + +## Goal + +Fix four issues identified in the PR #57 code review: + +1. `expected` unsafely accepts `unexpected` (const_cast UB) +2. `expected::value() &&` over-constrains E (requires copy+move, only needs move) +3. Tautological `requires` clause on `expected` partial specialization +4. Member layout inconsistency in `expected` (unex_ptr_ before has_val_) + +## Changes + +### expected.hpp + +**Issue 1:** Replace `unexpected` constructors (lines 3908-3922) with +unconditionally deleted overloads matching `expected` and +`expected`. Add deleted `operator=(unexpected&)` overloads +after `operator=(expected&&)`. + +**Issue 2 (investigated, no change):** The fallback +`reference_constructs_from_temporary_v` concept is correct for how it is +used. All constructor templates use forwarding references (U&&); lvalues +deduce U=T& (reference type), skipping the problematic disjunct 1. +Rvalues deduce U=T (non-reference), correctly triggering deletion. +GCC 11/12 hit the fallback and all CI checks pass. + +**Issue 3:** `expected::value() &&` static_assert changed from +`is_copy_constructible_v && is_move_constructible_v` to just +`is_move_constructible_v` (throw uses std::move). `expected::value() &&` changed to just `is_copy_constructible_v` (throw +copies, no std::move). + +**Issue 4:** Removed `requires std::is_lvalue_reference_v` from the +`expected` partial specialization declaration. Always true by +construction. + +**Issue 5:** Swapped `expected` member declaration order to +`bool has_val_; E* unex_ptr_;` matching all other specializations. Fixed +initializer-list order in default and in_place_t constructors. + +### New test files + +- `expected_void_ref_e_construct_from_unexpected_fail.cpp` +- `expected_void_ref_e_assign_unexpected_fail.cpp` + +### CMakeLists.txt + +Two new `add_fail_test()` entries with regex patterns matching the +existing `expected_ref_e_construct_from_unexpected_fail` pattern. + +## Verification + +```bash +make TOOLCHAIN=gcc-16 test +make lint +``` diff --git a/docs/plan/handoff-next.md b/docs/plan/handoff-next.md index bf3fae5..1999294 100644 --- a/docs/plan/handoff-next.md +++ b/docs/plan/handoff-next.md @@ -1,61 +1,77 @@ -# Handoff: After Fix 5 (All Conformance Fixes Complete) +# Handoff: After Step 7 (expected Complete) ## What Was Done -Fix 5 is complete. Branch `fix5-preconditions-and-minor` merged (--no-ff) into -`expected-over-references`. All conformance fixes (F1–F5) are now merged. +Step 7 is complete. Branch `step7-expected-ref-t` implements +`expected` — the reference-value partial specialization (P2988). +313 tests pass, lint clean. -### Changes in Fix 5 +### Changes in Step 7 **`include/beman/expected/expected.hpp`:** -- Added `BEMAN_EXPECTED_HARDENED` precondition guards (`__builtin_trap()`) to: - - `operator->()` (both const and non-const) — checks `has_val_` - - `operator*()` (all 4 overloads) — checks `has_val_` - - `error()` (all 4 overloads, primary template) — checks `!has_val_` - - `operator*()` (void specialization) — checks `has_val_` - - `error()` (all 4 overloads, void specialization) — checks `!has_val_` -- Void `or_else` (4 overloads): changed `is_void_v` to - `is_same_v` — correct for `const void` etc. -- Void `transform_error` (4 overloads): changed `expected` to - `expected` — matches standard wording - -**`include/beman/expected/unexpected.hpp`:** -- Added `requires std::is_swappable_v` to friend swap - -**Tests added:** -- `tests/beman/expected/expected_hardened.test.cpp` — compiled with - `-DBEMAN_EXPECTED_HARDENED`, verifies happy paths and swap constraint -- New CMake target `beman.expected.tests.hardened` +- Added `detail::reference_constructs_from_temporary_v` (portable fallback + using `__cpp_lib_reference_from_temporary` when available, otherwise + approximation via `is_convertible_v`) +- Added `expected` partial specialization (~670 lines) after the void + specialization, with: + - Storage: `union { T* val_; E unex_; }` + `bool has_val_` + - No default constructor (`= delete`) + - Copy/move constructors (trivial + non-trivial paths) + - Value constructor with dangling-prevention delete overload + - Converting constructors from `expected` (copy and move) + - Error constructors from `unexpected` (copy and move) + - `unexpect_t` in-place error constructors + - Rebind assignment (`operator=`) — rebinds pointer, never assigns through + - Assignment from `unexpected` (rebinds error) + - Observers: `operator*()` → `T&`, `operator->()` → `T*`, `value()` → `T&` + - Shallow const: `const expected` still gives `T*`/`T&` (not const) + - `value_or()`, `error_or()`, `error()` + - `swap()`, equality operators + - Monadic ops: `and_then`, `or_else`, `transform`, `transform_error` + (2 ref-qualified overloads each — `&` and `&&`) + - Mandate static_asserts: E must not be reference, void, array, or cv-qual + +**New test files:** +- `tests/beman/expected/expected_ref.test.cpp` — 477 lines of runtime tests +- `tests/beman/expected/expected_ref_constraints.test.cpp` — 259 lines of + static_assert / type-trait checks +- Negative compile tests: + - `expected_ref_temporary_fail.cpp` — binding temporary to T& is deleted + - `expected_ref_no_default_fail.cpp` — no default constructor + - `expected_ref_inplace_value_fail.cpp` — no in_place_t value constructor + - `expected_ref_e_ref_fail.cpp` — E must not be reference + - `expected_ref_e_void_fail.cpp` — E must not be void + - `expected_ref_e_array_fail.cpp` — E must not be array + - `expected_ref_e_cv_fail.cpp` — E must not be cv-qualified + +**`tests/beman/expected/CMakeLists.txt`:** +- Added `beman.expected.tests.expected_ref` target +- Added `beman.expected.tests.expected_ref_constraints` target +- Added all 7 negative-compile fail targets ### Test count -253 tests total, all passing. +313 tests total, all passing. ## Build Commands ```bash -make TOOLCHAIN=gcc-16 test # 253 tests, all passing -make lint # all linters pass (beman-tidy crash is pre-existing) +make TOOLCHAIN=gcc-16 test # 313 tests, all passing +make lint # all linters pass ``` -## Conformance Fix Checklist +## Step 7 Checklist -- [x] Fix 1: Constructor/assignment/equality constraints -- [x] Fix 2: Trivial special member functions -- [x] Fix 3: Monadic operation constraints -- [x] Fix 4: Mandates static_asserts -- [x] Fix 5: Hardened preconditions and minor fixes ← just done +- [x] Step 7: `expected` — pointer storage, rebind assignment, + observers returning T&, value_or, monadic ops, dangling prevention -## All Conformance Fixes Complete +## What Comes Next -The conformance fixes phase is finished. Per `docs/conformance-fixes/index.md`, -the next actions are: +**Step 8: `expected` error-reference specialization.** -1. Update `docs/conformance-audit.md` to mark resolved items -2. Update this handoff for the post-fix state -3. Proceed to Step 7: `docs/plan/step7-expected-ref-t.md` +Read `docs/plan/step8-expected-ref-e.md` for the full specification. -## State of the Implementation +### Key differences from Step 7 The `expected-over-references` branch now has a fully conformant `expected` and `expected` (modulo the extensions noted in the audit as conforming). diff --git a/docs/plan/handoff.md b/docs/plan/handoff.md index ee4e285..3687cb3 100644 --- a/docs/plan/handoff.md +++ b/docs/plan/handoff.md @@ -1,33 +1,61 @@ -# Handoff: Starting State +# Handoff: Current State ## Repository `beman.expected` — a Beman C++ Standards track reference implementation for `std::expected` with reference support (P2988 / targeting C++29). -## Current State +## Working Branch -The repository has a complete skeleton: all standard interface declarations -exist as comments in the header files, but no actual C++ class definitions -have been written. The headers compile (they're empty namespaces), and the -tests are breathing tests only (`EXPECT_EQ(true, true)`). +All step branches are created from and merged back into **`expected-over-references`** +(not `main`). The `main` branch tracks upstream; `expected-over-references` is +the integration branch for this work. -### Files +## Current State -- `include/beman/expected/expected.hpp` — commented-out specification for - `expected` and `expected`, empty `beman::expected` namespace -- `include/beman/expected/unexpected.hpp` — commented-out specification for - `unexpected`, empty namespace -- `include/beman/expected/bad_expected_access.hpp` — commented-out specification - for `bad_expected_access` and `bad_expected_access`, empty namespace -- `tests/beman/expected/expected.test.cpp` — breathing test only -- `tests/beman/expected/unexpected.test.cpp` — breathing test only -- `tests/beman/expected/bad_expected_access.test.cpp` — breathing test +All 10 steps are complete. Steps 1–9 are merged into `expected-over-references`. +Step 10 is on branch `step10-expected-void-ref-e`, ready to merge. +The implementation includes the full conformant `expected` primary template, +`expected`, monadic operations for both, `expected` (P2988 +reference-value specialization), `expected` (P2988 reference-error +specialization), `expected` (P2988 both-reference specialization), and +`expected` (P2988 void+reference-error specialization). +470 tests pass. + +### Key Files + +- `include/beman/expected/expected.hpp` — full implementation: + `unexpected`, `bad_expected_access`, `expected`, `expected`, + `expected`, `expected`, `expected`, `expected` + (with monadic ops for all) +- `tests/beman/expected/` — comprehensive test suite (470 tests) + +### Step 10 Design Notes + +- `expected` is a partial specialization `template class expected` + This is more specific than both `expected requires is_void_v` and + `expected`, so it is correctly selected for `expected`. +- Storage: `E* unex_ptr_` + `bool has_val_` (no union — void has no value) +- **Trivially copyable/destructible**: pointer + bool, all operations default +- **Default constructible**: sets `has_val_ = true` (void success state) +- **Shallow const on error**: `error()` always returns `E&` regardless of + `const` on the `expected` object +- **Dangling prevention**: `expected(unexpect_t, G&&)` is deleted when + `reference_constructs_from_temporary_v` is true +- **No `operator=` from `unexpected`**: `unexpected` requires G to be + an object type (reference types are ill-formed). Rebind is done via copy + assignment from another `expected`, same as in `expected`. +- **`emplace()` returns void**: sets `has_val_ = true`, no construction needed +- **`transform_error(F)`** returns `expected` — the plain void + specialization (non-reference error, since F transforms E& → G by value) +- **Removed `expected_void_ref_fail` negative compile test**: this test + asserted `expected` was ill-formed in `expected`. Now that + Step 10 adds `expected`, this is valid and the test was removed. ### Build System - CMake 3.30+, Ninja Multi-Config -- GoogleTest via vcpkg or FetchContent +- Catch2 via FetchContent - `make test` to build and run, `make lint` for pre-commit hooks - Header-only INTERFACE library (unless modules enabled) - Default config: Asan @@ -37,46 +65,40 @@ tests are breathing tests only (`EXPECT_EQ(true, true)`). - Namespace: `beman::expected` (nested, not `beman::expected::inline_namespace`) - Include guards: `#ifndef BEMAN_EXPECTED__HPP` / `#define` / `#endif` - License: `// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception` -- Angle-bracket includes with full paths: `` -- Functions defined out-of-line within the header (body after class) +- Angle-bracket includes with full paths: `` +- Functions defined inline within the class for the reference specializations - `constexpr` everything -- Test includes: header under test twice (idempotence), then gtest, then std +- Test includes: header under test twice (idempotence), then Catch2, then std ### Reference Implementation For `optional` patterns, see `~/src/steve-downey/optional/main`: - `include/beman/optional/optional.hpp` lines 1515-2119 for the reference specialization -- Key pattern: `T* value_ = nullptr` storage, `convert_ref_init_val()` for - binding, rebind semantics on assignment -- `reference_constructs_from_temporary_v` concept for dangling prevention ## Plan See `docs/plan/index.md` for the full plan with step index, checklist, -and links to individual step files. - -## Test Plan Documents - -Each implementation step has a corresponding test plan: - -| File | Covers | -|------|--------| -| `tests-overview.md` | Framework, conventions, negative compile pattern, CMakeLists structure | -| `tests-step1.md` | `unexpected` — all testable statements from [expected.un.*] | -| `tests-step2.md` | `bad_expected_access` — [expected.bad] and [expected.bad.void] | -| `tests-step3.md` | `expected` primary template — [expected.object.*] excluding monadic | -| `tests-step4.md` | `expected` partial specialization | -| `tests-step5.md` | `expected` monadic operations | -| `tests-step6.md` | `expected` monadic operations | -| `tests-step7.md` | `expected` reference-value specialization (P2988) | -| `tests-step8.md` | `expected` reference-error specialization (P2988) | -| `tests-step9.md` | `expected` both-reference specialization (P2988) | -| `tests-step10.md`| `expected` void+reference-error specialization (P2988) | - -Read `tests-overview.md` first, then the `tests-stepN.md` for the step being -worked before writing any tests. +and links to individual step files. **All 10 steps are now complete.** ## What Comes Next -Step 1: Implement `unexpected` — see `docs/plan/step1-unexpected.md`. +The core implementation is complete. The full set of specializations: + +| Specialization | Value storage | Error storage | Step | +|----------------|---------------|---------------|------| +| `expected` | T (owned) | E (owned) | 3, 5 | +| `expected` | none | E (owned) | 4, 6 | +| `expected` | T* (pointer) | E (owned) | 7 | +| `expected` | T (owned) | E* (pointer) | 8 | +| `expected` | T* (pointer) | E* (pointer) | 9 | +| `expected` | none | E* (pointer) | 10 | + +Remaining work beyond the core implementation: + +1. **Merge Step 10** into `expected-over-references` +2. **Paper/proposal writing** — document the API surface for the C++ committee +3. **Additional test coverage** — edge cases, more type combinations +4. **Performance benchmarks** — compare with std::expected on supported compilers +5. **CI/CD setup** — ensure all specializations are tested on multiple compilers +6. **Upstream PR** — submit to beman project upstream diff --git a/docs/plan/index.md b/docs/plan/index.md index 767dcce..80dc8eb 100644 --- a/docs/plan/index.md +++ b/docs/plan/index.md @@ -37,17 +37,34 @@ Steps 7-10 are the reference specializations (the novel work in this proposal). ## Standing Conventions +### Code + - Include guards: `#ifndef`/`#define`/`#endif` (never `#pragma once`) - Format: `BEMAN_EXPECTED__HPP` (uppercase, path separators to `_`) - Includes: angle brackets, full paths from include root - SPDX license: `// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception` - Functions defined out-of-line in headers (body after class) - `constexpr` everything possible -- Test framework: GoogleTest -- Test files include the header under test twice (idempotence check) -- Each step runs `make test` and `make lint` before completion - Namespace: `beman::expected` +### Testing + +- Test framework: Catch2 (`Catch2::Catch2WithMain`) +- Test files include the header under test twice (idempotence check) +- Each step runs `make TOOLCHAIN=gcc-16 test` and `make lint` before + completion +- **Every constraint and mandate must have a negative test.** See + `docs/plan/tests-overview.md` §6 for full rules. In short: + - **Constraint** (`requires` clause) → SFINAE-friendly; test with + `static_assert(!std::is_constructible_v<...>)` or a concept detector + in a `*_constraints.test.cpp` file + - **Mandate** (`static_assert` inside a function/class body) → ill-formed + on instantiation; test with a `*_fail.cpp` negative compile test + - **Hardened precondition** → runtime trap; test under + `#if defined(BEMAN_EXPECTED_HARDENED)` + - Each negative test should have a matching positive test confirming the + operation works for conforming types + ## Step Details - [Step 1: unexpected](step1-unexpected.md) @@ -69,7 +86,7 @@ Steps 7-10 are the reference specializations (the novel work in this proposal). - [x] Step 4: `expected` — constructors, destructor, assignment, emplace, swap, observers, error_or, equality - [x] Step 5: `expected` monadic — and_then, or_else, transform, transform_error (4 ref-qualified overloads each) - [x] Step 6: `expected` monadic — and_then, or_else, transform, transform_error -- [ ] Step 7: `expected` — pointer storage, rebind assignment, observers returning T&, value_or, monadic ops, dangling prevention -- [ ] Step 8: `expected` — union+pointer storage, error as E&, rebind error assignment, observers, monadic ops -- [ ] Step 9: `expected` — both pointer storage, rebind both, observers, monadic ops -- [ ] Step 10: `expected` — no value storage, error pointer, rebind error, observers, monadic ops +- [x] Step 7: `expected` — pointer storage, rebind assignment, observers returning T&, value_or, monadic ops, dangling prevention +- [x] Step 8: `expected` — union+pointer storage, error as E&, rebind error assignment, observers, monadic ops +- [x] Step 9: `expected` — both pointer storage, rebind both, observers, monadic ops +- [x] Step 10: `expected` — no value storage, error pointer, rebind error, observers, monadic ops diff --git a/docs/plan/step10-expected-void-ref-e.md b/docs/plan/step10-expected-void-ref-e.md index f2c4883..e193eb8 100644 --- a/docs/plan/step10-expected-void-ref-e.md +++ b/docs/plan/step10-expected-void-ref-e.md @@ -120,7 +120,7 @@ Combine void-value patterns (Step 6) with error-reference patterns (Step 8): ## Procedure -1. Create branch from `main` (with Steps 4, 6, 8 merged) +1. Create branch from `expected-over-references` 2. Add `expected` specialization (or T, E& with void constraint) 3. Implement with pointer-only error storage 4. Implement constructors with dangling prevention diff --git a/docs/plan/step7-expected-ref-t.md b/docs/plan/step7-expected-ref-t.md index 04fa18d..6ebd836 100644 --- a/docs/plan/step7-expected-ref-t.md +++ b/docs/plan/step7-expected-ref-t.md @@ -202,7 +202,7 @@ using `is_convertible_v` checks. ## Procedure -1. Create branch from `main` (with Steps 1-5 merged) +1. Create branch from `expected-over-references` 2. Add `reference_constructs_from_temporary_v` concept (portable fallback) in a `detail` namespace within `beman::expected` 3. Add `expected` partial specialization with `requires std::is_lvalue_reference_v<...>` diff --git a/docs/plan/step8-expected-ref-e.md b/docs/plan/step8-expected-ref-e.md index cca36da..62402f8 100644 --- a/docs/plan/step8-expected-ref-e.md +++ b/docs/plan/step8-expected-ref-e.md @@ -129,7 +129,7 @@ Same as primary but `error()` returns `E&`: ## Procedure -1. Create branch from `main` (with Steps 1-5 merged) +1. Create branch from `expected-over-references` 2. Add `expected` partial specialization 3. Implement storage (value union member + error pointer + bool) 4. Implement constructors with dangling prevention on error side diff --git a/docs/plan/step9-expected-ref-both.md b/docs/plan/step9-expected-ref-both.md index b62621c..7913f1e 100644 --- a/docs/plan/step9-expected-ref-both.md +++ b/docs/plan/step9-expected-ref-both.md @@ -116,7 +116,7 @@ Same structure as primary but with reference semantics on both sides: ## Procedure -1. Create branch from `main` (with Steps 7-8 merged) +1. Create branch from `expected-over-references` 2. Add `expected` partial specialization 3. Implement with dual-pointer storage 4. Implement constructors (combine patterns from Steps 7 and 8) diff --git a/docs/plan/tests-overview.md b/docs/plan/tests-overview.md index fdc8f4b..f6bd196 100644 --- a/docs/plan/tests-overview.md +++ b/docs/plan/tests-overview.md @@ -9,8 +9,6 @@ corresponding `tests-stepN.md` file for that step. ## Test Framework **Catch2** (`catch2/catch_test_macros.hpp`, `Catch2::Catch2WithMain`). -The project switched from GoogleTest to Catch2 (commit `cde76dd`). The -index.md reference to GoogleTest is stale. --- @@ -31,31 +29,59 @@ Every test file includes the header under test **twice**: | **Mandate** | "Mandates: X is true" | Ill-formed at instantiation → `static_assert(!std::is_invocable_v<...>)` or a negative compile file | | **Hardened precondition** | "Hardened preconditions: X" | Runtime UB without contracts; if the implementation enforces contracts, use `REQUIRE_THROWS` under `#if defined(BEMAN_EXPECTED_HARDENED)` | -### 3. Negative compile test pattern (from transcode) +### 3. Negative compile test pattern -Each negative compile test is a `.cpp` file that **must not compile**. -In CMakeLists: +Each negative compile test is a `.cpp` file that **must not compile**, and +its failure must be verified against a **specific diagnostic** to confirm +the failure is the intended constraint/mandate, not an unrelated build error. + +#### Source file format + +Every `_fail.cpp` must have these comment lines near the top: + +```cpp +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: +// EXPECT: "" +``` + +The `EXPECT` string is the same regex passed to `add_fail_test()` in CMake. +It should match: +- The `static_assert` message string for **mandates** + (e.g. `"E must not be void"`) +- `"delete"` for **deleted function** diagnostics +- `"no matching function"` for **SFINAE / constraint** failures where no + overload is viable +- `"no matching function|delete"` when either form is possible depending + on compiler + +#### CMakeLists pattern + +Use the `add_fail_test(name source reason)` macro, which registers the +test with `PASS_REGULAR_EXPRESSION`: ```cmake -add_library(beman.expected.tests._fail OBJECT) -target_sources(beman.expected.tests._fail PRIVATE _fail.cpp) -target_link_libraries(beman.expected.tests._fail PRIVATE beman::expected) +add_fail_test(expected_ref_e_void_fail expected_ref_e_void_fail.cpp + "E must not be void" +) +``` + +The macro expands to: + +```cmake +add_library(beman.expected.tests. OBJECT) +target_sources(beman.expected.tests. PRIVATE ) +target_link_libraries(beman.expected.tests. PRIVATE beman::expected) set_target_properties( - beman.expected.tests._fail + beman.expected.tests. PROPERTIES EXCLUDE_FROM_ALL true EXCLUDE_FROM_DEFAULT_BUILD true ) add_test( - NAME _fail + NAME COMMAND ${CMAKE_COMMAND} --build "${CMAKE_BINARY_DIR}" - --target beman.expected.tests._fail --config $ + --target beman.expected.tests. --config $ ) -set_tests_properties(_fail PROPERTIES WILL_FAIL TRUE) -``` - -If the diagnostic message is reliable, replace `WILL_FAIL TRUE` with: -```cmake -set_tests_properties(_fail PROPERTIES - PASS_REGULAR_EXPRESSION "specific error text") +set_tests_properties( PROPERTIES PASS_REGULAR_EXPRESSION "") ``` ### 4. Type-trait / static_assert tests @@ -118,6 +144,72 @@ TEST_CASE("hardened: operator* on empty terminates", "[hardened]") { | 9 | `expected` | `expected_ref_both.test.cpp` | `expected_ref_both_temp_value_fail.cpp`, `expected_ref_both_temp_error_fail.cpp`, `expected_ref_both_no_default_fail.cpp` | | 10 | `expected` | `expected_void_ref_e.test.cpp` | `expected_void_ref_e_temporary_fail.cpp`, `expected_void_ref_e_const_lvalue_fail.cpp`, `expected_void_ref_e_no_value_or_fail.cpp` | +### 6. Constraint and mandate coverage requirement + +**Every `requires` clause, concept constraint, and `static_assert` mandate +in the implementation must have a corresponding negative test.** This is a +standing rule — not optional per step. + +#### How to cover each kind: + +**Constraints (requires clauses / SFINAE-friendly):** + +Write `static_assert(!trait_v<...>)` in a `*_constraints.test.cpp` file. +For operations that need a callable argument (monadic ops, etc.), use a +concept detector: + +```cpp +template +concept has_and_then = requires(F f) { std::declval().and_then(f); }; + +// Negative: lvalue overload constrained out when E is move-only +static_assert(!has_and_then&, decltype(lambda)>); + +// Positive: rvalue overload works (E is move-constructible) +static_assert(has_and_then&&, decltype(lambda)>); +``` + +For rvalue-argument constraints (dangling prevention, emplace from rvalue), +use `std::declval` to force the argument category: + +```cpp +template +concept has_emplace_from = requires { std::declval().emplace(std::declval()); }; +``` + +**Mandates (static_assert — ill-formed on instantiation):** + +Write a `*_fail.cpp` negative compile test registered with `add_fail_test()` +in CMakeLists. The file should be minimal — just `#include` the header and +attempt the ill-formed instantiation: + +```cpp +#include +void test() { + beman::expected::expected e; // must not compile +} +``` + +**Hardened preconditions:** + +Write runtime tests under `#if defined(BEMAN_EXPECTED_HARDENED)` in a +`*_hardened.test.cpp` file compiled with `-DBEMAN_EXPECTED_HARDENED`. + +#### What to cover for each step: + +- Class-level `static_assert`s (E/T type requirements) → negative compile tests +- Constructor `requires` clauses → `static_assert(!is_constructible_v<...>)` +- Assignment `requires` clauses → `static_assert(!is_assignable_v<...>)` +- Member function `requires` clauses (swap, emplace, monadic) → concept detectors +- Triviality (`= default` paths) → `static_assert(is_trivially_*_v<...>)` +- Non-triviality → `static_assert(!is_trivially_*_v<...>)` with non-trivial E/T + +#### Naming convention: + +- `_constraints.test.cpp` — SFINAE/requires tests +- `__fail.cpp` — negative compile tests +- `_hardened.test.cpp` — runtime precondition tests + --- ## Standard Reference Summary diff --git a/papers/.gitignore b/papers/.gitignore new file mode 100644 index 0000000..817c814 --- /dev/null +++ b/papers/.gitignore @@ -0,0 +1,25 @@ +# LaTeX build artifacts +std-gram.ext +*.aux +*.bbl +*.blg +*.fdb_latexmk +*.fls +*.idx +*.ilg +*.ind +*.log +*.out +*.toc +*.glo +*.pdf +_minted/ +.deps/ +.venv/ + +# Downloaded bibliography (too large to track; regenerate with make curl-bib) +wg21.bib + +# Emacs +*~ +\#*\# diff --git a/papers/D4280R0.tex b/papers/D4280R0.tex new file mode 100644 index 0000000..3dffea4 --- /dev/null +++ b/papers/D4280R0.tex @@ -0,0 +1,386 @@ +\documentclass[a4paper,10pt,oneside,openany,final,article]{memoir} +\input{common} +\settocdepth{chapter} +\usepackage{minted} +\usepackage{fontspec} +% \setromanfont{Source Serif Pro} +% \setsansfont{Source Sans Pro} +% \setmonofont{Source Code Pro} + +\begin{document} +\title{std::expected} +\author{ + Steve Downey \small<\href{mailto:sdowney@gmail.com}{sdowney@gmail.com}> \\ +} +\date{} %unused. Type date explicitly below. +\maketitle + +\begin{flushright} + \begin{tabular}{ll} + Document \#: & D4280R0 \\ + Date: & \today \\ + Project: & Programming Language C++ \\ + Audience: & LEWG + \end{tabular} +\end{flushright} + +\begin{abstract} + We propose to add a partial specialization of \tcode{std::expected} for + lvalue reference types as the value type: \tcode{expected}. + The specialization holds a pointer to \tcode{T} when engaged and stores + \tcode{E} by value when in error. Assignment has rebind semantics --- + assigning to an engaged \tcode{expected} always rebinds the + reference, never assigns through to the referent. Construction from + temporaries that would dangle is deleted. This mirrors the design of + \tcode{optional} accepted in \cite{P2988R12} and completes reference + support across the standard library's principal sum types. +\end{abstract} + +\tableofcontents* + +\chapter*{Changes Since Last Version} + +\begin{itemize} +\item \textbf{R0} Initial revision. +\end{itemize} + +\chapter{Comparison table} + +\section{Using a raw pointer for a fallible lookup} + +The conventional idiom for a function that searches a container and may fail +is to return a raw pointer, or to take an out-parameter. Neither approach +captures the error reason. + +\begin{tabular}{ lr } + \begin{minipage}[t]{0.45\columnwidth} + \begin{minted}[fontsize=\small]{c++} +Cat* find_cat(std::string_view name); + +Cat* cat = find_cat("Fido"); +if (cat != nullptr) { + return doit(*cat); +} +// error reason lost + \end{minted} + \end{minipage} + & + \begin{minipage}[t]{0.45\columnwidth} + \begin{minted}[fontsize=\small]{c++} +auto find_cat(std::string_view name) + -> std::expected; + +auto cat = find_cat("Fido"); +return cat.and_then(doit); + \end{minted} + \end{minipage} +\end{tabular} + +\section{Using \tcode{expected} as a substitute} + +Returning \tcode{expected} is a common workaround. It forces an +extra dereference through the pointer on the happy path, and the interface +still permits a null pointer to escape as a ``success''. + +\begin{tabular}{ lr } + \begin{minipage}[t]{0.45\columnwidth} + \begin{minted}[fontsize=\small]{c++} +auto find_cat(std::string_view name) + -> std::expected; + +auto cat = find_cat("Fido"); +if (cat && *cat) { // two checks + return doit(**cat); // two derefs +} + \end{minted} + \end{minipage} + & + \begin{minipage}[t]{0.45\columnwidth} + \begin{minted}[fontsize=\small]{c++} +auto find_cat(std::string_view name) + -> std::expected; + +auto cat = find_cat("Fido"); +return cat.and_then(doit); + \end{minted} + \end{minipage} +\end{tabular} + +\section{Using \tcode{expected, E>}} + +\tcode{std::reference_wrapper} avoids the null-pointer escape but is verbose +and requires \tcode{.get()} everywhere the reference is used. + +\begin{tabular}{ lr } + \begin{minipage}[t]{0.45\columnwidth} + \begin{minted}[fontsize=\small]{c++} +auto find_cat(std::string_view name) + -> std::expected< + std::reference_wrapper, + Error>; + +auto cat = find_cat("Fido"); +if (cat) { + return doit(cat->get()); +} + \end{minted} + \end{minipage} + & + \begin{minipage}[t]{0.45\columnwidth} + \begin{minted}[fontsize=\small]{c++} +auto find_cat(std::string_view name) + -> std::expected; + +auto cat = find_cat("Fido"); +return cat.and_then(doit); + \end{minted} + \end{minipage} +\end{tabular} + +\section{Monadic composition} + +The monadic API of \tcode{expected} works uniformly whether \tcode{T} is a +value type or a reference type, letting callers chain operations without +breaking out of the expected idiom. + +\begin{tabular}{ lr } + \begin{minipage}[t]{0.45\columnwidth} + \begin{minted}[fontsize=\small]{c++} +Cat* cat = find_cat("Fido"); +if (!cat) return make_error(); +std::string* name = get_name(*cat); +if (!name) return make_error(); +return process(*name); + \end{minted} + \end{minipage} + & + \begin{minipage}[t]{0.45\columnwidth} + \begin{minted}[fontsize=\small]{c++} +return find_cat("Fido") + .and_then(get_name) + .and_then(process); + \end{minted} + \end{minipage} +\end{tabular} + +\chapter{Motivation} + +\tcode{std::expected} \cite{P0323R12}, added in \CppXXIII{}, is a sum +type holding either a value of type \tcode{T} or an error of type \tcode{E}. +Like \tcode{std::optional} before it, the primary template explicitly +prohibits reference types for \tcode{T}; the current working draft contains +the static assertion: + +\begin{minted}[fontsize=\small]{c++} +static_assert(!std::is_reference_v, + "T must not be a reference (use expected specialization)"); +\end{minted} + +The comment points toward the natural next step. + +\tcode{std::optional} has been accepted as \cite{P2988R12}. Its +motivation section observes: + +\begin{quote} + Thus, we expect future papers to propose \tcode{std::expected} and + \tcode{std::variant} with the ability to hold references. +\end{quote} + +This paper delivers the first of those two promises. + +The design rationale for preferring rebind semantics over assign-through +semantics is well established by \cite{P2988R12} and the research surveyed +in \cite{P1683R0}. All library implementations that have attempted +assign-through semantics for optional-like types over references have +eventually abandoned that approach due to the bug-prone behavior it +introduces. The same applies to \tcode{expected}. + +Lookup functions that return references to elements of a container are +common in high-performance code precisely because they avoid copying +potentially large objects. When such a lookup can fail, there is no +satisfying workaround available today: + +\begin{itemize} +\item A raw pointer loses the error value and type safety. +\item \tcode{expected} requires two checks and two dereferences on + the happy path, and does not prevent the caller from returning a null + \tcode{T*} in the success channel. +\item \tcode{expected, E>} works but is verbose; + \tcode{.get()} is required everywhere the value is used, and + \tcode{reference_wrapper} does not participate in the monadic API as + naturally as a reference would. +\end{itemize} + +\tcode{expected} fills this gap cleanly. It participates in the +monadic API of \tcode{expected} (\tcode{and_then}, \tcode{transform}, +\tcode{or_else}, \tcode{transform_error}) without any awkward adapters. + +There is a principled reason the standard chose to disallow reference types +for sum types: the semantics of assignment differ subtly between value types +and reference types. For the primary template, \tcode{optional} has pure +value semantics; assignment copies or moves the stored value. For +\tcode{optional}, assignment rebinds the pointer. The observable +behavior is different. However, this difference is not a defect --- it is +the same distinction that exists between \tcode{int} and \tcode{int*}, or +between \tcode{tuple} and \tcode{tuple}. The standard already +accommodates reference types in \tcode{std::tuple}, \tcode{std::pair}, and +\tcode{std::reference_wrapper}. Sum types should be no different. + +The relationship between sum types and product types matters here. +\tcode{optional} is equivalent to \tcode{variant}; +\tcode{expected} is equivalent to \tcode{variant}. Neither +\tcode{variant} nor \tcode{tuple} can hold references today, but that is a +separate defect to address separately. \tcode{expected} can be +defined precisely and usefully now, following the exact same pattern as +\tcode{optional}. + +\chapter{Design} + +The design of \tcode{expected} follows \tcode{optional} +\cite{P2988R12} closely. Where the designs diverge it is because +\tcode{expected} carries an error value rather than simply being absent. + +\section{Storage} + +\tcode{expected} stores a \tcode{T*} pointer when engaged and an +\tcode{E} value in-place when in the error state. The default constructor +is deleted --- an engaged \tcode{expected} must always hold a valid +reference, and there is no default reference. + +\section{Rebind Semantics on Assignment} + +Assignment always rebinds the pointer, never assigns through to the referent. +This is the only semantics that is not a source of latent bugs +\cite{P1683R0}. Because an \tcode{expected} in the value state +holds only a pointer, any assignment in the value-to-value case is equivalent +to pointer copy, and the full set of converting assignments can be +synthesized from copy-assignment via implicit construction of a temporary +\tcode{expected} on the right-hand side. + +\section{Dangling Prevention} + +Constructors from types \tcode{U} that would bind \tcode{T\&} to a +temporary --- as defined by \tcode{reference_constructs_from_temporary} --- are +explicitly deleted. This mirrors the approach taken for +\tcode{optional} \cite{P2988R12}. + +Deletion of these constructors can cause ambiguity in overload resolution +in the same way it does for \tcode{optional}. The design follows the +precedent set by \tcode{std::tuple} and \tcode{std::pair}: make dangling +calls ill-formed rather than silently eliding candidates. + +\section{Value Access} + +\tcode{operator*()} and \tcode{value()} return \tcode{T\&}. Value category +is shallow: the reference stored in the \tcode{expected} is not propagated. +An \tcode{expected\&\&} does not steal from the referent; it copies +the pointer. This matches the behavior of \tcode{optional}. + +\section{Error Access} + +\tcode{error()} returns a reference to the stored \tcode{E}, with the same +cv-ref qualifications as the primary template. + +\section{value\_or} + +\tcode{value_or} returns \tcode{T} by value, for the same reasons as in +\tcode{optional::value_or} \cite{P2988R12}: returning a reference +would require the alternative to be of type \tcode{T\&} or compatible, which +is overly restrictive. Free-function alternatives such as +\tcode{std::reference_or} \cite{P1255R12} and \tcode{std::reference_or} +\cite{D4270R0} provide reference-returning variants over all nullable types +for callers who need that behavior. + +\section{Monadic Operations} + +\tcode{and_then(f)}: calls \tcode{f} with \tcode{T\&}; returns the result of +\tcode{f}, which must be an \tcode{expected} specialization. + +\tcode{transform(f)}: calls \tcode{f} with \tcode{T\&}; wraps the result in +an \tcode{expected}. + +\tcode{or_else(f)}: calls \tcode{f} with the error \tcode{E}; returns the +result of \tcode{f}, which must be an \tcode{expected} specialization. + +\tcode{transform_error(f)}: calls \tcode{f} with the error \tcode{E}; wraps +the new error in an \tcode{expected}. + +All four operations propagate the value or error, whichever is not being +processed, without modification. + +\section{In-Place Construction} + +\tcode{expected(std::in_place_t, Args...)} is deleted. There is no place to +construct a \tcode{T} in an \tcode{expected}. In-place error +construction via \tcode{unexpect_t} works as in the primary template, because +the error type \tcode{E} is stored by value. + +\section{Shallow vs Deep \tcode{const}} + +\tcode{const expected} permits modification of the referent through +\tcode{operator*()}. Constness is shallow, consistent with the pointer model. +If deep constness is desired, use \tcode{expected}. + +\section{Relationship to \tcode{expected} and \tcode{expected}} + +The three specializations --- primary (\tcode{T} object type), void value +(\tcode{expected}), and lvalue reference (\tcode{expected}) +--- share the same error-handling API. Conversion from +\tcode{expected} to \tcode{expected} follows the same +constraints as conversion between \tcode{optional} and +\tcode{optional}. + +\section{Feature Test Macro} + +A feature test macro \tcode{__cpp_lib_expected_ref} is proposed, allowing +code to detect the availability of the reference specializations. + +\chapter{Proposal} + +Add a partial specialization \tcode{expected} to the \tcode{} +header, with the design described above. + +The reference implementation is available as part of the Beman Project's +\tcode{beman.expected26} library \cite{Downey_beman_expected}. + +\chapter{Impact on the standard} + +A pure library extension. No changes to the language are required. No +existing well-formed code is made ill-formed. + +The change adds a new partial specialization of \tcode{expected}; code that +currently relies on the absence of such a specialization (e.g., by static +assertion) will need to be updated, but such code would necessarily have been +deliberately excluding a feature rather than relying on normal program +behavior. + +\chapter{Wording} + +Wording to follow in a subsequent revision. + +\chapter{Acknowledgments} + +The design of \tcode{expected} is a direct extension of the work on +\tcode{optional} \cite{P2988R12} by Steve Downey and Peter Sommerlad. +Many thanks to JeanHeyd Meneide for the foundational research in +\cite{P1683R0} and \cite{REFBIND}. + +Thanks to the contributors to the Beman Project's \tcode{beman.expected26} +reference implementation \cite{Downey_beman_expected}. + +\chapter*{Document history} + +\begin{itemize} +\item \textbf{R0} Initial revision. +\end{itemize} + +\renewcommand{\bibname}{References} +\bibliographystyle{abstract} +\bibliography{wg21,mybiblio} + +\backmatter +\chapter*{Implementation} + +\inputminted{c++}{../include/beman/expected/expected.hpp} + +\end{document} diff --git a/papers/Makefile b/papers/Makefile new file mode 100644 index 0000000..2e57783 --- /dev/null +++ b/papers/Makefile @@ -0,0 +1,103 @@ +# Makefile -*-makefile-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +export + +MAKEFLAGS += --no-builtin-rules + +.SUFFIXES: + +PYEXECPATH ?= $(shell which python3.13 || which python3.12 || which python3.11 || which python3.10 || which python3.9 || which python3.8 || which python3) +PYTHON ?= $(notdir $(PYEXECPATH)) +VENV := .venv +UV := $(shell command -v uv 2> /dev/null) +ACTIVATE := $(UV) run +PYEXEC := $(UV) run python +MARKER = .initialized.venv.stamp +DEPS_DIR := .deps + +default: papers + +.PHONY: env +env: + $(foreach v, $(.VARIABLES), $(info $(v) = $($(v)))) + +.PHONY: realclean +realclean: ## Delete all artifacts + +.PHONY: venv +venv: ## Create python virtual env +venv: $(VENV) + +.PHONY: clean-venv +clean-venv: ## Delete python virtual env + -rm -rf $(VENV) + +realclean: clean-venv + +$(VENV): + $(UV) venv --python $(PYTHON) + +.PHONY: dev-shell +dev-shell: venv +dev-shell: ## Shell with the venv activated + $(ACTIVATE) $(notdir $(SHELL)) + +.PHONY: bash zsh +bash zsh: venv +bash zsh: ## Run bash or zsh with the venv activated + $(ACTIVATE) $@ + +$(DEPS_DIR): + mkdir -p $(DEPS_DIR) + +.PHONY: papers +papers: D4280R0.pdf ## Compile papers + +D4280R0.pdf : D4280R0.tex wg21.bib + +%.pdf : %.tex $(DEPS_DIR) | $(VENV) + latexmk -f -shell-escape -pdflua -use-make -deps -deps-out=$(DEPS_DIR)/$@.d -MP $< + +define curl_cmd = + curl https://wg21.link/index.bib > wg21.bib +endef + +wg21.bib: + $(curl_cmd) + +.PHONY: curl-bib +curl-bib: ## Download wg21.bib from wg21.link + $(curl_cmd) + +.PHONY: clean +clean: + -latexmk -c + -rm *.pdf + +# Include dependencies +-include $(DEPS_DIR)/*.d + +ifeq ($(UV),) +define install_uv_cmd +pipx install uv +endef + +define uv_error_message + +'uv' command not found. +Please install uv or set the UV variable to the path of the uv binary. +The makefile target "install-uv" will run ``$(install_uv_cmd)'' +endef + +$(error "$(uv_error_message)") +endif + +.PHONY: install-uv +install-uv: ## install uv via `pipx install uv` + $(install_uv_cmd) + +# Help target +.PHONY: help +help: ## Show this help. + @awk 'BEGIN {FS = ":.*?## "} /^[.a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort diff --git a/papers/latexmkrc b/papers/latexmkrc new file mode 100644 index 0000000..5360089 --- /dev/null +++ b/papers/latexmkrc @@ -0,0 +1,7 @@ +$cleanup_includes_cusdep_generated = 1; +$cleanup_includes_generated = 1; + +# Paper infrastructure (common.tex, stdtex/, abstract.bst) lives in the +# wg21-latex subtree; resolve inputs and bibliography styles from there. +ensure_path('TEXINPUTS', './wg21-latex//'); +ensure_path('BSTINPUTS', './wg21-latex//'); diff --git a/papers/mybiblio.bib b/papers/mybiblio.bib new file mode 100644 index 0000000..4585afa --- /dev/null +++ b/papers/mybiblio.bib @@ -0,0 +1,27 @@ +@misc{D4270R0, +author = {Downey, Stephen}, +title = {Free Value Or Else}, +howpublished = {\url{https://github.com/steve-downey/free_value_or}}, +year = {2026}, +note = {D4270R0} +} + +@misc{Downey_beman_expected, +author = {Downey, Stephen}, +title = {{beman.expected26}}, +howpublished = {\url{https://github.com/bemanproject/expected26}}, +} + +@misc{The_Beman_Project_beman_optional, +author = {The Beman Project}, +license = {Apache-2.0}, +title = {{beman.optional}}, +howpublished = {\url{https://github.com/bemanproject/optional}}, +} + +@misc{REFBIND, +author = {JeanHeyd Meneide}, +title = {To Bind and Loose a Reference | The Pasture}, +howpublished = {\url{https://thephd.dev/to-bind-and-loose-a-reference-optional}}, +note = {(Accessed on 06/26/2026)} +} diff --git a/papers/wg21-latex/.gitignore b/papers/wg21-latex/.gitignore new file mode 100644 index 0000000..fd3ffdd --- /dev/null +++ b/papers/wg21-latex/.gitignore @@ -0,0 +1,547 @@ + +## Core latex/pdflatex auxiliary files: +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.fmt +*.fot +*.cb +*.cb2 +.*.lb + +## Intermediate documents: +*.dvi +*.xdv +*-converted-to.* +# these rules might exclude image files for figures etc. +# *.ps +# *.eps +# *.pdf + +## Generated if empty string is given at "Please type another file name for output:" +.pdf + +## Bibliography auxiliary files (bibtex/biblatex/biber): +*.bbl +*.bbl-SAVE-ERROR +*.bcf +*.bcf-SAVE-ERROR +*.blg +*-blx.aux +*-blx.bib +*.run.xml + +## Build tool auxiliary files: +*.fdb_latexmk +*.synctex +*.synctex(busy) +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync +*.rubbercache +rubber.cache + +## Build tool directories for auxiliary files +# latexrun +latex.out/ + +## Auxiliary and intermediate files from other packages: +# algorithms +*.alg +*.loa + +# achemso +acs-*.bib + +# amsthm +*.thm + +# attachfile2 +*.atfi + +# beamer +*.nav +*.pre +*.snm +*.vrb + +# changes +*.soc +*.loc + +# comment +*.cut + +# cprotect +*.cpt + +# elsarticle (documentclass of Elsevier journals) +*.spl + +# endnotes +*.ent + +# fixme +*.lox + +# feynmf/feynmp +*.mf +*.mp +*.t[1-9] +*.t[1-9][0-9] +*.tfm + +#(r)(e)ledmac/(r)(e)ledpar +*.end +*.?end +*.[1-9] +*.[1-9][0-9] +*.[1-9][0-9][0-9] +*.[1-9]R +*.[1-9][0-9]R +*.[1-9][0-9][0-9]R +*.eledsec[1-9] +*.eledsec[1-9]R +*.eledsec[1-9][0-9] +*.eledsec[1-9][0-9]R +*.eledsec[1-9][0-9][0-9] +*.eledsec[1-9][0-9][0-9]R + +# glossaries +*.acn +*.acr +*.glg +*.glg-abr +*.glo +*.glo-abr +*.gls +*.gls-abr +*.glsdefs +*.lzo +*.lzs +*.slg +*.slo +*.sls + +# uncomment this for glossaries-extra (will ignore makeindex's style files!) +# *.ist + +# gnuplot +*.gnuplot +*.table + +# gnuplottex +*-gnuplottex-* + +# gregoriotex +*.gaux +*.glog +*.gtex + +# htlatex +*.4ct +*.4tc +*.idv +*.lg +*.trc +*.xref + +# hypdoc +*.hd + +# hyperref +*.brf + +# knitr +*-concordance.tex +# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files +# *.tikz +*-tikzDictionary + +# latexindent will create succesive backup files by default +#*.bak* + +# listings +*.lol + +# luatexja-ruby +*.ltjruby + +# makeidx +*.idx +*.ilg +*.ind + +# minitoc +*.maf +*.mlf +*.mlt +*.mtc[0-9]* +*.slf[0-9]* +*.slt[0-9]* +*.stc[0-9]* + +# minted +_minted* +*.data.minted +*.pyg + +# morewrites +*.mw + +# newpax +*.newpax + +# nomencl +*.nlg +*.nlo +*.nls + +# pax +*.pax + +# pdfpcnotes +*.pdfpc + +# sagetex +*.sagetex.sage +*.sagetex.py +*.sagetex.scmd + +# scrwfile +*.wrt + +# spelling +*.spell.bad +*.spell.txt + +# svg +svg-inkscape/ + +# sympy +*.sout +*.sympy +sympy-plots-for-*.tex/ + +# pdfcomment +*.upa +*.upb + +# pythontex +*.pytxcode +pythontex-files-*/ + +# tcolorbox +*.listing + +# thmtools +*.loe + +# TikZ & PGF +*.dpth +*.md5 +*.auxlock + +# titletoc +*.ptc + +# todonotes +*.tdo + +# vhistory +*.hst +*.ver + +# easy-todo +*.lod + +# xcolor +*.xcp + +# xmpincl +*.xmpi + +# xindy +*.xdy + +# xypic precompiled matrices and outlines +*.xyc +*.xyd + +# endfloat +*.ttt +*.fff + +# Latexian +TSWLatexianTemp* + +## Editors: +# WinEdt +*.bak +*.sav + +# latexindent.pl +*.bak[0-9]* + +# Texpad +.texpadtmp + +# LyX +*.lyx~ + +# Kile +*.backup + +# gummi +.*.swp + +# KBibTeX +*~[0-9]* + +# TeXnicCenter +*.tps + +# auto folder when using emacs and auctex +./auto/* +*.el + +# expex forward references with \gathertags +*-tags.tex + +# standalone packages +*.sta + +# Makeindex log files +*.lpz + +# xwatermark package +*.xwm + +# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib +# option is specified. Footnotes are the stored in a file with suffix Notes.bib. +# Uncomment the next line to have this generated file ignored. +#*Notes.bib + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml +/.deps/ +/wg21.bib +/std-gram.ext diff --git a/papers/wg21-latex/Makefile b/papers/wg21-latex/Makefile new file mode 100644 index 0000000..2e2775f --- /dev/null +++ b/papers/wg21-latex/Makefile @@ -0,0 +1,126 @@ +# Makefile -*-makefile-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +export + +MAKEFLAGS += --no-builtin-rules + +.SUFFIXES: + +PYEXECPATH ?= $(shell which python3.13 || which python3.12 || which python3.11 || which python3.10 || which python3.9 || which python3.8 || which python3) +PYTHON ?= $(notdir $(PYEXECPATH)) +VENV := .venv +UV := $(shell command -v uv 2> /dev/null) +ACTIVATE := $(UV) run +PYEXEC := $(UV) run python +MARKER = .initialized.venv.stamp +PRE_COMMIT := $(UV) run pre-commit +DEPS_DIR := .deps + +default: papers + +.PHONY: env +env: + $(foreach v, $(.VARIABLES), $(info $(v) = $($(v)))) + +.PHONY: realclean +realclean: ## Delete all artifacts + +.PHONY: venv +venv: ## Create python virtual env +venv: $(VENV)/$(MARKER) + +.PHONY: clean-venv +clean-venv: ## Delete python virtual env + -rm -rf $(VENV) + +realclean: clean-venv + +.PHONY: show-venv +show-venv: venv +show-venv: ## Debugging target - show venv details + $(PYEXEC) -c "import sys; print('Python ' + sys.version.replace('\n',''))" + @echo venv: $(VENV) + +uv.lock: pyproject.toml + $(UV) lock + +$(VENV): + $(UV) venv --python $(PYTHON) + +$(VENV)/$(MARKER): uv.lock | $(VENV) + $(UV) sync + touch $(VENV)/$(MARKER) + +.PHONY: dev-shell +dev-shell: venv +dev-shell: ## Shell with the venv activated + $(ACTIVATE) $(notdir $(SHELL)) + +.PHONY: bash zsh +bash zsh: venv +bash zsh: ## Run bash or zsh with the venv activated + $(ACTIVATE) $@ + +.PHONY: lint +lint: venv +lint: ## Run all configured tools in pre-commit + $(PRE_COMMIT) run -a + +.PHONY: lint-manual +lint-manual: venv +lint-manual: ## Run all manual tools in pre-commit + $(PRE_COMMIT) run --hook-stage manual -a + +$(DEPS_DIR): + mkdir -p $(DEPS_DIR) + +.PHONY: papers +papers: example-paper.pdf ## Compile papers + +%.pdf : %.tex $(DEPS_DIR) | $(VENV) + $(SOURCE_VENV) latexmk -f -shell-escape -pdflua -use-make -deps -deps-out=$(DEPS_DIR)/$@.d -MP $< + +define curl_cmd = + curl https://wg21.link/index.bib > wg21.bib +endef + +wg21.bib: + $(curl_cmd) + +.PHONY: curl-bib +curl-bib: ## Download wg21.bib from wg21.link + $(curl_cmd) + +.PHONY: clean +clean: + -latexmk -c + -rm *.pdf + +# Include dependencies +# $(foreach file,$(TARGET),$(eval -include $(DEPS_DIR)/$(file).d)) +-include $(DEPS_DIR)/*.d + +ifeq ($(UV),) +define install_uv_cmd +pipx install uv +endef + +define uv_error_message + +'uv' command not found. +Please install uv or set the UV variable to the path of the uv binary. +The makefile target "install-uv" will run ``$(install_uv_cmd)'' +endef + +$(error "$(uv_error_message)") +endif + +.PHONY: install-uv +install-uv: ## install uv via `pipx install uv` + $(install_uv_cmd) + +# Help target +.PHONY: help +help: ## Show this help. + @awk 'BEGIN {FS = ":.*?## "} /^[.a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort diff --git a/papers/wg21-latex/README.md b/papers/wg21-latex/README.md new file mode 100644 index 0000000..670f835 --- /dev/null +++ b/papers/wg21-latex/README.md @@ -0,0 +1,17 @@ +# P2988 + + + +## Building P2988 + +Compiling the paper requires a working LaTeX installation. See instructions for configuring your system at [C++ Standard Draft Sources](https://github.com/cplusplus/draft/blob/main/README.rst) + +The papers/ subdirectory has the LaTeX souces for P2988 and the supporting macro definitions. To build, run + +```shell +make papers +``` + +A working recent Python 3 is required to format the sources for the paper. A virtual env will be created in the papers subdirectory which the `minted` LaTeX package will use. diff --git a/papers/wg21-latex/abstract.bst b/papers/wg21-latex/abstract.bst new file mode 100644 index 0000000..ab2459d --- /dev/null +++ b/papers/wg21-latex/abstract.bst @@ -0,0 +1,1345 @@ +% BibTeX bibliography style `abstract' +% by David Kotz dfk@cs.duke.edu +% March 1989, May 1993, November 1995, October 2000 +% modified from +% BibTeX standard bibliography style `alpha' + % version 0.99a for BibTeX versions 0.99a or later, LaTeX version 2.09. + % Copyright (C) 1985, all rights reserved. + % Copying of this file is authorized only if either + % (1) you make absolutely no changes to your copy, including name, or + % (2) if you do make changes, you name it something other than + % btxbst.doc, plain.bst, unsrt.bst, alpha.bst, and abbrv.bst. + % This restriction helps ensure that all standard styles are identical. + % The file btxbst.doc has the documentation for this style. + +% The 'abstract' section is suited for multi-paragraph abstracts, if +% necessary, but note that bibtex takes out the blank line that you +% might use to indicate a new paragraph. Use \par where you want a +% new paragraph in an abstract or comment. + +% DFK added abstract, comment, keyword, url +ENTRY + { abstract + address + author + booktitle + chapter + comment + edition + editor + howpublished + institution + journal + key + keyword + month + note + number + organization + pages + private + publisher + school + series + title + type + URL + volume + year + } + {} + { label extra.label sort.label } + +INTEGERS { output.state before.all mid.sentence after.sentence after.block } + +FUNCTION {init.state.consts} +{ #0 'before.all := + #1 'mid.sentence := + #2 'after.sentence := + #3 'after.block := +} + +STRINGS { s t } + +FUNCTION {output.nonnull} +{ swap$ + output.state mid.sentence = + { ", " * write$ } + { output.state after.block = + { add.period$ write$ + newline$ + "\newblock " write$ + } + { output.state before.all = + 'write$ + { add.period$ " " * write$ } + if$ + } + if$ + mid.sentence 'output.state := + } + if$ +} + +FUNCTION {output} +{ duplicate$ empty$ + 'pop$ + 'output.nonnull + if$ +} + +FUNCTION {output.check} +{ 't := + duplicate$ empty$ + { pop$ "empty " t * " in " * cite$ * warning$ } + 'output.nonnull + if$ +} + +% DFK changed to use cite$ for the label +FUNCTION {output.bibitem} +{ newline$ + "\bibitem[" write$ + cite$ write$ + "]{" write$ + cite$ write$ + "}" write$ + newline$ + "" + before.all 'output.state := +} + +FUNCTION {fin.entry} +{ add.period$ + write$ + newline$ +} + +FUNCTION {new.block} +{ output.state before.all = + 'skip$ + { after.block 'output.state := } + if$ +} + +FUNCTION {new.sentence} +{ output.state after.block = + 'skip$ + { output.state before.all = + 'skip$ + { after.sentence 'output.state := } + if$ + } + if$ +} + +FUNCTION {not} +{ { #0 } + { #1 } + if$ +} + +FUNCTION {and} +{ 'skip$ + { pop$ #0 } + if$ +} + +FUNCTION {or} +{ { pop$ #1 } + 'skip$ + if$ +} + +FUNCTION {new.block.checka} +{ empty$ + 'skip$ + 'new.block + if$ +} + +FUNCTION {new.block.checkb} +{ empty$ + swap$ empty$ + and + 'skip$ + 'new.block + if$ +} + +FUNCTION {new.sentence.checka} +{ empty$ + 'skip$ + 'new.sentence + if$ +} + +FUNCTION {new.sentence.checkb} +{ empty$ + swap$ empty$ + and + 'skip$ + 'new.sentence + if$ +} + +FUNCTION {field.or.null} +{ duplicate$ empty$ + { pop$ "" } + 'skip$ + if$ +} + +FUNCTION {emphasize} +{ duplicate$ empty$ + { pop$ "" } + { "{\em " swap$ * "}" * } + if$ +} + +INTEGERS { nameptr namesleft numnames } + +FUNCTION {format.names} +{ 's := + #1 'nameptr := + s num.names$ 'numnames := + numnames 'namesleft := + { namesleft #0 > } + { s nameptr "{ff~}{vv~}{ll}{, jj}" format.name$ 't := + nameptr #1 > + { namesleft #1 > + { ", " * t * } + { numnames #2 > + { "," * } + 'skip$ + if$ + t "others" = + { " et~al." * } + { " and " * t * } + if$ + } + if$ + } + 't + if$ + nameptr #1 + 'nameptr := + namesleft #1 - 'namesleft := + } + while$ +} + +FUNCTION {format.authors} +{ author empty$ + { "" } + { author format.names } + if$ +} + +FUNCTION {format.editors} +{ editor empty$ + { "" } + { editor format.names + editor num.names$ #1 > + { ", editors" * } + { ", editor" * } + if$ + } + if$ +} + +FUNCTION {format.title} +{ title empty$ + { "" } + { title "t" change.case$ } + if$ +} + +FUNCTION {n.dashify} +{ 't := + "" + { t empty$ not } + { t #1 #1 substring$ "-" = + { t #1 #2 substring$ "--" = not + { "--" * + t #2 global.max$ substring$ 't := + } + { { t #1 #1 substring$ "-" = } + { "-" * + t #2 global.max$ substring$ 't := + } + while$ + } + if$ + } + { t #1 #1 substring$ * + t #2 global.max$ substring$ 't := + } + if$ + } + while$ +} + +FUNCTION {format.date} +{ year empty$ + { month empty$ + { "" } + { "there's a month but no year in " cite$ * warning$ + month + } + if$ + } + { month empty$ + 'year + { month " " * year * } + if$ + } + if$ +} + +FUNCTION {format.btitle} +{ title emphasize +} + +FUNCTION {tie.or.space.connect} +{ duplicate$ text.length$ #3 < + { "~" } + { " " } + if$ + swap$ * * +} + +FUNCTION {either.or.check} +{ empty$ + 'pop$ + { "can't use both " swap$ * " fields in " * cite$ * warning$ } + if$ +} + +FUNCTION {format.bvolume} +{ volume empty$ + { "" } + { "volume" volume tie.or.space.connect + series empty$ + 'skip$ + { " of " * series emphasize * } + if$ + "volume and number" number either.or.check + } + if$ +} + +FUNCTION {format.number.series} +{ volume empty$ + { number empty$ + { series field.or.null } + { output.state mid.sentence = + { "number" } + { "Number" } + if$ + number tie.or.space.connect + series empty$ + { "there's a number but no series in " cite$ * warning$ } + { " in " * series * } + if$ + } + if$ + } + { "" } + if$ +} + +FUNCTION {format.edition} +{ edition empty$ + { "" } + { output.state mid.sentence = + { edition "l" change.case$ " edition" * } + { edition "t" change.case$ " edition" * } + if$ + } + if$ +} + +INTEGERS { multiresult } + +FUNCTION {multi.page.check} +{ 't := + #0 'multiresult := + { multiresult not + t empty$ not + and + } + { t #1 #1 substring$ + duplicate$ "-" = + swap$ duplicate$ "," = + swap$ "+" = + or or + { #1 'multiresult := } + { t #2 global.max$ substring$ 't := } + if$ + } + while$ + multiresult +} + +FUNCTION {format.pages} +{ pages empty$ + { "" } + { pages multi.page.check + { "pages" pages n.dashify tie.or.space.connect } + { "page" pages tie.or.space.connect } + if$ + } + if$ +} + +FUNCTION {format.vol.num.pages} +{ volume field.or.null + number empty$ + 'skip$ + { "(" number * ")" * * + volume empty$ + { "there's a number but no volume in " cite$ * warning$ } + 'skip$ + if$ + } + if$ + pages empty$ + 'skip$ + { duplicate$ empty$ + { pop$ format.pages } + { ":" * pages n.dashify * } + if$ + } + if$ +} + +FUNCTION {format.chapter.pages} +{ chapter empty$ + 'format.pages + { type empty$ + { "chapter" } + { type "l" change.case$ } + if$ + chapter tie.or.space.connect + pages empty$ + 'skip$ + { ", " * format.pages * } + if$ + } + if$ +} + +FUNCTION {format.in.ed.booktitle} +{ booktitle empty$ + { "" } + { editor empty$ + { "In " booktitle emphasize * } + { "In " format.editors * ", " * booktitle emphasize * } + if$ + } + if$ +} + +FUNCTION {empty.misc.check} +{ author empty$ title empty$ howpublished empty$ + month empty$ year empty$ note empty$ + and and and and and + key empty$ not and + { "all relevant fields are empty in " cite$ * warning$ } + 'skip$ + if$ +} + +FUNCTION {format.thesis.type} +{ type empty$ + 'skip$ + { pop$ + type "t" change.case$ + } + if$ +} + +FUNCTION {format.tr.number} +{ type empty$ + { "Technical Report" } + 'type + if$ + number empty$ + { "t" change.case$ } + { number tie.or.space.connect } + if$ +} + +FUNCTION {format.article.crossref} +{ key empty$ + { journal empty$ + { "need key or journal for " cite$ * " to crossref " * crossref * + warning$ + "" + } + { "In {\em " journal * "\/}" * } + if$ + } + { "In " key * } + if$ + " \cite{" * crossref * "}" * +} + +FUNCTION {format.crossref.editor} +{ editor #1 "{vv~}{ll}" format.name$ + editor num.names$ duplicate$ + #2 > + { pop$ " et~al." * } + { #2 < + 'skip$ + { editor #2 "{ff }{vv }{ll}{ jj}" format.name$ "others" = + { " et~al." * } + { " and " * editor #2 "{vv~}{ll}" format.name$ * } + if$ + } + if$ + } + if$ +} + +FUNCTION {format.book.crossref} +{ volume empty$ + { "empty volume in " cite$ * "'s crossref of " * crossref * warning$ + "In " + } + { "Volume" volume tie.or.space.connect + " of " * + } + if$ + editor empty$ + editor field.or.null author field.or.null = + or + { key empty$ + { series empty$ + { "need editor, key, or series for " cite$ * " to crossref " * + crossref * warning$ + "" * + } + { "{\em " * series * "\/}" * } + if$ + } + { key * } + if$ + } + { format.crossref.editor * } + if$ + " \cite{" * crossref * "}" * +} + +FUNCTION {format.incoll.inproc.crossref} +{ editor empty$ + editor field.or.null author field.or.null = + or + { key empty$ + { booktitle empty$ + { "need editor, key, or booktitle for " cite$ * " to crossref " * + crossref * warning$ + "" + } + { "In {\em " booktitle * "\/}" * } + if$ + } + { "In " key * } + if$ + } + { "In " format.crossref.editor * } + if$ + " \cite{" * crossref * "}" * +} + +% DFK added +FUNCTION {format.abstract} +{ abstract empty$ + { "" } + { "\begin{quotation}\noindent {\bf Abstract:} " + abstract * + " \end{quotation}" *} + if$ +} + +% DFK added +FUNCTION {format.keyword} +{ keyword empty$ + { "" } + { "\begin{quote} {\bf Keyword:} " keyword * " \end{quote}" *} + if$ +} + +% DFK added +FUNCTION {format.url} +{ URL empty$ + { "" } + { "\begin{quote} {\bf URL} \verb`" URL * "` \end{quote}" *} + if$ +} + +% DFK added +% 5/93 added "private", as part of the comment, if both exist. +FUNCTION {format.comment} +{ comment empty$ + { private empty$ + { "" } + { "\begin{quote} {\bf Private:} " private * " \end{quote}" *} + if$ + } + { private empty$ + { "\begin{quote} {\bf Comment:} " comment * " \end{quote}" *} + { "\begin{quote} {\bf Comment:} " comment * + " {\bf Private:} " * private * + " \end{quote}" * } + if$ + } + if$ +} + +% DFK added +FUNCTION {dfk.stuff} +{ new.block + format.URL write$ newline$ + format.abstract write$ newline$ + format.keyword write$ newline$ + format.comment write$ newline$ +} + +% DFK: added a call to dfk.stuff in all entry-type functions below + +FUNCTION {article} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + crossref missing$ + { journal emphasize "journal" output.check + format.vol.num.pages output + format.date "year" output.check + } + { format.article.crossref output.nonnull + format.pages output + } + if$ + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {book} +{ output.bibitem + author empty$ + { format.editors "author and editor" output.check } + { format.authors output.nonnull + crossref missing$ + { "author and editor" editor either.or.check } + 'skip$ + if$ + } + if$ + new.block + format.btitle "title" output.check + crossref missing$ + { format.bvolume output + new.block + format.number.series output + new.sentence + publisher "publisher" output.check + address output + } + { new.block + format.book.crossref output.nonnull + } + if$ + format.edition output + format.date "year" output.check + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {booklet} +{ output.bibitem + format.authors output + new.block + format.title "title" output.check + howpublished address new.block.checkb + howpublished output + address output + format.date output + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {inbook} +{ output.bibitem + author empty$ + { format.editors "author and editor" output.check } + { format.authors output.nonnull + crossref missing$ + { "author and editor" editor either.or.check } + 'skip$ + if$ + } + if$ + new.block + format.btitle "title" output.check + crossref missing$ + { format.bvolume output + format.chapter.pages "chapter and pages" output.check + new.block + format.number.series output + new.sentence + publisher "publisher" output.check + address output + } + { format.chapter.pages "chapter and pages" output.check + new.block + format.book.crossref output.nonnull + } + if$ + format.edition output + format.date "year" output.check + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {incollection} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + crossref missing$ + { format.in.ed.booktitle "booktitle" output.check + format.bvolume output + format.number.series output + format.chapter.pages output + new.sentence + publisher "publisher" output.check + address output + format.edition output + format.date "year" output.check + } + { format.incoll.inproc.crossref output.nonnull + format.chapter.pages output + } + if$ + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {inproceedings} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + crossref missing$ + { format.in.ed.booktitle "booktitle" output.check + format.bvolume output + format.number.series output + format.pages output + address empty$ + { organization publisher new.sentence.checkb + organization output + publisher output + format.date "year" output.check + } + { address output.nonnull + format.date "year" output.check + new.sentence + organization output + publisher output + } + if$ + } + { format.incoll.inproc.crossref output.nonnull + format.pages output + } + if$ + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {conference} { inproceedings } + +FUNCTION {manual} +{ output.bibitem + author empty$ + { organization empty$ + 'skip$ + { organization output.nonnull + address output + } + if$ + } + { format.authors output.nonnull } + if$ + new.block + format.btitle "title" output.check + author empty$ + { organization empty$ + { address new.block.checka + address output + } + 'skip$ + if$ + } + { organization address new.block.checkb + organization output + address output + } + if$ + format.edition output + format.date output + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {mastersthesis} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + "Master's thesis" format.thesis.type output.nonnull + school "school" output.check + address output + format.date "year" output.check + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {misc} +{ output.bibitem + format.authors output + title howpublished new.block.checkb + format.title output + howpublished new.block.checka + howpublished output + format.date output + new.block + note output + fin.entry + dfk.stuff + + empty.misc.check +} + +FUNCTION {phdthesis} +{ output.bibitem + format.authors "author" output.check + new.block + format.btitle "title" output.check + new.block + "PhD thesis" format.thesis.type output.nonnull + school "school" output.check + address output + format.date "year" output.check + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {proceedings} +{ output.bibitem + editor empty$ + { organization output } + { format.editors output.nonnull } + if$ + new.block + format.btitle "title" output.check + format.bvolume output + format.number.series output + address empty$ + { editor empty$ + { publisher new.sentence.checka } + { organization publisher new.sentence.checkb + organization output + } + if$ + publisher output + format.date "year" output.check + } + { address output.nonnull + format.date "year" output.check + new.sentence + editor empty$ + 'skip$ + { organization output } + if$ + publisher output + } + if$ + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {techreport} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + format.tr.number output.nonnull + institution "institution" output.check + address output + format.date "year" output.check + new.block + note output + fin.entry + dfk.stuff +} + +FUNCTION {unpublished} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + note "note" output.check + format.date output + fin.entry + dfk.stuff +} + +FUNCTION {default.type} { misc } + +MACRO {jan} {"January"} + +MACRO {feb} {"February"} + +MACRO {mar} {"March"} + +MACRO {apr} {"April"} + +MACRO {may} {"May"} + +MACRO {jun} {"June"} + +MACRO {jul} {"July"} + +MACRO {aug} {"August"} + +MACRO {sep} {"September"} + +MACRO {oct} {"October"} + +MACRO {nov} {"November"} + +MACRO {dec} {"December"} + +MACRO {acmcs} {"ACM Computing Surveys"} + +MACRO {acta} {"Acta Informatica"} + +MACRO {cacm} {"Communications of the ACM"} + +MACRO {ibmjrd} {"IBM Journal of Research and Development"} + +MACRO {ibmsj} {"IBM Systems Journal"} + +MACRO {ieeese} {"IEEE Transactions on Software Engineering"} + +MACRO {ieeetc} {"IEEE Transactions on Computers"} + +MACRO {ieeetcad} + {"IEEE Transactions on Computer-Aided Design of Integrated Circuits"} + +MACRO {ipl} {"Information Processing Letters"} + +MACRO {jacm} {"Journal of the ACM"} + +MACRO {jcss} {"Journal of Computer and System Sciences"} + +MACRO {scp} {"Science of Computer Programming"} + +MACRO {sicomp} {"SIAM Journal on Computing"} + +MACRO {tocs} {"ACM Transactions on Computer Systems"} + +MACRO {tods} {"ACM Transactions on Database Systems"} + +MACRO {tog} {"ACM Transactions on Graphics"} + +MACRO {toms} {"ACM Transactions on Mathematical Software"} + +MACRO {toois} {"ACM Transactions on Office Information Systems"} + +MACRO {toplas} {"ACM Transactions on Programming Languages and Systems"} + +MACRO {tcs} {"Theoretical Computer Science"} + +READ + +FUNCTION {sortify} +{ purify$ + "l" change.case$ +} + +INTEGERS { len } + +FUNCTION {chop.word} +{ 's := + 'len := + s #1 len substring$ = + { s len #1 + global.max$ substring$ } + 's + if$ +} + +INTEGERS { et.al.char.used } + +FUNCTION {initialize.et.al.char.used} +{ #0 'et.al.char.used := +} + +EXECUTE {initialize.et.al.char.used} + +FUNCTION {format.lab.names} +{ 's := + s num.names$ 'numnames := + numnames #1 > + { numnames #4 > + { #3 'namesleft := } + { numnames 'namesleft := } + if$ + #1 'nameptr := + "" + { namesleft #0 > } + { nameptr numnames = + { s nameptr "{ff }{vv }{ll}{ jj}" format.name$ "others" = + { "{\etalchar{+}}" * + #1 'et.al.char.used := + } + { s nameptr "{v{}}{l{}}" format.name$ * } + if$ + } + { s nameptr "{v{}}{l{}}" format.name$ * } + if$ + nameptr #1 + 'nameptr := + namesleft #1 - 'namesleft := + } + while$ + numnames #4 > + { "{\etalchar{+}}" * + #1 'et.al.char.used := + } + 'skip$ + if$ + } + { s #1 "{v{}}{l{}}" format.name$ + duplicate$ text.length$ #2 < + { pop$ s #1 "{ll}" format.name$ #3 text.prefix$ } + 'skip$ + if$ + } + if$ +} + +FUNCTION {author.key.label} +{ author empty$ + { key empty$ + { cite$ #1 #3 substring$ } + { key #3 text.prefix$ } + if$ + } + { author format.lab.names } + if$ +} + +FUNCTION {author.editor.key.label} +{ author empty$ + { editor empty$ + { key empty$ + { cite$ #1 #3 substring$ } + { key #3 text.prefix$ } + if$ + } + { editor format.lab.names } + if$ + } + { author format.lab.names } + if$ +} + +FUNCTION {author.key.organization.label} +{ author empty$ + { key empty$ + { organization empty$ + { cite$ #1 #3 substring$ } + { "The " #4 organization chop.word #3 text.prefix$ } + if$ + } + { key #3 text.prefix$ } + if$ + } + { author format.lab.names } + if$ +} + +FUNCTION {editor.key.organization.label} +{ editor empty$ + { key empty$ + { organization empty$ + { cite$ #1 #3 substring$ } + { "The " #4 organization chop.word #3 text.prefix$ } + if$ + } + { key #3 text.prefix$ } + if$ + } + { editor format.lab.names } + if$ +} + +FUNCTION {calc.label} +{ type$ "book" = + type$ "inbook" = + or + 'author.editor.key.label + { type$ "proceedings" = + 'editor.key.organization.label + { type$ "manual" = + 'author.key.organization.label + 'author.key.label + if$ + } + if$ + } + if$ + duplicate$ + year field.or.null purify$ #-1 #2 substring$ + * + 'label := + year field.or.null purify$ #-1 #4 substring$ + * + sortify 'sort.label := +} + +FUNCTION {sort.format.names} +{ 's := + #1 'nameptr := + "" + s num.names$ 'numnames := + numnames 'namesleft := + { namesleft #0 > } + { nameptr #1 > + { " " * } + 'skip$ + if$ + s nameptr "{vv{ } }{ll{ }}{ ff{ }}{ jj{ }}" format.name$ 't := + nameptr numnames = t "others" = and + { "et al" * } + { t sortify * } + if$ + nameptr #1 + 'nameptr := + namesleft #1 - 'namesleft := + } + while$ +} + +FUNCTION {sort.format.title} +{ 't := + "A " #2 + "An " #3 + "The " #4 t chop.word + chop.word + chop.word + sortify + #1 global.max$ substring$ +} + +FUNCTION {author.sort} +{ author empty$ + { key empty$ + { "to sort, need author or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { author sort.format.names } + if$ +} + +FUNCTION {author.editor.sort} +{ author empty$ + { editor empty$ + { key empty$ + { "to sort, need author, editor, or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { editor sort.format.names } + if$ + } + { author sort.format.names } + if$ +} + +FUNCTION {author.organization.sort} +{ author empty$ + { organization empty$ + { key empty$ + { "to sort, need author, organization, or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { "The " #4 organization chop.word sortify } + if$ + } + { author sort.format.names } + if$ +} + +FUNCTION {editor.organization.sort} +{ editor empty$ + { organization empty$ + { key empty$ + { "to sort, need editor, organization, or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { "The " #4 organization chop.word sortify } + if$ + } + { editor sort.format.names } + if$ +} + +FUNCTION {presort} +{ calc.label + sort.label + " " + * + type$ "book" = + type$ "inbook" = + or + 'author.editor.sort + { type$ "proceedings" = + 'editor.organization.sort + { type$ "manual" = + 'author.organization.sort + 'author.sort + if$ + } + if$ + } + if$ + * + " " + * + year field.or.null sortify + * + " " + * + title field.or.null + sort.format.title + * +% DFK throw away stuff above and use cite$ for sort key + pop$ + cite$ + #1 entry.max$ substring$ + 'sort.key$ := +} + +ITERATE {presort} + +SORT + +STRINGS { longest.label last.sort.label next.extra } + +INTEGERS { longest.label.width last.extra.num } + +FUNCTION {initialize.longest.label} +{ "" 'longest.label := + #0 int.to.chr$ 'last.sort.label := + "" 'next.extra := + #0 'longest.label.width := + #0 'last.extra.num := +} + +FUNCTION {forward.pass} +{ last.sort.label sort.label = + { last.extra.num #1 + 'last.extra.num := + last.extra.num int.to.chr$ 'extra.label := + } + { "a" chr.to.int$ 'last.extra.num := + "" 'extra.label := + sort.label 'last.sort.label := + } + if$ +} + +FUNCTION {reverse.pass} +{ next.extra "b" = + { "a" 'extra.label := } + 'skip$ + if$ + label extra.label * 'label := + label width$ longest.label.width > + { label 'longest.label := + label width$ 'longest.label.width := + } + 'skip$ + if$ + extra.label 'next.extra := +} + +EXECUTE {initialize.longest.label} + +ITERATE {forward.pass} + +REVERSE {reverse.pass} + +FUNCTION {begin.bib} +{ et.al.char.used + { "\newcommand{\etalchar}[1]{$^{#1}$}" write$ newline$ } + 'skip$ + if$ + preamble$ empty$ + 'skip$ + { preamble$ write$ newline$ } + if$ + "\begin{thebibliography}{" longest.label * "}" * write$ newline$ +} + +EXECUTE {begin.bib} + +EXECUTE {init.state.consts} + +ITERATE {call.type$} + +FUNCTION {end.bib} +{ newline$ + "\end{thebibliography}" write$ newline$ +} + +EXECUTE {end.bib} diff --git a/papers/wg21-latex/back.tex b/papers/wg21-latex/back.tex new file mode 100644 index 0000000..da31760 --- /dev/null +++ b/papers/wg21-latex/back.tex @@ -0,0 +1,171 @@ +%!TEX root = std.tex + +\renewcommand{\leftmark}{\bibname} + +\begin{thebibliography}{99} +% ISO documents in numerical order. +\bibitem{iso4217} + ISO 4217:2015, + \doccite{Codes for the representation of currencies} +\bibitem{iso10967-1} + ISO/IEC 10967-1:2012, + \doccite{Information technology --- Language independent arithmetic --- + Part 1: Integer and floating point arithmetic} +\bibitem{iso14882:2023} + ISO/IEC 14882:2023, + \doccite{Programming Languages --- \Cpp{}} +\bibitem{iso14882:2020} + ISO/IEC 14882:2020, + \doccite{Programming Languages --- \Cpp{}} +\bibitem{iso14882:2017} + ISO/IEC 14882:2017, + \doccite{Programming Languages --- \Cpp{}} +\bibitem{iso14882:2014} + ISO/IEC 14882:2014, + \doccite{Information technology --- Programming Languages --- \Cpp{}} +\bibitem{iso14882:2011} + ISO/IEC 14882:2011, + \doccite{Information technology --- Programming Languages --- \Cpp{}} +\bibitem{iso14882:2003} + ISO/IEC 14882:2003, + \doccite{Programming Languages --- \Cpp{}} +\bibitem{iso18661-3} + ISO/IEC TS 18661-3:2015, + \doccite{Information Technology --- + Programming languages, their environments, and system software interfaces --- + Floating-point extensions for C --- Part 3: Interchange and extended types} +% Other international standards. +\bibitem{iana-charset} + IANA Character Sets Database. + Available from:\newline + \url{https://www.iana.org/assignments/character-sets/}, 2021-04-01 +\bibitem{iana-tz} + IANA Time Zone Database. + Available from: \url{https://www.iana.org/time-zones} +\bibitem{unicode-charmap} + Unicode Character Mapping Markup Language [online]. + Edited by Mark Davis and Markus Scherer. Revision 5.0.1; 2017-05-31 + Available from: \url{https://www.unicode.org/reports/tr22/tr22-8.html} +% Literature references. +\bibitem{cpp-r} + Bjarne Stroustrup, + \doccite{The \Cpp{} Programming Language, second edition}, Chapter R\@. + Addison-Wesley Publishing Company, ISBN 0-201-53992-6, copyright \copyright 1991 AT\&T +\bibitem{kr} + Brian W.\ Kernighan and Dennis M.\ Ritchie, + \doccite{The C Programming Language}, Appendix A\@. + Prentice-Hall, 1978, ISBN 0-13-110163-3, copyright \copyright 1978 AT\&T +\bibitem{cpp-lib} + P.\,J.\ Plauger, + \doccite{The Draft Standard \Cpp{} Library}. + Prentice-Hall, ISBN 0-13-117003-1, copyright \copyright 1995 P.\,J.\ Plauger +\bibitem{linalg-stable} + J.\ Demmel, I.\ Dumitriu, and O.\ Holtz, + \doccite{Fast linear algebra is stable}, + Numerische Mathematik 108 (59--91), 2007. +\bibitem{blas1} + C.\,L.\ Lawson, R.\,J.\ Hanson, D.\ Kincaid, and F.\,T.\ Krogh, + \doccite{Basic linear algebra subprograms for Fortran usage}. + ACM Trans.\ Math.\ Soft., Vol.\ 5, pp.\ 308--323, 1979. +\bibitem{blas2} + Jack J.\ Dongarra, Jeremy Du Croz, Sven Hammarling, and Richard J.\ Hanson, + \doccite{An Extended Set of FORTRAN Basic Linear Algebra Subprograms}. + ACM Trans.\ Math.\ Soft., Vol.\ 14, No.\ 1, pp.\ 1--17, Mar.\ 1988. +\bibitem{blas3} + Jack J.\ Dongarra, Jeremy Du Croz, Sven Hammarling, and Iain Duff, + \doccite{A Set of Level 3 Basic Linear Algebra Subprograms}. + ACM Trans.\ Math.\ Soft., Vol.\ 16, No.\ 1, pp.\ 1--17, Mar.\ 1990. +\bibitem{lapack} + E.\ Anderson, Z.\ Bai, C.\ Bischof, S.\ Blackford, J.\ Demmel, J.\ Dongarra, + J.\ Du Croz, A.\ Greenbaum, S.\ Hammarling, A.\ McKenney, and D.\ Sorensen, + \doccite{LAPACK Users' Guide, Third Edition}. + SIAM, Philadelphia, PA, USA, 1999. +\bibitem{blas-std} + L.\ Susan Blackford, James Demmel, Jack Dongarra, Iain Duff, Sven Hammarling, + Greg Henry, Michael Heroux, Linda Kaufman, Andrew Lumbsdaine, Antoine Petitet, + Roldan Pozo, Karin Remington, and R.\ Clint Whaley, + \doccite{An Updated Set of Basic Linear Algebra Subprograms (BLAS)}. + ACM Trans.\ Math.\ Soft., Vol.\ 28, Issue 2, pp.\ 135--151, 2002. +\bibitem{flynn-taxonomy} + Michael J.\ Flynn, + \doccite{Very High-Speed Computing Systems}. + Proceedings of the IEEE, Vol.\ 54, Issue 12, pp.\ 1901--1909, 1966. +\end{thebibliography} + +% FIXME: For unknown reasons, hanging paragraphs are not indented within our +% glossaries by default. +\let\realglossitem\glossitem +\renewcommand{\glossitem}[4]{\hangpara{4em}{1}\realglossitem{#1}{#2}{#3}{#4}} + +\clearpage +\renewcommand{\glossaryname}{Cross-references} +\renewcommand{\preglossaryhook}{Each clause and subclause label is listed below along with the +corresponding clause or subclause number and page number, in alphabetical order by label.\\} +\twocolglossary +\renewcommand{\leftmark}{\glossaryname} +{ +\raggedright +\printglossary[xrefindex] +} + +\clearpage +\input{xrefdelta} +\renewcommand{\glossaryname}{Cross-references from ISO \CppXVII{}} +\renewcommand{\preglossaryhook}{All clause and subclause labels from +ISO \CppXVII{} (ISO/IEC 14882:2017, \doccite{Programming Languages --- \Cpp{}}) +are present in this document, with the exceptions described below.\\} +\renewcommand{\leftmark}{\glossaryname} +{ +\raggedright +\printglossary[xrefdelta] +} + +\clearpage +\renewcommand{\leftmark}{\indexname} +\renewcommand{\preindexhook}{Constructions whose name appears in \exposid{monospaced italics} are for exposition only.\\} +{ +\raggedright +\printindex[generalindex] +} + +\clearpage +\renewcommand{\preindexhook}{The first bold page number for each entry is the page in the +general text where the grammar production is defined. The second bold page number is the +corresponding page in the Grammar summary\iref{gram}. Other page numbers refer to pages where the grammar production is mentioned in the general text.\\} +{ +\raggedright +\printindex[grammarindex] +} + +\clearpage +\renewcommand{\preindexhook}{The bold page number for each entry refers to +the page where the synopsis of the header is shown.\\} +{ +\raggedright +\printindex[headerindex] +} + +\clearpage +\renewcommand{\preindexhook}{Constructions whose name appears in \exposid{italics} are for exposition only.\\} +{ +\raggedright +\printindex[libraryindex] +} + +\clearpage +\renewcommand{\preindexhook}{The bold page number for each entry is the page +where the concept is defined. +Other page numbers refer to pages where the concept is mentioned in the general text. +Concepts whose name appears in \exposid{italics} are for exposition only.\\} +{ +\raggedright +\printindex[conceptindex] +} + +\clearpage +\renewcommand{\preindexhook}{The entries in this index are rough descriptions; exact +specifications are at the indicated page in the general text.\\} +{ +\raggedright +\printindex[impldefindex] +} diff --git a/papers/wg21-latex/common.tex b/papers/wg21-latex/common.tex new file mode 100644 index 0000000..740d5e0 --- /dev/null +++ b/papers/wg21-latex/common.tex @@ -0,0 +1,103 @@ +%% Common header for WG21 proposals: mainly taken from C++ standard draft source +%% + +%%-------------------------------------------------- +%% basics + +\usepackage[american] + {babel} % needed for iso dates +\usepackage[iso,american] + {isodate} % use iso format for dates +\usepackage[final] + {listings} % code listings +\usepackage{longtable} % auto-breaking tables +\usepackage{ltcaption} % fix captions for long tables +\usepackage{caption} % caption style +\usepackage{relsize} % provide relative font size changes +\usepackage{textcomp} % provide \text{l,r}angle +\usepackage{underscore} % remove special status of '_' in ordinary text +\usepackage{parskip} % handle non-indented paragraphs "properly" +\usepackage{array} % new column definitions for tables +\usepackage[normalem]{ulem} +\usepackage{enumitem} +\usepackage{color} % define colors for strikeouts and underlines +\usepackage{amsmath} % additional math symbols +\usepackage{mathrsfs} % mathscr font +\usepackage{bm} +\usepackage[final]{microtype} +\usepackage[splitindex,original]{imakeidx} +\usepackage{multicol} +\usepackage{lmodern} +\usepackage{xcolor} +\usepackage[T1]{fontenc} +\usepackage[luatex, final]{graphicx} +\usepackage[luatex, pdfusetitle]{hyperref} +\hypersetup{pdfcreator={WG21 Latex by Steve Downey}, + bookmarksnumbered=true, + pdfpagemode=UseOutlines, + pdfstartview=FitH, + linktocpage=true, + colorlinks=true, + linkcolor=blue, + citecolor=blue, + urlcolor=blue, % ISO/IEC Directives, Part 2, section 6.5 + plainpages=false} +\usepackage{memhfixc} % fix interactions between hyperref and memoir +\usepackage{environ} +\usepackage{expl3} +\usepackage{xparse} +\usepackage{xstring} +\usepackage{xspace} + +\pdfvariable majorversion 2 +\pdfvariable minorversion 0 +% \pdfminorversion=5 +% \pdfcompresslevel=9 +% \pdfobjcompresslevel=2 + +\renewcommand\RSsmallest{5.5pt} % smallest font size for relsize + +% Begin grammar extraction... +\newwrite\gramout +\immediate\openout\gramout=std-gram.ext + +\input{stdtex/layout} +\input{stdtex/styles} +\input{stdtex/macros} +\input{stdtex/tables} + +\makeindex[name=generalindex,options=-s generalindex.ist,title=Index] +\makeindex[name=grammarindex,title=Index of grammar productions] +\makeindex[name=headerindex,title=Index of library headers] +\makeindex[name=libraryindex,options=-s libraryindex.ist,title=Index of library names] +\makeindex[name=conceptindex,title=Index of library concepts] +\makeindex[name=impldefindex,title=Index of implementation-defined behavior] +\makeglossary[xrefindex] +\makeglossary[xrefdelta] + +%%-------------------------------------------------- +%% fix interaction between hyperref and other +%% commands +\pdfstringdefDisableCommands{\def\smaller#1{#1}} +\pdfstringdefDisableCommands{\def\textbf#1{#1}} +\pdfstringdefDisableCommands{\def\raisebox#1{}} +\pdfstringdefDisableCommands{\def\hspace#1{}} +\pdfstringdefDisableCommands{\def\frenchspacing{}} +\pdfstringdefDisableCommands{\def\@{}} + +%%-------------------------------------------------- +%% add special hyphenation rules +\hyphenation{tem-plate ex-am-ple in-put-it-er-a-tor name-space name-spaces non-zero cus-tom-i-za-tion im-ple-men-ted} + +%%-------------------------------------------------- +%% turn off all ligatures inside \texttt +\DisableLigatures{encoding = T1, family = tt*} + +%%-------------------------------------------------- +%% select regular text font for \url +\urlstyle{same} + + +%%-------------------------------------------------- +%% Extra macros for writing papers, like Wording +\input{stdtex/paper_macros} diff --git a/papers/wg21-latex/config.tex b/papers/wg21-latex/config.tex new file mode 100644 index 0000000..ac287f2 --- /dev/null +++ b/papers/wg21-latex/config.tex @@ -0,0 +1,16 @@ +%!TEX root = std.tex +%%-------------------------------------------------- +%% Version numbers +\newcommand{\docno}{DnnnnR0} +\newcommand{\prevdocno}{PnnnnR0} +\newcommand{\cppver}{202002L} + +%% Release date +\newcommand{\reldate}{\today} + +%% Core chapters +\newcommand{\lastcorechapter}{cpp} + +%% Library chapters +\newcommand{\firstlibchapter}{support} +\newcommand{\lastlibchapter}{exec} diff --git a/papers/wg21-latex/example-paper.tex b/papers/wg21-latex/example-paper.tex new file mode 100644 index 0000000..6259f3f --- /dev/null +++ b/papers/wg21-latex/example-paper.tex @@ -0,0 +1,147 @@ +\documentclass[a4paper,10pt,oneside,openany,final,article]{memoir} +\input{common} +\settocdepth{chapter} +\usepackage{minted} +\usepackage{fontspec} +% \setromanfont{Source Serif Pro} +% \setsansfont{Source Sans Pro} +% \setmonofont{Source Code Pro} + +\begin{document} +\title{std::optional} +\author{Steve Downey \small<\href{mailto:sdowney@gmail.com}{sdowney@gmail.com}>} +\date{} %unused. Type date explicitly below. +\maketitle + +\begin{flushright} + \begin{tabular}{ll} + Document \#: & PnnnnRm \\ + Date: & \today \\ + Project: & Programming Language C++ \\ + Audience: & LEWG + \end{tabular} +\end{flushright} + +\begin{abstract} + We propose to fix a hole intentionally left in \tcode{std::optional} --- + + An optional over a reference such that the post condition on assignment is independent of the engaged state, always producing a rebound reference, and assigning a \tcode{U} to a \tcode{T} is disallowed by \tcode{static_assert} if a \tcode{U} can not be bound to a \tcode{T\&}. +\end{abstract} + +\tableofcontents* + +\chapter*{Changes Since Last Version} + +\begin{itemize} +\item \textbf{Changes since R_{n-1}} + \begin{itemize} + \item One. + \end{itemize} +\item \textbf{Changes since R_{n-2}} + \begin{itemize} + \item Tw + \end{itemize} +\end{itemize} + +\chapter{Comparison table} +\section{Compare Some Things} + +This is the convention the ... + +\begin{tabular}{ lr } + \begin{minipage}[t]{0.45\columnwidth} + \begin{minted}[fontsize=\small]{c++} + Cat* cat = find_cat("Fido"); + if (cat!=nullptr) { return doit(*cat); } + + + \end{minted} + \end{minipage} + & + \begin{minipage}[t]{0.45\columnwidth} + \begin{minted}[fontsize=\small]{c++} + std::optional cat = find_cat("Fido"); + return cat.and_then(doit); + + \end{minted} + \end{minipage} +\end{tabular} + + +\chapter{Motivation} +Other than the standard library's implementation of ... + +\chapter{Design} + +The design is straightforward. The \tcode{new_thing} ... + +\section{Relational Operations} + +The definitions of the relational operators ... + +\section{Trivial construction} +Construction of \tcode{optional} should be trivial, because it is straightforward to implement, and \tcode{optional} is trivial. +Boost is not. + +\chapter{Rendering Design In C++} + +While we value performance, we value safety more. + +Errors that can be detected at compile time must be detected at compile time. + +New Thing must never construct a temporary, or knowingly take the address of an temporary or part of an temporary. + +It is always presumed safe to copy the pointer value from New Thing, since by induction, it is not intrinsically dangling. + +\chapter{Proposal} + +Add a New Thing which solves all the problems. + +\chapter{Wording} + +The proposed changes are relative to the current working draft \cite{N5001}. + + +\begin{wording} + +\include{new-wording} + +\end{wording} + +\chapter{Impact on the standard} + +A pure library extension, affecting no other parts of the library or language. + +\chapter{Acknowledgments} +Many thanks to all of the reviewers and authors + +\chapter*{Document history} + +\begin{itemize} +\item \textbf{Changes since R4} + \begin{itemize} + \item feature test macro + \end{itemize} +\item \textbf{Changes since R3} + \begin{itemize} + \item discussion + \end{itemize} +\item \textbf{Changes since R1} + \begin{itemize} + \item Design points called out + \end{itemize} +\item \textbf{Changes since R0} + \begin{itemize} + \item Wording Updates + \end{itemize} +\end{itemize} + +\renewcommand{\bibname}{References} +\bibliographystyle{abstract} +\bibliography{wg21,mybiblio} + +\backmatter +\chapter*{Implementation} + +\inputminted{c++}{implementation.hpp} +\end{document} diff --git a/papers/wg21-latex/implementation.hpp b/papers/wg21-latex/implementation.hpp new file mode 100644 index 0000000..1d6aad4 --- /dev/null +++ b/papers/wg21-latex/implementation.hpp @@ -0,0 +1,382 @@ +// ---------------------- +// BASE AND DETAILS ELIDED +// ---------------------- + +/****************/ +/* optional */ +/****************/ + +template +class optional { + public: + using value_type = T; + using iterator = + std::contiguous_iterator; // see [optionalref.iterators] + public: + // \ref{optionalref.ctor}, constructors + + constexpr optional() noexcept = default; + constexpr optional(nullopt_t) noexcept : optional() {} + constexpr optional(const optional& rhs) noexcept = default; + + template + requires(std::is_constructible_v && + !std::reference_constructs_from_temporary_v) + constexpr explicit optional(in_place_t, Arg&& arg); + + template + requires(std::is_constructible_v && + !(std::is_same_v, in_place_t>) && + !(std::is_same_v, optional>) && + !std::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) + optional(U&& u) noexcept(std::is_nothrow_constructible_v) { + convert_ref_init_val(u); + } + + template + requires(std::is_constructible_v && + !(std::is_same_v, in_place_t>) && + !(std::is_same_v, optional>) && + std::reference_constructs_from_temporary_v) + constexpr optional(U&& u) = delete; + + // The full set of 4 overloads on optional by value category, doubled to + // 8 by deleting if reference_constructs_from_temporary_v is true. This + // allows correct constraints by propagating the value category from the + // optional to the value within the rhs. + template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + !std::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) optional( + optional& rhs) noexcept(std::is_nothrow_constructible_v); + + template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + !std::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) + optional(const optional& rhs) noexcept( + std::is_nothrow_constructible_v); + + template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + !std::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) + optional(optional&& rhs) noexcept( + noexcept(std::is_nothrow_constructible_v)); + + template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + !std::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) + optional(const optional&& rhs) noexcept( + noexcept(std::is_nothrow_constructible_v)); + + template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + std::reference_constructs_from_temporary_v) + constexpr optional(optional& rhs) = delete; + + template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + std::reference_constructs_from_temporary_v) + constexpr optional(const optional& rhs) = delete; + + template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + std::reference_constructs_from_temporary_v) + constexpr optional(optional&& rhs) = delete; + + template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + std::reference_constructs_from_temporary_v) + constexpr optional(const optional&& rhs) = delete; + + // \ref{optionalref.dtor}, destructor + constexpr ~optional() = default; + + // \ref{optionalref.assign}, assignment + constexpr optional& operator=(nullopt_t) noexcept; + + constexpr optional& operator=(const optional& rhs) noexcept = default; + + template + requires(std::is_constructible_v && + !std::reference_constructs_from_temporary_v) + constexpr T& + emplace(U&& u) noexcept(std::is_nothrow_constructible_v); + + // \ref{optionalref.swap}, swap + constexpr void swap(optional& rhs) noexcept; + + // \ref{optional.iterators}, iterator support + constexpr iterator begin() const noexcept; + constexpr iterator end() const noexcept; + + // \ref{optionalref.observe}, observers + constexpr T* operator->() const noexcept; + constexpr T& operator*() const noexcept; + constexpr explicit operator bool() const noexcept; + constexpr bool has_value() const noexcept; + constexpr T& value() const; + template > + constexpr std::remove_cv_t value_or(U&& u) const; + + // \ref{optionalref.monadic}, monadic operations + template + constexpr auto and_then(F&& f) const; + template + constexpr optional> transform(F&& f) const; + template + constexpr optional or_else(F&& f) const; + + // \ref{optional.mod}, modifiers + constexpr void reset() noexcept; + + private: + T* value_ = nullptr; // exposition only + + // \ref{optionalref.expos}, exposition only helper functions + template + constexpr void convert_ref_init_val(U&& u) { + // Creates a variable, \tcode{r}, + // as if by \tcode{T\& r(std::forward(u));} + // and then initializes \exposid{val} with \tcode{addressof(r)} + T& r(std::forward(u)); + value_ = std::addressof(r); + } +}; + +// \rSec3[optionalref.ctor]{Constructors} +template +template + requires(std::is_constructible_v && + !std::reference_constructs_from_temporary_v) +constexpr optional::optional(in_place_t, Arg&& arg) { + convert_ref_init_val(std::forward(arg)); +} + +// Clang is unhappy with the out-of-line definition +// +// template +// template +// requires(std::is_constructible_v && +// !(is_same_v, in_place_t>) && +// !(is_same_v, optional>) && +// !std::reference_constructs_from_temporary_v) +// constexpr optional::optional(U&& u) +// noexcept(is_nothrow_constructible_v) +// : value_(std::addressof(static_cast(std::forward(u)))) {} + +template +template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + !std::reference_constructs_from_temporary_v) +constexpr optional::optional(optional& rhs) noexcept( + std::is_nothrow_constructible_v) { + if (rhs.has_value()) { + convert_ref_init_val(*rhs); + } +} + +template +template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + !std::reference_constructs_from_temporary_v) +constexpr optional::optional(const optional& rhs) noexcept( + std::is_nothrow_constructible_v) { + if (rhs.has_value()) { + convert_ref_init_val(*rhs); + } +} + +template +template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + !std::reference_constructs_from_temporary_v) +constexpr optional::optional(optional&& rhs) noexcept( + noexcept(std::is_nothrow_constructible_v)) { + if (rhs.has_value()) { + convert_ref_init_val(*std::move(rhs)); + } +} + +template +template + requires(std::is_constructible_v && + !std::is_same_v, optional> && + !std::is_same_v && + !std::reference_constructs_from_temporary_v) +constexpr optional::optional(const optional&& rhs) noexcept( + noexcept(std::is_nothrow_constructible_v)) { + if (rhs.has_value()) { + convert_ref_init_val(*std::move(rhs)); + } +} + +// \rSec3[optionalref.assign]{Assignment} +template +constexpr optional& optional::operator=(nullopt_t) noexcept { + value_ = nullptr; + return *this; +} + +template +template + requires(std::is_constructible_v && + !std::reference_constructs_from_temporary_v) +constexpr T& +optional::emplace(U&& u) noexcept(std::is_nothrow_constructible_v) { + convert_ref_init_val(std::forward(u)); + return *value_; +} + +// \rSec3[optionalref.swap]{Swap} + +template +constexpr void optional::swap(optional& rhs) noexcept { + std::swap(value_, rhs.value_); +} + +// \rSec3[optionalref.iterators]{Iterator Support} + +template +constexpr optional::iterator optional::begin() const noexcept { + return iterator(has_value() ? value_ : nullptr); +}; + +template +constexpr optional::iterator optional::end() const noexcept { + return begin() + has_value(); +} + +// \rSec3[optionalref.observe]{Observers} +template +constexpr T* optional::operator->() const noexcept { + return value_; +} + +template +constexpr T& optional::operator*() const noexcept { + return *value_; +} + +template +constexpr optional::operator bool() const noexcept { + return value_ != nullptr; +} +template +constexpr bool optional::has_value() const noexcept { + return value_ != nullptr; +} + +template +constexpr T& optional::value() const { + return has_value() ? *value_ : throw bad_optional_access(); +} + +template +template +constexpr std::remove_cv_t optional::value_or(U&& u) const { + static_assert(std::is_constructible_v, T&>, + "T must be constructible from a T&"); + static_assert(std::is_convertible_v>, + "Must be able to convert u to T"); + return has_value() ? *value_ + : static_cast>(std::forward(u)); +} + +// \rSec3[optionalref.monadic]{Monadic operations} +template +template +constexpr auto optional::and_then(F&& f) const { + using U = std::invoke_result_t; + static_assert(detail::is_optional, "F must return an optional"); + if (has_value()) { + return std::invoke(std::forward(f), *value_); + } else { + return std::remove_cvref_t(); + } +} + +template +template +constexpr optional> +optional::transform(F&& f) const { + using U = std::invoke_result_t; + static_assert(!std::is_same_v, in_place_t>, + "Result must not be in_place_t"); + static_assert(!std::is_same_v, nullopt_t>, + "Result must not be nullopt_t"); + static_assert((std::is_object_v && !std::is_array_v) || + std::is_lvalue_reference_v, + "Result must be an non-array object or an lvalue reference"); + if (has_value()) { + return optional{std::invoke(std::forward(f), *value_)}; + } else { + return optional{}; + } +} + +template +template +constexpr optional optional::or_else(F&& f) const { + using U = std::invoke_result_t; + static_assert(std::is_same_v, optional>, + "Result must be an optional"); + if (has_value()) { + return *value_; + } else { + return std::forward(f)(); + } +} + +// \rSec3[optional.mod]{modifiers} +template +constexpr void optional::reset() noexcept { + value_ = nullptr; +} +} // namespace beman::optional + +namespace std { +template + requires requires(T a) { + { + std::hash>{}(a) + } -> std::convertible_to; + } +struct hash> { + static_assert(!is_reference_v, + "hash is not enabled for reference types"); + size_t operator()(const beman::optional::optional& o) const + noexcept(noexcept(hash>{}(*o))) { + if (o) { + return std::hash>{}(*o); + } else { + return 0; + } + } +}; diff --git a/papers/wg21-latex/intro.tex b/papers/wg21-latex/intro.tex new file mode 100644 index 0000000..5cb98d2 --- /dev/null +++ b/papers/wg21-latex/intro.tex @@ -0,0 +1,1093 @@ +%!TEX root = std.tex + +\clearpage +\bigskip\noindent\textlarger{\textbf{Programming languages --- \Cpp{}}} +\bigskip\bigskip + +\begingroup +\let\clearpage\relax +\rSec0[intro.scope]{Scope} +\endgroup +\copypagestyle{cpppageone}{cpppage} +\makeoddhead{cpppageone}{\textbf{WORKING DRAFT}}{}{\leaders\hrule height 2pt\hfill\kern0pt\\\textbf{\docno}} +\makeheadrule{cpppageone}{\textwidth}{2pt} +\thispagestyle{cpppageone} + +\pnum +\indextext{scope|(}% +This document specifies requirements for implementations +of \Cpp{}, which is a general-purpose programming language. +The first such requirement is that +an implementation implements the language, so this document also +defines \Cpp{}. Other requirements and relaxations of the first +requirement appear at various places within this document. +\indextext{scope|)} + +\rSec0[intro.refs]{Normative references}% +\indextext{normative references|see{references, normative}}% + +\pnum +\indextext{references!normative|(}% +The following documents are referred to in the text +in such a way that some or all of their content +constitutes requirements +of this document. For dated references, only the edition cited applies. +For undated references, the latest edition of the referenced document +(including any amendments) applies. +\begin{itemize} +% ISO documents in numerical order. +\item ISO/IEC 2382, \doccite{Information technology --- Vocabulary} +\item ISO 8601-1:2019, \doccite{Date and time --- Representations for information interchange --- Part 1: Basic rules} +\item \IsoC{}, \doccite{Information technology --- Programming languages --- C} +\item \IsoPosix{}, \doccite{Information Technology --- Portable Operating System Interface (POSIX\textregistered)\begin{footnote} +POSIX\textregistered\ is a registered trademark of +the Institute of Electrical and Electronic Engineers, Inc. +This information is given for the convenience of users of this document and +does not constitute an endorsement by ISO or IEC of this product. +\end{footnote} +Base Specifications, Issue 7} +\item \IsoPosix{}/Cor 1:2013, \doccite{Information Technology --- Portable Operating System Interface +(POSIX\textregistered) Base Specifications, Issue 7 --- Technical Corrigendum 1} +\item \IsoPosix{}/Cor 2:2017, \doccite{Information Technology --- Portable Operating System Interface +(POSIX\textregistered) Base Specifications, Issue 7 --- Technical Corrigendum 2} +\item \IsoFloatUndated{}:2020, \doccite{Information technology --- Microprocessor Systems --- Floating-Point arithmetic} +\item ISO 80000-2:2019, \doccite{Quantities and units --- Part 2: Mathematics} +% Other international standards. +\item Ecma International, \doccite{ECMAScript +\begin{footnote} +ECMAScript\textregistered\ is a registered trademark of Ecma +International. +This information is given for the convenience of users of this document and +does not constitute an endorsement by ISO or IEC of this product. +\end{footnote} +Language Specification}, +Standard Ecma-262, third edition, 1999. +\item +The Unicode Consortium. \doccite{The Unicode Standard}, Version 15.1. +Available from: \url{https://www.unicode.org/versions/Unicode15.1.0/} +\end{itemize} +\indextext{references!normative|)} + +\rSec0[intro.defs]{Terms and definitions} + +\pnum +\indextext{definitions|(}% +For the purposes of this document, +the terms and definitions +given in ISO/IEC 2382, ISO 80000-2:2019, +and the following apply. + +\pnum +ISO and IEC maintain terminology databases +for use in standardization +at the following addresses: +\begin{itemize} +\item ISO Online browsing platform: available at \url{https://www.iso.org/obp} +\item IEC Electropedia: available at \url{https://www.electropedia.org/} +\end{itemize} + +\indexdefn{access}% +\definition{access}{defns.access} +\defncontext{execution-time action} +read or modify the value of an object + +\begin{defnote} +Only glvalues of scalar type\iref{basic.types.general} can be used to access objects. +Reads of scalar objects are described in \ref{conv.lval} and +modifications of scalar objects are described in +\ref{expr.assign}, \ref{expr.post.incr}, and \ref{expr.pre.incr}. +Attempts to read or modify an object of class type +typically invoke a constructor\iref{class.ctor} +or assignment operator\iref{class.copy.assign}; +such invocations do not themselves constitute accesses, +although they may involve accesses of scalar subobjects. +\end{defnote} + +\indexdefn{argument}% +\indexdefn{argument!function call expression} +\definition{argument}{defns.argument} +\defncontext{function call expression} +expression or \grammarterm{braced-init-list} +in the comma-separated list bounded by the parentheses + +\indexdefn{argument}% +\indexdefn{argument!function-like macro}% +\definition{argument}{defns.argument.macro} +\defncontext{function-like macro} sequence of preprocessing tokens in the +comma-separated list bounded by the parentheses + +\indexdefn{argument}% +\indexdefn{argument!throw expression}% +\definition{argument}{defns.argument.throw} +\defncontext{throw expression} operand of \keyword{throw} + +\indexdefn{argument}% +\indexdefn{argument!template instantiation}% +\definition{argument}{defns.argument.templ} +\defncontext{template instantiation} +\grammarterm{constant-expression}, +\grammarterm{type-id}, or +\grammarterm{id-expression} in the comma-separated +list bounded by the angle brackets + +\indexdefn{block (execution)}% +\definition{block}{defns.block} +\defncontext{execution} +wait for some condition (other than for the implementation to execute +the execution steps of the thread of execution) to be satisfied before +continuing execution past the blocking operation + +\indexdefn{block (statement)}% +\definition{block}{defns.block.stmt} +\defncontext{statement} +compound statement + +\indexdefn{C!standard library}% +\definition{C standard library}{defns.c.lib} +library described in \IsoC{}, Clause 7 +\begin{defnote} +With the qualifications noted in \ref{\firstlibchapter} +through \ref{\lastlibchapter} and in \ref{diff.library}, +the C standard library is a subset of the \Cpp{} standard library. +\end{defnote} + +\definition{character}{defns.character} +\indexdefn{character}% +\defncontext{library} +object which, +when treated sequentially, +can represent text + +\begin{defnote} +The term does not mean only +\tcode{char}, +\keyword{char8_t}, +\keyword{char16_t}, +\keyword{char32_t}, +and +\keyword{wchar_t} +objects\iref{basic.fundamental}, +but any value that can be represented by a type +that provides the definitions specified in +\ref{strings}, \ref{localization}, \ref{input.output}, or~\ref{re}. +\end{defnote} + +\definition{character container type}{defns.character.container} +\defncontext{library} +\indexdefn{type!character container}% +class or a type used to +represent a \termref{defns.character}{character}{} + +\begin{defnote} +It is used for one of the template parameters of \tcode{char_traits} +and the class templates which use that, +such as the string, iostream, and regular expression class templates. +\end{defnote} + +\definition{collating element}{defns.regex.collating.element} +\indexdefn{collating element}% +sequence of one or more \termref{defns.character}{character}{s} within the +current locale that collate as if they were a single character + +\definition{component}{defns.component} +\defncontext{library} +\indexdefn{component}% +group of library entities directly related as members, \termref{defns.parameter}{parameter}{s}, or +return types + +\begin{defnote} +For example, the class template \tcode{basic_string} +and the non-member function templates +that operate on strings are referred to as the string component. +\end{defnote} + +\indexdefn{behavior!conditionally-supported}% +\definition{conditionally-supported}{defns.cond.supp} +program construct that an implementation is not required to support + +\begin{defnote} +Each implementation documents all conditionally-supported +constructs that it does not support. +\end{defnote} + +\definition{constant evaluation}{defns.const.eval} +\indexdefn{constant evaluation}% +evaluation that is performed as part of evaluating an expression +as a core constant expression\iref{expr.const} + +\definition{constant subexpression}{defns.const.subexpr} +\indexdefn{constant subexpression}% +expression whose evaluation as subexpression of a +\grammarterm{conditional-expression} +\tcode{CE} would not prevent \tcode{CE} +from being a core constant expression + +\definition{deadlock}{defns.deadlock} +\defncontext{library} +\indexdefn{deadlock}% +situation wherein +one or more threads are unable to continue execution because each is +\termref{defns.block}{block}{ed} waiting for one or more of the others to satisfy some condition + +\definition{default behavior}{defns.default.behavior.impl} +\indexdefn{behavior!default}% +\defncontext{library implementation} +specific behavior provided by the implementation, +within the scope of the \termref{defns.required.behavior}{required behavior}{} + +\indexdefn{message!diagnostic}% +\definition{diagnostic message}{defns.diagnostic} +message belonging to an \impldef{diagnostic message} subset of the +implementation's output messages + +\indexdefn{type!dynamic}% +\definition{dynamic type}{defns.dynamic.type} +\defncontext{glvalue} type of the most derived object to which the +glvalue refers + +\begin{example} +If a pointer\iref{dcl.ptr} \tcode{p} whose type is ``pointer to +class \tcode{B}'' is pointing to a base class subobject +of class \tcode{B}, whose most derived object is of class \tcode{D}, derived +from \tcode{B}\iref{class.derived}, the dynamic type of the +expression \tcode{*p} is ``\tcode{D}''. References\iref{dcl.ref} are +treated similarly. +\end{example} + +\indexdefn{type!dynamic}% +\definition{dynamic type}{defns.dynamic.type.prvalue} +\defncontext{prvalue} \termref{defns.static.type}{static type}{} of the prvalue expression + +\indexdefn{behavior!erroneous}% +\definition{erroneous behavior}{defns.erroneous} +well-defined behavior that the implementation is recommended to diagnose +\begin{defnote} +Erroneous behavior is always the consequence of incorrect program code. +Implementations are allowed, but not required, +to diagnose it\iref{intro.compliance.general}. +Evaluation of a constant expression\iref{expr.const} +never exhibits behavior specified as erroneous in \ref{intro} through \ref{\lastcorechapter}. +\end{defnote} + +\definition{expression-equivalent}{defns.expression.equivalent} +\defncontext{library} +\indexdefn{expression-equivalent}% +expressions that all have the same effects, +either +are all potentially-throwing or +are all not potentially-throwing, +and +either +are all \termref{defns.const.subexpr}{constant subexpression}{s} or +are all not constant subexpressions + +\begin{example} +For a value \tcode{x} of type \tcode{int} +and a function \tcode{f} that accepts integer arguments, +the expressions +\tcode{f(x + 2)}, +\tcode{f(2 + x)}, +and +\tcode{f(1 + x + 1)} +are expression-equivalent. +\end{example} + +\definition{finite state machine}{defns.regex.finite.state.machine} +\defncontext{regular expression} +\indexdefn{finite state machine}% +unspecified data structure that is used to +represent a \termref{defns.regex.regular.expression}{regular expression}{}, and which permits efficient matches +against the regular expression to be obtained + +\definition{format specifier}{defns.regex.format.specifier} +\defncontext{regular expression} +\indexdefn{format specifier}% +sequence of one or more \termref{defns.character}{character}{s} that is expected to be +replaced with some part of a \termref{defns.regex.regular.expression}{regular expression}{} match + +\definition{handler function}{defns.handler} +\defncontext{library} +\indexdefn{function!handler}% +non-reserved function whose definition may be provided by a \Cpp{} program + +\begin{defnote} +A \Cpp{} program may designate a handler function at various points in its execution by +supplying a pointer to the function when calling any of the library functions that install +handler functions (see \ref{support}). +\end{defnote} + +\indexdefn{program!ill-formed}% +\definition{ill-formed program}{defns.ill.formed} +program that is not well-formed\iref{defns.well.formed} + +\indexdefn{behavior!implementation-defined}% +\definition{implementation-defined behavior}{defns.impl.defined} +behavior, for a \termref{defns.well.formed}{well-formed program}{} construct and correct data, that +depends on the implementation and that each implementation documents + +\definition{implementation-defined strict total order over pointers} +{defns.order.ptr} +\indexdefn{pointer!strict total order}% +\defncontext{library} +\impldef{strict total order over pointer values} +strict total ordering over all pointer values +such that the ordering is consistent with the partial order +imposed by the built-in operators +\tcode{<}, \tcode{>}, \tcode{<=}, \tcode{>=}, and \tcode{<=>} + +\indexdefn{limits!implementation}% +\definition{implementation limit}{defns.impl.limits} +restriction imposed upon programs by the implementation + +\indexdefn{behavior!locale-specific}% +\definition{locale-specific behavior}{defns.locale.specific} +behavior that depends on local conventions of nationality, culture, and +language that each implementation documents + +\definition{matched}{defns.regex.matched} +\defncontext{regular expression} +\indexdefn{matched}% +\indexdefn{regular expression!matched}% +condition when a sequence of zero or more \termref{defns.character}{character}{s} +correspond to a sequence of characters defined by the pattern + +\definition{modifier function}{defns.modifier} +\defncontext{library} +\indexdefn{function!modifier}% +class member function other than a constructor, +assignment operator, or destructor +that alters the state of an object of the class + +\definition{move assignment}{defns.move.assign} +\defncontext{library} +\indexdefn{assignment!move}% +assignment of an rvalue of some object type to a modifiable lvalue of the same type + +\definition{move construction}{defns.move.constr} +\defncontext{library} +\indexdefn{construction!move}% +direct-initialization of an object of some type with an rvalue of the same type + +\indexdefn{library call!non-constant}% +\definition{non-constant library call}{defns.nonconst.libcall} +invocation of a library function that, +as part of evaluating any expression \tcode{E}, +prevents \tcode{E} from being a core constant expression + +\definition{NTCTS}{defns.ntcts} +\defncontext{library} +\indexdefn{NTCTS}% +\indexdefn{string!null-terminated character type}% +sequence of values that have +\termref{defns.character}{character}{} type +that precede the terminating null character type +value +\tcode{charT()} + +\definition{observer function}{defns.observer} +\defncontext{library} +\indexdefn{function!observer}% +class member function that accesses the state of an object of the class +but does not alter that state + +\begin{defnote} +Observer functions are specified as +\keyword{const} +member functions. +\end{defnote} + +\indexdefn{parameter}% +\indexdefn{parameter!function}% +\indexdefn{parameter!catch clause}% +\definition{parameter}{defns.parameter} +\defncontext{function or catch clause} object or reference declared as part of a function declaration or +definition or in the catch clause of an exception handler that +acquires a value on entry to the function or handler + +\indexdefn{parameter}% +\indexdefn{parameter!function-like macro}% +\definition{parameter}{defns.parameter.macro} +\defncontext{function-like macro} identifier from +the comma-separated list bounded by the parentheses immediately +following the macro name + +\indexdefn{parameter}% +\indexdefn{parameter!template}% +\definition{parameter}{defns.parameter.templ} +\defncontext{template} member of a \grammarterm{template-parameter-list} + +\definition{primary equivalence class}{defns.regex.primary.equivalence.class} +\defncontext{regular expression} +\indexdefn{primary equivalence class}% +set of one or more \termref{defns.character}{character}{s} which +share the same primary sort key: that is the sort key weighting that +depends only upon character shape, and not accents, case, or +locale-specific tailorings + +\definition{program-defined specialization}{defns.prog.def.spec} +\defncontext{library} +\indexdefn{specialization!program-defined}% +explicit template specialization or partial specialization +that is not part of the \Cpp{} standard library and +not defined by the implementation + +\definition{program-defined type}{defns.prog.def.type} +\defncontext{library} +\indexdefn{type!program-defined}% +non-closure class type or enumeration type +that is not part of the \Cpp{} standard library and +not defined by the implementation, +or a closure type of a non-implementation-provided lambda expression, +or an instantiation of a \termref{defns.prog.def.spec}{program-defined specialization}{} + +\begin{defnote} +Types defined by the implementation include +extensions\iref{intro.compliance} and internal types used by the library. +\end{defnote} + +\definition{projection}{defns.projection} +\indexdefn{projection}% +\defncontext{library} +transformation that an algorithm applies +before inspecting the values of elements + +\begin{example} +\begin{codeblock} +std::pair pairs[] = {{2, "foo"}, {1, "bar"}, {0, "baz"}}; +std::ranges::sort(pairs, std::ranges::less{}, [](auto const& p) { return p.first; }); +\end{codeblock} +sorts the pairs in increasing order of their \tcode{first} members: +\begin{codeblock} +{{0, "baz"}, {1, "bar"}, {2, "foo"}} +\end{codeblock} +\end{example} + +\definition{referenceable type}{defns.referenceable} +\indexdefn{type!referenceable}% +type that is either an +object type, a function type that does not have cv-qualifiers or a +\grammarterm{ref-qualifier}, or a reference type + +\begin{defnote} +The term describes a type to which a reference can be created, +including reference types. +\end{defnote} + +\definition{regular expression}{defns.regex.regular.expression} +pattern that selects specific strings +from a set of \termref{defns.character}{character}{} strings + +\definition{replacement function}{defns.replacement} +\defncontext{library} +\indexdefn{function!replacement}% +non-reserved function +whose definition is provided by a \Cpp{} program + +\begin{defnote} +Only one definition for such a function is in effect for the duration of the program's +execution, as the result of creating the program\iref{lex.phases} and resolving the +definitions of all translation units\iref{basic.link}. +\end{defnote} + +\definition{required behavior}{defns.required.behavior} +\defncontext{library} +\indexdefn{behavior!required}% +description of \termref{defns.replacement}{replacement function}{} +and \termref{defns.handler}{handler function}{} semantics +applicable to both the behavior provided by the implementation and +the behavior of any such function definition in the program + +\begin{defnote} +If such a function defined in a \Cpp{} program fails to meet the required +behavior when it executes, the behavior is undefined.% +\indextext{undefined} +\end{defnote} + +\definition{reserved function}{defns.reserved.function} +\defncontext{library} +\indexdefn{function!reserved}% +function, specified as part of the \Cpp{} standard library, that is defined by the +implementation + +\begin{defnote} +If a \Cpp{} program provides a definition for any reserved function, the results are undefined.% +\indextext{undefined} +\end{defnote} + +\definition{runtime-undefined behavior}{defns.undefined.runtime} +\indexdefn{behavior!runtime-undefined}% +behavior that is undefined except when it occurs during constant evaluation + +\begin{defnote} +During constant evaluation, +\begin{itemize} +\item +it is +\impldef{whether runtime-undefined behavior results in the expression being deemed non-constant} +whether runtime-undefined behavior results in the expression being deemed non-constant +(as specified in~\ref{expr.const}) and +\item +runtime-undefined behavior has no other effect. +\end{itemize} +\end{defnote} + +\indexdefn{signature}% +\definition{signature}{defns.signature} +\defncontext{function} +name, +parameter-type-list, +and enclosing namespace + +\begin{defnote} +Signatures are used as a basis for +name mangling and linking. +\end{defnote} + +\indexdefn{signature}% +\definition{signature}{defns.signature.friend} +\defncontext{non-template friend function with trailing \grammarterm{requires-clause}} +name, +parameter-type-list, +enclosing class, +and +trailing \grammarterm{requires-clause} + +\indexdefn{signature}% +\definition{signature}{defns.signature.templ} +\defncontext{function template} +name, +parameter-type-list, +enclosing namespace, +return type, +\termref{defns.signature.template.head}{signature}{} of the \grammarterm{template-head}, +and +trailing \grammarterm{requires-clause} (if any) + +\indexdefn{signature}% +\definition{signature}{defns.signature.templ.friend} +\defncontext{friend function template with constraint involving enclosing template parameters} +name, +parameter-type-list, +return type, +enclosing class, +\termref{defns.signature.template.head}{signature}{} of the \grammarterm{template-head}, +and +trailing \grammarterm{requires-clause} (if any) + +\indexdefn{signature}% +\definition{signature}{defns.signature.spec} +\defncontext{function template specialization} \termref{defns.signature.templ}{signature}{} of the template of which it is a specialization +and its template \termref{defns.argument.templ}{argument}{s} (whether explicitly specified or deduced) + +\indexdefn{signature}% +\definition{signature}{defns.signature.member} +\defncontext{class member function} +name, +parameter-type-list, +class of which the function is a member, +\cv-qualifiers (if any), +\grammarterm{ref-qualifier} (if any), +and +trailing \grammarterm{requires-clause} (if any) + +\indexdefn{signature}% +\definition{signature}{defns.signature.member.templ} +\defncontext{class member function template} +name, +parameter-type-list, +class of which the function is a member, +\cv-qualifiers (if any), +\grammarterm{ref-qualifier} (if any), +return type (if any), +\termref{defns.signature.template.head}{signature}{} of the \grammarterm{template-head}, +and +trailing \grammarterm{requires-clause} (if any) + +\indexdefn{signature}% +\definition{signature}{defns.signature.member.spec} +\defncontext{class member function template specialization} \termref{defns.signature.member.templ}{signature}{} of the member function template +of which it is a specialization and its template arguments (whether explicitly specified or deduced) + +\indexdefn{signature}% +\definition{signature}{defns.signature.template.head} +\defncontext{\grammarterm{template-head}} +template \termref{defns.parameter.templ}{parameter}{} list, +excluding template parameter names and default \termref{defns.argument.templ}{argument}{s}, +and +\grammarterm{requires-clause} (if any) + +\definition{stable algorithm}{defns.stable} +\defncontext{library} +\indexdefn{algorithm!stable}% +\indexdefn{stable algorithm}% +algorithm that preserves, as appropriate to the particular algorithm, the order +of elements + +\begin{defnote} +Requirements for stable algorithms are given in \ref{algorithm.stable}. +\end{defnote} + +\indexdefn{type!static}% +\definition{static type}{defns.static.type} +type of an expression resulting from +analysis of the program without considering execution semantics + +\begin{defnote} +The static type of an expression depends only on the form of the program in +which the expression appears, and does not change while the program is +executing. +\end{defnote} + +\definition{sub-expression}{defns.regex.subexpression} +\defncontext{regular expression} +\indexdefn{sub-expression!regular expression}% +subset of a \termref{defns.regex.regular.expression}{regular expression}{} that has +been marked by parentheses + +\definition{traits class}{defns.traits} +\defncontext{library} +\indexdefn{traits}% +class that encapsulates a set of types and functions necessary for class templates and +function templates to manipulate objects of types for which they are instantiated + +\indexdefn{unblock}% +\definition{unblock}{defns.unblock} +satisfy a condition that one or more \termref{defns.block}{blocked}{} threads of execution are waiting for + +\indexdefn{behavior!undefined}% +\definition{undefined behavior}{defns.undefined} +behavior for which this document +imposes no requirements + +\begin{defnote} +Undefined behavior may be expected when +this document omits any explicit +definition of behavior or when a program uses an incorrect construct or invalid data. +Permissible undefined behavior ranges +from ignoring the situation completely with unpredictable results, to +behaving during translation or program execution in a documented manner +characteristic of the environment (with or without the issuance of a +\termref{defns.diagnostic}{diagnostic message}{}), to terminating a translation or execution (with the +issuance of a diagnostic message). Many incorrect program constructs do +not engender undefined behavior; they are required to be diagnosed. +Evaluation of a constant expression\iref{expr.const} never exhibits behavior explicitly +specified as undefined in \ref{intro} through \ref{\lastcorechapter}. +\end{defnote} + +\indexdefn{behavior!unspecified}% +\definition{unspecified behavior}{defns.unspecified} +behavior, for a \termref{defns.well.formed}{well-formed program}{} construct and correct data, that +depends on the implementation + +\begin{defnote} +The implementation is not required to +document which behavior occurs. The range of +possible behaviors is usually delineated by this document. +\end{defnote} + +\definition{valid but unspecified state}{defns.valid} +\defncontext{library} +\indexdefn{valid but unspecified state}% +value of an object that is not specified except that the object's invariants are +met and operations on the object behave as specified for its type + +\begin{example} +If an object \tcode{x} of type \tcode{std::vector} is in a +valid but unspecified state, \tcode{x.empty()} can be called unconditionally, +and \tcode{x.front()} can be called only if \tcode{x.empty()} returns +\tcode{false}. +\end{example} + +\indexdefn{program!well-formed}% +\definition{well-formed program}{defns.well.formed} +\Cpp{} program constructed according to the syntax and semantic rules +\indextext{definitions|)} + +\rSec0[intro]{General principles} + +\rSec1[intro.compliance]{Implementation compliance}% +\indextext{diagnostic message|see{message, diagnostic}}% +\indexdefn{conditionally-supported behavior|see{behavior, con\-ditionally-supported}}% +\indextext{dynamic type|see{type, dynamic}}% +\indextext{static type|see{type, static}}% +\indextext{ill-formed program|see{program, ill-formed}}% +\indextext{well-formed program|see{program, well-formed}}% +\indextext{implementation-defined behavior|see{behavior, im\-plementation-defined}}% +\indextext{undefined behavior|see{behavior, undefined}}% +\indextext{unspecified behavior|see{behavior, unspecified}}% +\indextext{implementation limits|see{limits, implementation}}% +\indextext{locale-specific behavior|see{behavior, locale-spe\-cific}}% +\indextext{multibyte character|see{character, multibyte}}% +\indextext{object|seealso{object model}}% +\indextext{subobject|seealso{object model}}% +\indextext{derived class!most|see{most derived class}}% +\indextext{derived object!most|see{most derived object}}% +\indextext{program execution!as-if rule|see{as-if rule}}% +\indextext{observable behavior|see{behavior, observable}}% +\indextext{precedence of operator|see{operator, precedence of}}% +\indextext{order of evaluation in expression|see{expression, order of evaluation of}}% +\indextext{multiple threads|see{threads, multiple}}% + +\rSec2[intro.compliance.general]{General} + +\pnum +\indextext{conformance requirements|(}% +\indextext{conformance requirements!general|(}% +The set of +\defn{diagnosable rules} +consists of all syntactic and semantic rules in this document +except for those rules containing an explicit notation that +``no diagnostic is required'' or which are described as resulting in +``undefined behavior''. + +\pnum +\indextext{conformance requirements!method of description}% +Although this document states only requirements on \Cpp{} +implementations, those requirements are often easier to understand if +they are phrased as requirements on programs, parts of programs, or +execution of programs. Such requirements have the following meaning: +\begin{itemize} +\item +If a program contains no violations of the rules in +\ref{lex} through \ref{\lastlibchapter} as well as those specified in \ref{depr}, +a conforming implementation shall accept and correctly execute +\begin{footnote} +``Correct execution'' can include undefined behavior +and erroneous behavior, depending on +the data being processed; see \ref{intro.defs} and~\ref{intro.execution}. +\end{footnote} +that program, +except when the implementation's limitations (see below) are exceeded. +\item +\indextext{behavior!undefined}% +If a program contains a violation of a rule for which no diagnostic is required, +this document places no requirement on implementations +with respect to that program. + +\item +\indextext{message!diagnostic}% +\indextext{contract evaluation semantics!checking}% +\indextext{contract evaluation semantics!terminating}% +Otherwise, if a program contains +\begin{itemize} +\item +a violation of any diagnosable rule, +\item +a preprocessing translation unit with +a \tcode{\#warning} preprocessing directive\iref{cpp.error}, +\item +an occurrence +of a construct described in this document as ``conditionally-supported'' when +the implementation does not support that construct, or +\item +a contract assertion\iref{basic.contract.eval} +evaluated with a checking semantic +in a manifestly constant-evaluated context\iref{expr.const} +resulting in a contract violation, +\end{itemize} +a conforming implementation +shall issue at least one diagnostic message. +\end{itemize} +\begin{note} +During template argument deduction and substitution, +certain constructs that in other contexts require a diagnostic +are treated differently; +see~\ref{temp.deduct}. +\end{note} +Furthermore, a conforming implementation +shall not accept +\begin{itemize} +\item +a preprocessing translation unit containing +a \tcode{\#error} preprocessing directive\iref{cpp.error}, +\item +a translation unit with +a \grammarterm{static_assert-declaration} that fails\iref{dcl.pre}, or +\item +a contract assertion evaluated with a terminating semantic\iref{basic.contract.eval} +in a manifestly constant-evaluated context\iref{expr.const} +resulting in a contract violation. +\end{itemize} + +\pnum +\indextext{conformance requirements!library|(}% +\indextext{conformance requirements!classes}% +\indextext{conformance requirements!class templates}% +For classes and class templates, the library Clauses specify partial +definitions. Private members\iref{class.access} are not +specified, but each implementation shall supply them to complete the +definitions according to the description in the library Clauses. + +\pnum +For functions, function templates, objects, and values, the library +Clauses specify declarations. Implementations shall supply definitions +consistent with the descriptions in the library Clauses. + +\pnum +A \Cpp{} translation unit\iref{lex.phases} +obtains access to the names defined in the library by +including the appropriate standard library header or importing +the appropriate standard library named header unit\iref{using.headers}. + +\pnum +The templates, classes, functions, and objects in the library have +external linkage\iref{basic.link}. The implementation provides +definitions for standard library entities, as necessary, while combining +translation units to form a complete \Cpp{} program\iref{lex.phases}.% +\indextext{conformance requirements!library|)} + +\pnum +An implementation is either a +\defnadj{hosted}{implementation} or a +\defnadj{freestanding}{implementation}. +A freestanding +implementation is one in which execution may take place without the benefit of +an operating system. +A hosted implementation +supports all the facilities described in this document, while +a freestanding implementation +supports the entire \Cpp{} language +described in \ref{lex} through \ref{\lastcorechapter} and +the subset of the library facilities described in \ref{compliance}. + +\pnum +It is +\impldef{whether the implementation is a hardened implementation} +whether the implementation is a +\defnadj{hardened}{implementation}. +If it is a hardened implementation, +violating a hardened precondition +results in a contract violation\iref{structure.specifications}. + +\pnum +An implementation is encouraged to document its limitations in +the size or complexity of the programs it can successfully process, +if possible and where known. +\ref{implimits} lists some quantities that can be subject to limitations and +a potential minimum supported value for each quantity. + +\pnum +A conforming implementation may use an implementation-defined version +of the Unicode Standard that is a later version than the one +referenced in \ref{intro.refs}. + +\pnum +A conforming implementation may have extensions (including +additional library functions), provided they do not alter the +behavior of any well-formed program. +Implementations are required to diagnose programs that use such +extensions that are ill-formed according to this document. +Having done so, however, they can compile and execute such programs. + +\pnum +Each implementation shall include documentation that identifies all +conditionally-supported constructs\indextext{behavior!conditionally-supported} +that it does not support and defines all locale-specific characteristics. +\begin{footnote} +This documentation also defines implementation-defined behavior; +see~\ref{intro.abstract}. +\end{footnote} +\indextext{conformance requirements!general|)}% +\indextext{conformance requirements|)}% + +\rSec2[intro.abstract]{Abstract machine} + +\pnum +\indextext{program execution|(}% +\indextext{program execution!abstract machine}% +The semantic descriptions in this document define a +parameterized nondeterministic abstract machine. This document +places no requirement on the structure of conforming +implementations. In particular, they need not copy or emulate the +structure of the abstract machine. +\indextext{as-if rule}% +\indextext{behavior!observable}% +Rather, conforming implementations are required to emulate (only) the observable +behavior of the abstract machine as explained below. +\begin{footnote} +This provision is +sometimes called the ``as-if'' rule, because an implementation is free to +disregard any requirement of this document as long as the result +is \emph{as if} the requirement had been obeyed, as far as can be determined +from the observable behavior of the program. For instance, an actual +implementation need not evaluate part of an expression if it can deduce that its +value is not used and that no +\indextext{side effects}% +side effects affecting the +observable behavior of the program are produced. +\end{footnote} + +\pnum +\indextext{behavior!implementation-defined}% +Certain aspects and operations of the abstract machine are described in this +document as implementation-defined behavior (for example, +\tcode{sizeof(int)}). These constitute the parameters of the abstract machine. +Each implementation shall include documentation describing its characteristics +and behavior in these respects. +\begin{footnote} +This documentation also includes +conditionally-supported constructs and locale-specific behavior. +See~\ref{intro.compliance.general}. +\end{footnote} +Such documentation shall define the instance of the +abstract machine that corresponds to that implementation (referred to as the +``corresponding instance'' below). + +\pnum +\indextext{behavior!unspecified}% +Certain other aspects and operations of the abstract machine are +described in this document as unspecified behavior (for example, +order of evaluation of arguments in a function call\iref{expr.call}). +Where possible, this +document defines a set of allowable behaviors. These +define the nondeterministic aspects of the abstract machine. An instance +of the abstract machine can thus have more than one possible execution +for a given program and a given input. + +\pnum +\indextext{behavior!undefined}% +Certain other operations are described in this document as +undefined behavior (for example, the effect of +attempting to modify a const object). + +\pnum +Certain events in the execution of a program +are termed \defnadj{observable}{checkpoints}. +\begin{note} +A call to \tcode{std::observable_checkpoint}\iref{utility.undefined} +is an observable checkpoint, +as are certain parts of +the evaluation of contract assertions\iref{basic.contract}. +\end{note} + +\pnum +\indextext{program!well-formed}% +\indextext{behavior!observable}% +The \defnadj{defined}{prefix} of an execution +comprises the operations $O$ +for which for every undefined operation $U$ +there is an observable checkpoint $C$ +such that $O$ happens before $C$ and +$C$ happens before $U$. + +\begin{note} +The undefined behavior that arises from a data race\iref{intro.races} +occurs on all participating threads. +\end{note} + +A conforming implementation executing a well-formed program shall +produce the observable behavior +of the defined prefix +of one of the possible executions +of the corresponding instance +of the abstract machine with the +same program and the same input. +\indextext{behavior!undefined}% +If the selected execution contains an undefined operation, +the implementation executing that program with that input +may produce arbitrary additional observable behavior afterwards. +If the execution of an operation is specified as having erroneous behavior, +the implementation is permitted to issue a diagnostic and +is permitted to terminate the execution of the program. + +\pnum +\recommended +An implementation should issue a diagnostic when such an operation is executed. +\begin{note} +An implementation can issue a diagnostic +if it can determine that erroneous behavior is reachable +under an implementation-specific set of assumptions about the program behavior, +which can result in false positives. +\end{note} + +\pnum +\indextext{conformance requirements}% +The following specify the +\defnadj{observable}{behavior} +of the program: +\begin{itemize} +\item +Accesses through volatile glvalues are evaluated strictly according to the +rules of the abstract machine. +\item +Data is delivered to the host environment to be written into files (\xrefc{7.23.3}). + +\begin{note} +Delivering such data +is followed by an observable checkpoint\iref{cstdio.syn}. +Not all host environments provide access to file contents before program termination. +\end{note} + +\item +The input and output dynamics of interactive devices shall take +place in such a fashion that prompting output is actually delivered before a program waits for input. What constitutes an interactive device is +\impldef{interactive device}. +\end{itemize} + +\begin{note} +More stringent correspondences between abstract and actual +semantics can be defined by each implementation. +\end{note} +\indextext{program execution|)}% + +\rSec1[intro.structure]{Structure of this document} + +\pnum +\indextext{standard!structure of|(}% +\indextext{standard!structure of}% +\ref{lex} through \ref{\lastcorechapter} describe the \Cpp{} programming +language. That description includes detailed syntactic specifications in +a form described in~\ref{syntax}. For convenience, \ref{gram} +repeats all such syntactic specifications. + +\pnum +\ref{\firstlibchapter} through \ref{\lastlibchapter} and \ref{depr} +(the \defn{library clauses}) describe the \Cpp{} standard library. +That description includes detailed descriptions of the +entities and macros +that constitute the library, in a form described in \ref{library}. + +\pnum +\ref{implimits} recommends lower bounds on the capacity of conforming +implementations. + +\pnum +\ref{diff} summarizes the evolution of \Cpp{} since its first +published description, and explains in detail the differences between +\Cpp{} and C\@. Certain features of \Cpp{} exist solely for compatibility +purposes; \ref{depr} describes those features. +\indextext{standard!structure of|)} + +\rSec1[syntax]{Syntax notation} + +\pnum +\indextext{notation!syntax|(}% +In the syntax notation used in this document, syntactic +categories are indicated by \fakegrammarterm{italic, sans-serif} type, and literal words +and characters in \tcode{constant} \tcode{width} type. Alternatives are +listed on separate lines except in a few cases where a long set of +alternatives is marked by the phrase ``one of''. If the text of an alternative is too long to fit on a line, the text is continued on subsequent lines indented from the first one. +An optional terminal or non-terminal symbol is indicated by the subscript +``\opt{\relax}'', so +\begin{ncbnf} +\terminal{\{} \opt{expression} \terminal{\}} +\end{ncbnf} +indicates an optional expression enclosed in braces.% + +\pnum +Names for syntactic categories have generally been chosen according to +the following rules: +\begin{itemize} +\item \fakegrammarterm{X-name} is a use of an identifier in a context that +determines its meaning (e.g., \grammarterm{class-name}, +\grammarterm{typedef-name}). +\item \fakegrammarterm{X-id} is an identifier with no context-dependent meaning +(e.g., \grammarterm{qualified-id}). +\item \fakegrammarterm{X-seq} is one or more \fakegrammarterm{X}'s without intervening +delimiters (e.g., \grammarterm{declaration-seq} is a sequence of +\grammarterm{declaration}s). +\item \fakegrammarterm{X-list} is one or more \fakegrammarterm{X}'s separated by +intervening commas (e.g., \grammarterm{identifier-list} is a sequence of +\grammarterm{identifier}s separated by commas). +\end{itemize}% +\indextext{notation!syntax|)} diff --git a/papers/wg21-latex/latexmkrc b/papers/wg21-latex/latexmkrc new file mode 100644 index 0000000..cf4ee69 --- /dev/null +++ b/papers/wg21-latex/latexmkrc @@ -0,0 +1,2 @@ +$cleanup_includes_cusdep_generated = 1; +$cleanup_includes_generated = 1; diff --git a/papers/wg21-latex/ldiff.sh b/papers/wg21-latex/ldiff.sh new file mode 100755 index 0000000..603e6e5 --- /dev/null +++ b/papers/wg21-latex/ldiff.sh @@ -0,0 +1,7 @@ +#!/bin/env sh + +export SAFECMDS=Rplus,Cpp,CppIII,opt,shl,shr,dcr,exor,bigoh,tilde,bitand,bitor,xor,rightshift,enternote,enterexample,exitexample,required,requires,effects,postconditions,postcondition,preconditions,precondition,returns,throws,default,complexity,remark,remarks,note,notes,realnote,realnotes,errors,sync,implimits,replaceable,exceptionsafety,returntype,cvalue,ctype,ctypes,dtype,ctemplate,templalias,xref,xsee,ntbs,ntmbs,ntwcs,ntcxvis,ntcxxxiis,expos,impdef,notdef,unspec,unspecbool,seebelow,unspecuniqtype,unspecalloctype,unun,change,rationale,effect,difficulty,howwide,uniquens,cv,seebelow + +export TEXTCMDS=tcode,term,grammarterm,techterm,defnx,defn,Fundescx,Fundesc,state,leftshift,EnterBlock,ExitBlock,NTS,EXPO,impdefx,UNSP,xname,mname,diffdef,stage,doccite,cvqual,numconst,logop + +git latexdiff --no-cleanup --flatten --main new.tex --append-textcmd=${TEXTCMDS} --append-safecmd=${SAFECMDS} --add-to-config='ARRENV=itemdecl;codeblock' -o diff.pdf 917cce5e008263a04aa66db6a5bc1e6d45688f30 -- diff --git a/papers/wg21-latex/mybiblio.bib b/papers/wg21-latex/mybiblio.bib new file mode 100644 index 0000000..c7e959a --- /dev/null +++ b/papers/wg21-latex/mybiblio.bib @@ -0,0 +1,69 @@ +@misc{viewmayb27:online, +author = {Steve Downey}, +title = {A view of 0 or 1 elements: views::maybe}, +howpublished = {\url{https://github.com/steve-downey/view_maybe/blob/master/papers/view-maybe.tex}}, +month = {07}, +year = {2022}, +note = {(Accessed on 08/15/2022)} +} + +@misc{UAX31-14:online, +author = {Mark Davis and Robin Leroy}, +title = {UNICODE IDENTIFIER AND PATTERN SYNTAX}, +howpublished = {\url{https://www.unicode.org/reports/tr31/tr31-35.html}}, +month = {08}, +year = {2021}, +note = {(Accessed on 10/20/2022)} +} + +@misc{UAX31-15:online, +author = {Mark Davis and Robin Leroy}, +title = {UNICODE IDENTIFIER AND PATTERN SYNTAX}, +howpublished = {\url{https://www.unicode.org/reports/tr31/tr31-37.html}}, +month = {08}, +year = {2022}, +note = {(Accessed on 10/20/2022)} +} + +@misc{UAX31-DIFF:online, +author = {Mark Davis and Robin Leroy}, +title = {UNICODE IDENTIFIER AND PATTERN SYNTAX}, +howpublished = {\url{https://www.unicode.org/reports/tr31/tr31-36.html}}, +month = {08}, +year = {2022}, +note = {(Accessed on 10/20/2022)} +} + + +@misc{REFBIND, +author = {JeanHeyd Meneide}, +title = {To Bind and Loose a Reference | The Pasture}, +howpublished = {\url{https://thephd.dev/to-bind-and-loose-a-reference-optional}}, +month = {}, +year = {}, +note = {(Accessed on 01/01/2024)} +} + +@misc{Downey_smd_optional_optional_T, +author = {Downey, Stephen}, +title = {{optional}}, +howpublished = {\url{https://github.com/steve-downey/Optional26}} +} + +@misc{rawgithu58:online, +author = {Downey, Stephen}, +title = {optional.hpp}, +howpublished = {\url{https://raw.githubusercontent.com/steve-downey/Optional26/refref/include/Beman/Optional26/optional.hpp}}, +month = {}, +year = {}, +note = {(Accessed on 08/14/2024)} +} + +@misc{The_Beman_Project_beman_optional, +author = {The Beman Project}, +license = {Apache-2.0}, +title = {{beman.optional}}, +howpublished = {\url{https://github.com/bemanproject/optional}}, +month = {}, +year = {}, +} diff --git a/papers/wg21-latex/new-wording.tex b/papers/wg21-latex/new-wording.tex new file mode 100644 index 0000000..1697f8f --- /dev/null +++ b/papers/wg21-latex/new-wording.tex @@ -0,0 +1,1648 @@ +\rSec1[optional]{Optional objects} + +\rSec2[optional.general]{General} + +\pnum +Subclause~\ref{optional} describes class template \tcode{optional} that represents +optional objects. +An \defn{optional object} is an +object that contains the storage for another object and manages the lifetime of +this contained object, if any. The contained object may be initialized after +the optional object has been initialized, and may be destroyed before the +optional object has been destroyed. The initialization state of the contained +object is tracked by the optional object. + +\rSec2[optional.syn]{Header \tcode{} synopsis} + +\indexheader{optional}% +\begin{codeblock} +// mostly freestanding +#include // see \tcode{compare.syn} + +namespace std { + // \ref{optional.optional}, class template \tcode{optional} + template + class optional; // partially freestanding + + @\added{// \ref{optional.optional.ref}, partial specialization of \tcode{optional} for lvalue reference types }@ + @\added{template }@ + @\added{class optional; // partially freestanding}@ + + template + constexpr bool ranges::enable_view> = true; + template + constexpr auto format_kind> = range_format::disabled; + @\added{template}@ + @\added{constexpr bool ranges::enable_borrowed_range> = true;}@ + + template + concept @\defexposconcept{is-derived-from-optional}@ = requires(const T& t) { // \expos + [](const optional&){ }(t); + }; + + // \ref{optional.nullopt}, no-value state indicator + struct nullopt_t{@\seebelow@}; + inline constexpr nullopt_t nullopt(@\unspec@); + + // \ref{optional.bad.access}, class \tcode{bad_optional_access} + class bad_optional_access; + + // \ref{optional.relops}, relational operators + template + constexpr bool operator==(const optional&, const optional&); + template + constexpr bool operator!=(const optional&, const optional&); + template + constexpr bool operator<(const optional&, const optional&); + template + constexpr bool operator>(const optional&, const optional&); + template + constexpr bool operator<=(const optional&, const optional&); + template + constexpr bool operator>=(const optional&, const optional&); + template U> + constexpr compare_three_way_result_t + operator<=>(const optional&, const optional&); + + // \tcode{optional.nullops}, comparison with \tcode{nullopt} + template constexpr bool operator==(const optional&, nullopt_t) noexcept; + template + constexpr strong_ordering operator<=>(const optional&, nullopt_t) noexcept; + + // \tcode{optional.comp.with.t}, comparison with \tcode{T} + template constexpr bool operator==(const optional&, const U&); + template constexpr bool operator==(const T&, const optional&); + template constexpr bool operator!=(const optional&, const U&); + template constexpr bool operator!=(const T&, const optional&); + template constexpr bool operator<(const optional&, const U&); + template constexpr bool operator<(const T&, const optional&); + template constexpr bool operator>(const optional&, const U&); + template constexpr bool operator>(const T&, const optional&); + template constexpr bool operator<=(const optional&, const U&); + template constexpr bool operator<=(const T&, const optional&); + template constexpr bool operator>=(const optional&, const U&); + template constexpr bool operator>=(const T&, const optional&); + template + requires (!@\exposconcept{is-derived-from-optional}@) && @\libconcept{three_way_comparable_with}@ + constexpr compare_three_way_result_t + operator<=>(const optional&, const U&); + + // \ref{optional.specalg}, specialized algorithms + template + constexpr void swap(optional&, optional&) noexcept(@\seebelow@); + + template + constexpr optional> make_optional(T&&); + template + constexpr optional make_optional(Args&&... args); + template + constexpr optional make_optional(initializer_list il, Args&&... args); + + // \ref{optional.hash}, hash support + template struct hash; + template struct hash>; +} +\end{codeblock} + +\rSec2[optional.optional]{Class template \tcode{optional}} + +\rSec3[optional.optional.general]{General} + +\indexlibraryglobal{optional}% +\indexlibrarymember{value_type}{optional}% +\begin{codeblock} +namespace std { + template + class optional { + public: + using value_type = T; + using iterator = @\impdefnc@; // see~\ref{optional.iterators} + using const_iterator = @\impdefnc@; // see~\ref{optional.iterators} + + // \ref{optional.ctor}, constructors + constexpr optional() noexcept; + constexpr optional(nullopt_t) noexcept; + constexpr optional(const optional&); + constexpr optional(optional&&) noexcept(@\seebelow@); + template + constexpr explicit optional(in_place_t, Args&&...); + template + constexpr explicit optional(in_place_t, initializer_list, Args&&...); + template> + constexpr explicit(@\seebelow@) optional(U&&); + template + constexpr explicit(@\seebelow@) optional(const optional&); + template + constexpr explicit(@\seebelow@) optional(optional&&); + + // \ref{optional.dtor}, destructor + constexpr ~optional(); + + // \ref{optional.assign}, assignment + constexpr optional& operator=(nullopt_t) noexcept; + constexpr optional& operator=(const optional&); + constexpr optional& operator=(optional&&) noexcept(@\seebelow@); + template> constexpr optional& operator=(U&&); + template constexpr optional& operator=(const optional&); + template constexpr optional& operator=(optional&&); + template constexpr T& emplace(Args&&...); + template constexpr T& emplace(initializer_list, Args&&...); + + // \ref{optional.swap}, swap + constexpr void swap(optional&) noexcept(@\seebelow@); + + // \ref{optional.iterators}, iterator support + constexpr iterator begin() noexcept; + constexpr const_iterator begin() const noexcept; + constexpr iterator end() noexcept; + constexpr const_iterator end() const noexcept; + + // \ref{optional.observe}, observers + constexpr const T* operator->() const noexcept; + constexpr T* operator->() noexcept; + constexpr const T& operator*() const & noexcept; + constexpr T& operator*() & noexcept; + constexpr T&& operator*() && noexcept; + constexpr const T&& operator*() const && noexcept; + constexpr explicit operator bool() const noexcept; + constexpr bool has_value() const noexcept; + constexpr const T& value() const &; // freestanding-deleted + constexpr T& value() &; // freestanding-deleted + constexpr T&& value() &&; // freestanding-deleted + constexpr const T&& value() const &&; // freestanding-deleted + template> constexpr T value_or(U&&) const &; + template> constexpr T value_or(U&&) &&; + + // \ref{optional.monadic}, monadic operations + template constexpr auto and_then(F&& f) &; + template constexpr auto and_then(F&& f) &&; + template constexpr auto and_then(F&& f) const &; + template constexpr auto and_then(F&& f) const &&; + template constexpr auto transform(F&& f) &; + template constexpr auto transform(F&& f) &&; + template constexpr auto transform(F&& f) const &; + template constexpr auto transform(F&& f) const &&; + template constexpr optional or_else(F&& f) &&; + template constexpr optional or_else(F&& f) const &; + + // \ref{optional.mod}, modifiers + constexpr void reset() noexcept; + + private: + T* val; // \expos + }; + + template + optional(T) -> optional; +} +\end{codeblock} + +\pnum +Any instance of \tcode{optional} at any given time either contains a value or does not contain a value. +When an instance of \tcode{optional} \defnx{contains a value}{contains a value!\idxcode{optional}}, +it means that an object of type \tcode{T}, referred to as the optional object's \defnx{contained value}{contained value!\idxcode{optional}}, +is nested within\iref{intro.object} the optional object. +When an object of type \tcode{optional} is contextually converted to \tcode{bool}, +the conversion returns \tcode{true} if the object contains a value; +otherwise the conversion returns \tcode{false}. + +\pnum +When an \tcode{optional} object contains a value, +member \tcode{val} points to the contained value. + +\begin{removedblock} +\tcode{T} shall be a type +\tcode{T} shall be a type +other than \cv{} \tcode{in_place_t} or \cv{} \tcode{nullopt_t} +that meets the \oldconcept{Destructible} requirements (\tcode{cpp17.destructible}). + +\end{removedblock} + +\begin{addedblock} +\pnum +A type \tcode{X} is a \defnx{valid contained type}{valid contained type!\idxcode{optional}} for \tcode{optional} if \tcode{X} is an lvalue reference type +or a complete non-array object type, and \tcode{remove_cvref_t} is a type other than \tcode{in_place_t} or \tcode{nullopt_t}. +\pnum +If a specialization of \tcode{optional} is instantiated with a type \tcode{T} that is not a valid contained type for \tcode{optional}, the program is ill-formed. If \tcode{T} is an object type, \tcode{T} shall meet the \oldconcept{Destructible} requirements (\tcode{cpp17.destructible}). +\end{addedblock} + +\rSec3[optional.ctor]{Constructors} + +\pnum +The exposition-only variable template \exposid{converts-from-any-cvref} +is used by some constructors for \tcode{optional}. +\begin{codeblock} +template +constexpr bool @\exposid{converts-from-any-cvref}@ = // \expos + disjunction_v, is_convertible, + is_constructible, is_convertible, + is_constructible, is_convertible, + is_constructible, is_convertible>; +\end{codeblock} + +\indexlibraryctor{optional}% +\begin{itemdecl} +constexpr optional() noexcept; +constexpr optional(nullopt_t) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\ensures +\tcode{*this} does not contain a value. + +\pnum +\remarks +No contained value is initialized. +For every object type \tcode{T} these constructors are constexpr constructors\iref{dcl.constexpr}. +\end{itemdescr} + +\indexlibraryctor{optional}% +\begin{itemdecl} +constexpr optional(const optional& rhs); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +If \tcode{rhs} contains a value, direct-non-list-initializes the contained value +with \tcode{*rhs}. + +\pnum +\ensures +\tcode{rhs.has_value() == this->has_value()}. + +\pnum +\throws +Any exception thrown by the selected constructor of \tcode{T}. + +\pnum +\remarks +This constructor is defined as deleted unless +\tcode{is_copy_constructible_v} is \tcode{true}. +If \tcode{is_trivially_copy_constructible_v} is \tcode{true}, +this constructor is trivial. +\end{itemdescr} + +\indexlibraryctor{optional}% +\begin{itemdecl} +constexpr optional(optional&& rhs) noexcept(@\seebelow@); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\tcode{is_move_constructible_v} is \tcode{true}. + +\pnum +\effects +If \tcode{rhs} contains a value, direct-non-list-initializes the contained value +with \added{\tcode{*std::move(rhs)}}\removed{\tcode{std::move(*rhs)}}. +\tcode{rhs.has_value()} is unchanged. + +\pnum +\ensures +\tcode{rhs.has_value() == this->has_value()}. + +\pnum +\throws +Any exception thrown by the selected constructor of \tcode{T}. + +\pnum +\remarks +The exception specification is equivalent to +\tcode{is_nothrow_move_constructible_v}. +If \tcode{is_trivially_move_constructible_v} is \tcode{true}, +this constructor is trivial. +\end{itemdescr} + +\indexlibraryctor{optional}% +\begin{itemdecl} +template constexpr explicit optional(in_place_t, Args&&... args); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\tcode{is_constructible_v} is \tcode{true}. + +\pnum +\effects +Direct-non-list-initializes the contained value with \tcode{std::forward(args)...}. + +\pnum +\ensures +\tcode{*this} contains a value. + +\pnum +\throws +Any exception thrown by the selected constructor of \tcode{T}. + +\pnum +\remarks +If \tcode{T}'s constructor selected for the initialization is a constexpr constructor, this constructor is a constexpr constructor. +\end{itemdescr} + +\indexlibraryctor{optional}% +\begin{itemdecl} +template + constexpr explicit optional(in_place_t, initializer_list il, Args&&... args); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\tcode{is_constructible_v\&, Args...>} is \tcode{true}. + +\pnum +\effects +Direct-non-list-initializes the contained value with \tcode{il, std::forward(args)...}. + +\pnum +\ensures +\tcode{*this} contains a value. + +\pnum +\throws +Any exception thrown by the selected constructor of \tcode{T}. + +\pnum +\remarks +If \tcode{T}'s constructor selected for the initialization is a constexpr constructor, this constructor is a constexpr constructor. +\end{itemdescr} + +\indexlibraryctor{optional}% +\begin{itemdecl} +template> constexpr explicit(@\seebelow@) optional(U&& v); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\begin{itemize} +\item \tcode{is_constructible_v} is \tcode{true}, +\item \tcode{is_same_v, in_place_t>} is \tcode{false}, +\item \tcode{is_same_v, optional>} is \tcode{false}, and +\item if \tcode{T} is \cv{} \tcode{bool}, +\tcode{remove_cvref_t} is not a specialization of \tcode{optional}. +\end{itemize} + +\pnum +\effects +Direct-non-list-initializes the contained value with \tcode{std::forward(v)}. + +\pnum +\ensures +\tcode{*this} contains a value. + +\pnum +\throws +Any exception thrown by the selected constructor of \tcode{T}. + +\pnum +\remarks +If \tcode{T}'s selected constructor is a constexpr constructor, +this constructor is a constexpr constructor. +The expression inside \keyword{explicit} is equivalent to: +\begin{codeblock} +!is_convertible_v +\end{codeblock} +\end{itemdescr} + +\indexlibraryctor{optional}% +\begin{itemdecl} +template constexpr explicit(@\seebelow@) optional(const optional& rhs); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\begin{itemize} +\item \tcode{is_constructible_v} is \tcode{true}, and +\item if \tcode{T} is not \cv{} \tcode{bool}, +\tcode{\exposid{converts-from-any-cvref}>} is \tcode{false}. +\end{itemize} + +\pnum +\effects +If \tcode{rhs} contains a value, +direct-non-list-initializes the contained value with \tcode{*rhs}. + +\pnum +\ensures +\tcode{rhs.has_value() == this->has_value()}. + +\pnum +\throws +Any exception thrown by the selected constructor of \tcode{T}. + +\pnum +\remarks +The expression inside \keyword{explicit} is equivalent to: +\begin{codeblock} +!is_convertible_v +\end{codeblock} +\end{itemdescr} + +\indexlibraryctor{optional}% +\begin{itemdecl} +template constexpr explicit(@\seebelow@) optional(optional&& rhs); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\begin{itemize} +\item \tcode{is_constructible_v} is \tcode{true}, and +\item if \tcode{T} is not \cv{} \tcode{bool}, +\tcode{\exposid{converts-from-any-cvref}>} is \tcode{false}. +\end{itemize} + +\pnum +\effects +If \tcode{rhs} contains a value, +direct-non-list-initializes the contained value with \added{\tcode{*std::move(rhs)}}\removed{\tcode{std::move(*rhs)}}. +\tcode{rhs.has_value()} is unchanged. + +\pnum +\ensures +\tcode{rhs.has_value() == this->has_value()}. + +\pnum +\throws +Any exception thrown by the selected constructor of \tcode{T}. + +\pnum +\remarks +The expression inside \keyword{explicit} is equivalent to: +\begin{codeblock} +!is_convertible_v +\end{codeblock} +\end{itemdescr} + +\rSec3[optional.dtor]{Destructor} + +\rSec3[optional.assign]{Assignment} + +\indexlibrarymember{operator=}{optional}% +\begin{itemdecl} +constexpr optional& operator=(nullopt_t) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +If \tcode{*this} contains a value, calls \tcode{val->T::\~T()} to destroy the contained value; otherwise no effect. + +\pnum +\ensures +\tcode{*this} does not contain a value. + +\pnum +\returns +\tcode{*this}. +\end{itemdescr} + +\indexlibrarymember{operator=}{optional}% +\begin{itemdecl} +constexpr optional& operator=(const optional& rhs); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +See \tcode{optional.assign.copy}. +\begin{lib2dtab2}{\tcode{optional::operator=(const optional\&)} effects}{optional.assign.copy} +{\tcode{*this} contains a value} +{\tcode{*this} does not contain a value} + +\rowhdr{\tcode{rhs} contains a value} & +assigns \tcode{*rhs} to the contained value & +direct-non-list-initializes the contained value with \tcode{*rhs} \\ +\rowsep + +\rowhdr{\tcode{rhs} does not contain a value} & +destroys the contained value by calling \tcode{val->T::\~T()} & +no effect \\ +\end{lib2dtab2} + +\pnum +\ensures +\tcode{rhs.has_value() == this->has_value()}. + +\pnum +\returns +\tcode{*this}. + +\pnum +\remarks +If any exception is thrown, the result of the expression \tcode{this->has_value()} remains unchanged. +If an exception is thrown during the call to \tcode{T}'s copy constructor, no effect. +If an exception is thrown during the call to \tcode{T}'s copy assignment, +the state of its contained value is as defined by the exception safety guarantee of \tcode{T}'s copy assignment. +This operator is defined as deleted unless +\tcode{is_copy_constructible_v} is \tcode{true} and +\tcode{is_copy_assignable_v} is \tcode{true}. +If \tcode{is_trivially_copy_constructible_v \&\&} +\tcode{is_trivially_copy_assignable_v \&\&} +\tcode{is_trivially_destructible_v} is \tcode{true}, +this assignment operator is trivial. +\end{itemdescr} + +\indexlibrarymember{operator=}{optional}% +\begin{itemdecl} +constexpr optional& operator=(optional&& rhs) noexcept(@\seebelow@); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\tcode{is_move_constructible_v} is \tcode{true} and +\tcode{is_move_assignable_v} is \tcode{true}. + +\pnum +\effects +See \tcode{optional.assign.move}. +The result of the expression \tcode{rhs.has_value()} remains unchanged. +\begin{lib2dtab2}{\tcode{optional::operator=(optional\&\&)} effects}{optional.assign.move} +{\tcode{*this} contains a value} +{\tcode{*this} does not contain a value} + +\rowhdr{\tcode{rhs} contains a value} & +assigns \added{\tcode{*std::move(rhs)}}\removed{\tcode{std::move(*rhs)}} to the contained value & +direct-non-list-initializes the contained value with \added{\tcode{*std::move(rhs)}}\removed{\tcode{std::move(*rhs)}} \\ +\rowsep + +\rowhdr{\tcode{rhs} does not contain a value} & +destroys the contained value by calling \tcode{val->T::\~T()} & +no effect \\ +\end{lib2dtab2} + +\pnum +\ensures +\tcode{rhs.has_value() == this->has_value()}. + +\pnum +\returns +\tcode{*this}. + +\pnum +\remarks +The exception specification is equivalent to: +\begin{codeblock} +is_nothrow_move_assignable_v && is_nothrow_move_constructible_v +\end{codeblock} + +\pnum +If any exception is thrown, the result of the expression \tcode{this->has_value()} remains unchanged. +If an exception is thrown during the call to \tcode{T}'s move constructor, +the state of \tcode{*rhs.val} is determined by the exception safety guarantee of \tcode{T}'s move constructor. +If an exception is thrown during the call to \tcode{T}'s move assignment, +the state of \tcode{*val} and \tcode{*rhs.val} is determined by the exception safety guarantee of \tcode{T}'s move assignment. +If \tcode{is_trivially_move_constructible_v \&\&} +\tcode{is_trivially_move_assignable_v \&\&} +\tcode{is_trivially_destructible_v} is \tcode{true}, +this assignment operator is trivial. +\end{itemdescr} + +\indexlibrarymember{operator=}{optional}% +\begin{itemdecl} +template> constexpr optional& operator=(U&& v); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\begin{itemize} +\item \tcode{is_same_v, optional>} is \tcode{false}, +\item \tcode{conjunction_v, is_same>>} is \tcode{false}, +\item \tcode{is_constructible_v} is \tcode{true}, and +\item \tcode{is_assignable_v} is \tcode{true}. +\end{itemize} + +\pnum +\effects +If \tcode{*this} contains a value, assigns \tcode{std::forward(v)} to the contained value; otherwise direct-non-list-initializes the contained value with \tcode{std::forward(v)}. + +\pnum +\ensures +\tcode{*this} contains a value. + +\pnum +\returns +\tcode{*this}. + +\pnum +\remarks +If any exception is thrown, the result of the expression \tcode{this->has_value()} remains unchanged. If an exception is thrown during the call to \tcode{T}'s constructor, the state of \tcode{v} is determined by the exception safety guarantee of \tcode{T}'s constructor. If an exception is thrown during the call to \tcode{T}'s assignment, the state of \tcode{*val} and \tcode{v} is determined by the exception safety guarantee of \tcode{T}'s assignment. +\end{itemdescr} + +\indexlibrarymember{operator=}{optional}% +\begin{itemdecl} +template constexpr optional& operator=(const optional& rhs); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\begin{itemize} +\item \tcode{is_constructible_v} is \tcode{true}, +\item \tcode{is_assignable_v} is \tcode{true}, +\item \tcode{\exposid{converts-from-any-cvref}>} is \tcode{false}, +\item \tcode{is_assignable_v\&>} is \tcode{false}, +\item \tcode{is_assignable_v\&\&>} is \tcode{false}, +\item \tcode{is_assignable_v\&>} is \tcode{false}, and +\item \tcode{is_assignable_v\&\&>} is \tcode{false}. +\end{itemize} + +\pnum +\effects +See \tcode{optional.assign.copy.templ}. +\begin{lib2dtab2}{\tcode{optional::operator=(const optional\&)} effects}{optional.assign.copy.templ} +{\tcode{*this} contains a value} +{\tcode{*this} does not contain a value} + +\rowhdr{\tcode{rhs} contains a value} & +assigns \tcode{*rhs} to the contained value & +direct-non-list-initializes the contained value with \tcode{*rhs} \\ +\rowsep + +\rowhdr{\tcode{rhs} does not contain a value} & +destroys the contained value by calling \tcode{val->T::\~T()} & +no effect \\ +\end{lib2dtab2} + +\pnum +\ensures +\tcode{rhs.has_value() == this->has_value()}. + +\pnum +\returns +\tcode{*this}. + +\pnum +\remarks +If any exception is thrown, +the result of the expression \tcode{this->has_value()} remains unchanged. +If an exception is thrown during the call to \tcode{T}'s constructor, +the state of \tcode{*rhs.val} is determined by +the exception safety guarantee of \tcode{T}'s constructor. +If an exception is thrown during the call to \tcode{T}'s assignment, +the state of \tcode{*val} and \tcode{*rhs.val} is determined by +the exception safety guarantee of \tcode{T}'s assignment. +\end{itemdescr} + +\indexlibrarymember{operator=}{optional}% +\begin{itemdecl} +template constexpr optional& operator=(optional&& rhs); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\begin{itemize} +\item \tcode{is_constructible_v} is \tcode{true}, +\item \tcode{is_assignable_v} is \tcode{true}, +\item \tcode{\exposid{converts-from-any-cvref}>} is \tcode{false}, +\item \tcode{is_assignable_v\&>} is \tcode{false}, +\item \tcode{is_assignable_v\&\&>} is \tcode{false}, +\item \tcode{is_assignable_v\&>} is \tcode{false}, and +\item \tcode{is_assignable_v\&\&>} is \tcode{false}. +\end{itemize} + +\pnum +\effects +See \tcode{optional.assign.move.templ}. +The result of the expression \tcode{rhs.has_value()} remains unchanged. +\begin{lib2dtab2}{\tcode{optional::operator=(optional\&\&)} effects}{optional.assign.move.templ} +{\tcode{*this} contains a value} +{\tcode{*this} does not contain a value} + +\rowhdr{\tcode{rhs} contains a value} & +assigns \added{\tcode{*std::move(rhs)}}\removed{\tcode{std::move(*rhs)}} to the contained value & +direct-non-list-initializes the contained value with \added{\tcode{*std::move(rhs)}}\removed{\tcode{std::move(*rhs)}} \\ +\rowsep + +\rowhdr{\tcode{rhs} does not contain a value} & +destroys the contained value by calling \tcode{val->T::\~T()} & +no effect \\ +\end{lib2dtab2} + +\pnum +\ensures +\tcode{rhs.has_value() == this->has_value()}. + +\pnum +\returns +\tcode{*this}. + +\pnum +\remarks +If any exception is thrown, +the result of the expression \tcode{this->has_value()} remains unchanged. +If an exception is thrown during the call to \tcode{T}'s constructor, +the state of \tcode{*rhs.val} is determined by +the exception safety guarantee of \tcode{T}'s constructor. +If an exception is thrown during the call to \tcode{T}'s assignment, +the state of \tcode{*val} and \tcode{*rhs.val} is determined by +the exception safety guarantee of \tcode{T}'s assignment. +\end{itemdescr} + +\indexlibrarymember{emplace}{optional}% +\begin{itemdecl} +template constexpr T& emplace(Args&&... args); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\mandates +\tcode{is_constructible_v} is \tcode{true}. + +\pnum +\effects +Calls \tcode{*this = nullopt}. Then direct-non-list-initializes the contained value +with \tcode{std::forward\brk{}(args)...}. + +\pnum +\ensures +\tcode{*this} contains a value. + +\pnum +\returns +A reference to the new contained value. + +\pnum +\throws +Any exception thrown by the selected constructor of \tcode{T}. + +\pnum +\remarks +If an exception is thrown during the call to \tcode{T}'s constructor, \tcode{*this} does not contain a value, and the previous \tcode{*val} (if any) has been destroyed. +\end{itemdescr} + +\indexlibrarymember{emplace}{optional}% +\begin{itemdecl} +template constexpr T& emplace(initializer_list il, Args&&... args); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\tcode{is_constructible_v\&, Args...>} is \tcode{true}. + +\pnum +\effects +Calls \tcode{*this = nullopt}. Then direct-non-list-initializes the contained value with +\tcode{il, std::\brk{}forward(args)...}. + +\pnum +\ensures +\tcode{*this} contains a value. + +\pnum +\returns +A reference to the new contained value. + +\pnum +\throws +Any exception thrown by the selected constructor of \tcode{T}. + +\pnum +\remarks +If an exception is thrown during the call to \tcode{T}'s constructor, \tcode{*this} does not contain a value, and the previous \tcode{*val} (if any) has been destroyed. +\end{itemdescr} + + +\rSec3[optional.swap]{Swap} + +\rSec3[optional.iterators]{Iterator support} + +\rSec3[optional.observe]{Observers} + +\rSec3[optional.monadic]{Monadic operations} + +\indexlibrarymember{and_then}{optional} +\begin{itemdecl} +template constexpr auto and_then(F&& f) &; +template constexpr auto and_then(F&& f) const &; +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \tcode{U} be \tcode{invoke_result_t}. + +\pnum +\mandates +\tcode{remove_cvref_t} is a specialization of \tcode{optional}. + +\pnum +\effects +Equivalent to: +\begin{codeblock} +if (*this) { + return invoke(std::forward(f), *@\exposid{val}@); +} else { + return remove_cvref_t(); +} +\end{codeblock} +\end{itemdescr} + +\indexlibrarymember{and_then}{optional} +\begin{itemdecl} +template constexpr auto and_then(F&& f) &&; +template constexpr auto and_then(F&& f) const &&; +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \tcode{U} be \tcode{invoke_result_t}. + +\pnum +\mandates +\tcode{remove_cvref_t} is a specialization of \tcode{optional}. + +\pnum +\effects +Equivalent to: +\begin{codeblock} +if (*this) { + return invoke(std::forward(f), std::move(*@\exposid{val}@)); +} else { + return remove_cvref_t(); +} +\end{codeblock} +\end{itemdescr} + +\indexlibrarymember{transform}{optional} +\begin{itemdecl} +template constexpr auto transform(F&& f) &; +template constexpr auto transform(F&& f) const &; +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \tcode{U} be \tcode{remove_cv_t>}. + +\pnum +\mandates +\removed{ +\tcode{U} is a non-array object type +other than \tcode{in_place_t} or \tcode{nullopt_t}.} +\added{ +\tcode{U} is a valid contained type for \tcode{optional}. +} +The declaration +\begin{codeblock} +U u(invoke(std::forward(f), *@\exposid{val}@)); +\end{codeblock} +is well-formed for some invented variable \tcode{u}. +\begin{note} +There is no requirement that \tcode{U} is movable\iref{dcl.init.general}. +\end{note} + +\pnum +\returns +If \tcode{*this} contains a value, an \tcode{optional} object +whose contained value is direct-non-list-initialized with +\tcode{invoke(std::forward(f), *\exposid{val})}; +otherwise, \tcode{optional()}. +\end{itemdescr} + +\indexlibrarymember{transform}{optional} +\begin{itemdecl} +template constexpr auto transform(F&& f) &&; +template constexpr auto transform(F&& f) const &&; +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \tcode{U} be +\tcode{remove_cv_t>}. + +\pnum +\mandates +\removed{ +\tcode{U} is a non-array object type +other than \tcode{in_place_t} or \tcode{nullopt_t}. +} +\added{ +\tcode{U} is a valid contained type for \tcode{optional}. +} +The declaration +\begin{codeblock} +U u(invoke(std::forward(f), std::move(*@\exposid{val}@))); +\end{codeblock} +is well-formed for some invented variable \tcode{u}. +\begin{note} +There is no requirement that \tcode{U} is movable\iref{dcl.init.general}. +\end{note} + +\pnum +\returns +If \tcode{*this} contains a value, an \tcode{optional} object +whose contained value is direct-non-list-initialized with +\tcode{invoke(std::forward(f), std::move(*\exposid{val}))}; +otherwise, \tcode{optional()}. +\end{itemdescr} + +\indexlibrarymember{or_else}{optional} +\begin{itemdecl} +template constexpr optional or_else(F&& f) const &; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\tcode{F} models \tcode{\libconcept{invocable}} and +\tcode{T} models \libconcept{copy_constructible}. + +\pnum +\mandates +\tcode{is_same_v>, optional>} is \tcode{true}. + +\pnum +\effects +Equivalent to: +\begin{codeblock} +if (*this) { + return *this; +} else { + return std::forward(f)(); +} +\end{codeblock} +\end{itemdescr} + +\indexlibrarymember{or_else}{optional} +\begin{itemdecl} +template constexpr optional or_else(F&& f) &&; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +\tcode{F} models \tcode{\libconcept{invocable}} and +\tcode{T} models \libconcept{move_constructible}. + +\pnum +\mandates +\tcode{is_same_v>, optional>} is \tcode{true}. + +\pnum +\effects +Equivalent to: +\begin{codeblock} +if (*this) { + return std::move(*this); +} else { + return std::forward(f)(); +} +\end{codeblock} +\end{itemdescr} + +\rSec3[optional.mod]{Modifiers} + +\begin{addedblock} +\rSec2[optional.optional.ref]{Partial specialization of \tcode{optional} for reference types} + +\rSec3[optional.optional.ref.general]{General} +\begin{codeblock} +namespace std { + template + class optional { + public: + using value_type = T; + using iterator = @\impdefnc@; // see~\ref{optional.ref.iterators} + + public: + // \ref{optional.ref.ctor}, constructors + constexpr optional() noexcept = default; + constexpr optional(nullopt_t) noexcept : optional() {} + constexpr optional(const optional& rhs) noexcept = default; + + template + constexpr explicit optional(in_place_t, Arg&& arg); + template + constexpr explicit(@\seebelow@) optional(U&& u) noexcept(@\seebelow@); + template + constexpr explicit(@\seebelow@) optional(optional& rhs) noexcept(@\seebelow@); + template + constexpr explicit(@\seebelow@) optional(const optional& rhs) noexcept(@\seebelow@); + template + constexpr explicit(@\seebelow@) optional(optional&& rhs) noexcept(@\seebelow@); + template + constexpr explicit(@\seebelow@) optional(const optional&& rhs) noexcept(@\seebelow@); + + constexpr ~optional() = default; + + // \ref{optional.ref.assign}, assignment + constexpr optional& operator=(nullopt_t) noexcept; + constexpr optional& operator=(const optional& rhs) noexcept = default; + + template constexpr T& emplace(U&& u) noexcept(@\seebelow@); + + // \ref{optional.ref.swap}, swap + constexpr void swap(optional& rhs) noexcept; + + // \ref{optional.iterators}, iterator support + constexpr iterator begin() const noexcept; + constexpr iterator end() const noexcept; + + // \ref{optional.ref.observe}, observers + constexpr T* operator->() const noexcept; + constexpr T& operator*() const noexcept; + constexpr explicit operator bool() const noexcept; + constexpr bool has_value() const noexcept; + constexpr T& value() const; // freestanding-deleted + template> constexpr remove_cv_t value_or(U&& u) const; + + // \ref{optional.ref.monadic}, monadic operations + template constexpr auto and_then(F&& f) const; + template constexpr optional> transform(F&& f) const; + template constexpr optional or_else(F&& f) const; + + // \ref{optional.ref.mod}, modifiers + constexpr void reset() noexcept; + + private: + T* @\exposid{val}@ = nullptr; // \expos + + // \ref{optional.ref.expos}, exposition only helper functions + template + constexpr void @\exposid{convert-ref-init-val}@(U&& u); // \expos + }; +} +\end{codeblock} + +\pnum +An object of \tcode{optional} \defnx{contains a value}{contains a value!\idxcode{optional.ref}} if and only if \tcode{\exposid{val} != nullptr} is \tcode{true}. +When an \tcode{optional} contains a value, the \defnx{contained value}{contained value!\idxcode{optional.ref}} is a reference to \tcode{\exposid{*val}}. + +\rSec3[optional.ref.ctor]{Constructors} + +\begin{itemdecl} +template +constexpr explicit optional(in_place_t, Arg&& arg); +\end{itemdecl} + +\begin{itemdescr} + \pnum + \constraints + \begin{itemize} + \item \tcode{is_constructible_v} is \tcode{true}, and + \item \tcode{reference_constructs_from_temporary_v} is \tcode{false}. + \end{itemize} + + \pnum + \effects + Equivalent to: \tcode{\exposid{convert-ref-init-val}(std::forward(arg))}. + + \pnum + \ensures + \tcode{*this} contains a value. +\end{itemdescr} + +\begin{itemdecl} +template +constexpr explicit(!is_convertible_v) +optional(U&& u) noexcept(is_nothrow_constructible_v); +\end{itemdecl} + +\begin{itemdescr} + \pnum + \constraints + \begin{itemize} + \item \tcode{is_same_v, optional>} is \tcode{false}, + \item \tcode{is_same_v, in_place_t>} is \tcode{false}, and + \item \tcode{is_constructible_v} is \tcode{true}. + \end{itemize} + + \pnum + \effects + Equivalent to: \tcode{\exposid{convert-ref-init-val}(std::forward(u))}. + + \pnum + \ensures + \tcode{*this} contains a value. + + \pnum + \remarks + This constructor is defined as deleted if +\begin{codeblock} +reference_constructs_from_temporary_v +\end{codeblock} + is \tcode{true}. +\end{itemdescr} + +\begin{itemdecl} +template +constexpr explicit(!is_convertible_v) +optional(optional& rhs) noexcept(is_nothrow_constructible_v); +\end{itemdecl} + +\begin{itemdescr} + \pnum + \constraints + \begin{itemize} + \item \tcode{is_same_v, optional>} is \tcode{false}, + \item \tcode{is_same_v} is \tcode{false}, and + \item \tcode{is_constructible_v} is \tcode{true}. + \end{itemize} + + \pnum + \effects + Equivalent to: + \begin{codeblock} +if (rhs.has_value()) @\exposid{convert-ref-init-val}@(*rhs); + \end{codeblock} + + \pnum + \remarks + This constructor is defined as deleted if +\begin{codeblock} +reference_constructs_from_temporary_v +\end{codeblock} + is \tcode{true}. +\end{itemdescr} + + +\begin{itemdecl} +template +constexpr explicit(!is_convertible_v) +optional(const optional& rhs) noexcept(is_nothrow_constructible_v); +\end{itemdecl} + +\begin{itemdescr} + \pnum + \constraints + \begin{itemize} + \item \tcode{is_same_v, optional>} is \tcode{false}, + \item \tcode{is_same_v} is \tcode{false}, and + \item \tcode{is_constructible_v} is \tcode{true}. + \end{itemize} + + \pnum + \effects + Equivalent to: + \begin{codeblock} +if (rhs.has_value()) @\exposid{convert-ref-init-val}@(*rhs); + \end{codeblock} + + + \pnum + \remarks + This constructor is defined as deleted if +\begin{codeblock} +reference_constructs_from_temporary_v +\end{codeblock} + is \tcode{true}. +\end{itemdescr} + +\begin{itemdecl} +template +constexpr explicit(!is_convertible_v) +optional(optional&& rhs) noexcept(is_nothrow_constructible_v); +\end{itemdecl} + +\begin{itemdescr} + \pnum + \constraints + \begin{itemize} + \item \tcode{is_same_v, optional>} is \tcode{false}, + \item \tcode{is_same_v} is \tcode{false}, and + \item \tcode{is_constructible_v} is \tcode{true}. + \end{itemize} + + \pnum + \effects + Equivalent to: + \begin{codeblock} +if (rhs.has_value()) @\exposid{convert-ref-init-val}@(*std::move(rhs)); + \end{codeblock} + + \pnum + \remarks + This constructor is defined as deleted if +\begin{codeblock} +reference_constructs_from_temporary_v +\end{codeblock} + is \tcode{true}. +\end{itemdescr} + +\begin{itemdecl} +template +constexpr explicit(!is_convertible_v) +optional(const optional&& rhs) noexcept(is_nothrow_constructible_v); +\end{itemdecl} + +\begin{itemdescr} + \pnum + \constraints + \begin{itemize} + \item \tcode{is_same_v, optional>} is \tcode{false}, and + \item \tcode{is_same_v} is \tcode{false}. + \item \tcode{is_constructible_v} is \tcode{true}, + \end{itemize} + + \pnum + \effects + Equivalent to: + \begin{codeblock} +if (rhs.has_value()) @\exposid{convert-ref-init-val}@(*std::move(rhs)); + \end{codeblock} + + \pnum + \remarks + This constructor is defined as deleted if +\begin{codeblock} +reference_constructs_from_temporary_v +\end{codeblock} + is \tcode{true}. +\end{itemdescr} + + +\rSec3[optional.ref.assign]{Assignment} + +\begin{itemdecl} +constexpr optional& operator=(nullopt_t) noexcept; +\end{itemdecl} + +\begin{itemdescr} + \pnum + \effects + Assigns \tcode{nullptr} to \exposid{val}. + + \pnum + \ensures + \tcode{*this} does not contain a value. + + \pnum + \returns + \tcode{*this}. +\end{itemdescr} + + +\begin{itemdecl} +template +constexpr T& emplace(U&& u) noexcept(is_nothrow_constructible_v); +\end{itemdecl} + +\begin{itemdescr} + \pnum + \constraints + \begin{itemize} + \item \tcode{is_constructible_v} is \tcode{true}, and + \item \tcode{reference_constructs_from_temporary_v} is \tcode{false}. + \end{itemize} + + \pnum + \effects + Equivalent to: \tcode{\exposid{convert-ref-init-val}(std::forward(u))}. + + \pnum +\end{itemdescr} + + +\rSec3[optional.ref.swap]{Swap} + +\begin{itemdecl} +constexpr void swap(optional& rhs) noexcept; +\end{itemdecl} + +\begin{itemdescr} + \pnum + \effects + Equivalent to: \tcode{swap(\exposid{val}, rhs.\exposid{val})}. +\end{itemdescr} + + +\rSec3[optional.ref.iterators]{Iterator support} +\begin{itemdecl} +using iterator = @\impdef@; +\end{itemdecl} + +\begin{itemdescr} +\pnum +This type +models \libconcept{contiguous_iterator}\iref{iterator.concept.contiguous}, +meets the \oldconcept{RandomAccessIterator} requirements\iref{random.access.iterators}, and +meets the requirements for constexpr iterators\iref{iterator.requirements.general}, +with value type \tcode{remove_cv_t}. +The reference type is \tcode{T\&} for \tcode{iterator}. + +\pnum +All requirements on container iterators\iref{container.reqmts} apply to +\tcode{optional::iterator}. + +\end{itemdescr} + + +\begin{itemdecl} +constexpr iterator begin() const noexcept; +\end{itemdecl} + +\begin{itemdescr} + \pnum + \returns + If \tcode{has_value()} is \tcode{true}, + an iterator referring to \tcode{*\exposid{val}}. + Otherwise, a past-the-end iterator value. + +\end{itemdescr} + +\begin{itemdecl} +constexpr iterator end() const noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\returns +\tcode{begin() + has_value()}. +\end{itemdescr} + + + +\rSec3[optional.ref.observe]{Observers} + +\begin{itemdecl} +constexpr T* operator->() const noexcept; +\end{itemdecl} + +\begin{itemdescr} + \pnum + \hardexpects + \tcode{has_value()} is \tcode{true}. + + \pnum + \returns + \tcode{\exposid{val}}. + +\end{itemdescr} + +\begin{itemdecl} +constexpr T& operator*() const noexcept; +\end{itemdecl} + +\begin{itemdescr} + \pnum + \hardexpects + \tcode{has_value()} is \tcode{true}. + + \pnum + \returns + \tcode{*\exposid{val}}. + +\end{itemdescr} + +\begin{itemdecl} +constexpr explicit operator bool() const noexcept; +\end{itemdecl} + +\begin{itemdescr} + \pnum + \returns + \tcode{\exposid{val} != nullptr}. +\end{itemdescr} + +\begin{itemdecl} +constexpr bool has_value() const noexcept; +\end{itemdecl} + +\begin{itemdescr} + \pnum + \returns + \tcode{\exposid{val} != nullptr}. +\end{itemdescr} + +\begin{itemdecl} +constexpr T& value() const; +\end{itemdecl} + +\begin{itemdescr} + \pnum + \effects + Equivalent to: + \begin{codeblock} +return has_value() ? *@\exposid{val}@ : throw bad_optional_access(); + \end{codeblock} +\end{itemdescr} + +\begin{itemdecl} +template> constexpr remove_cv_t value_or(U&& u) const; +\end{itemdecl} + +\begin{itemdescr} + \pnum + Let \tcode{X} be \tcode{remove_cv_t}. + + \pnum + \mandates + \tcode{is_constructible_v \&\& is_convertible_v} is \tcode{true}. + + \pnum + \effects + Equivalent to: + \begin{codeblock} +return has_value() ? *@\exposid{val}@ : static_cast(std::forward(u)); + \end{codeblock} +\end{itemdescr} + + +\rSec3[optional.ref.monadic]{Monadic operations} + +\begin{itemdecl} +template +constexpr auto and_then(F&& f) const; +\end{itemdecl} + +\begin{itemdescr} + \pnum + Let \tcode{U} be \tcode{invoke_result_t}. + + \pnum + \mandates + \tcode{remove_cvref_t} is a specialization of \tcode{optional}. + + \pnum + \effects + Equivalent to: + \begin{codeblock} +if (has_value()) + return invoke(std::forward(f), *@\exposid{val}@); +else + return remove_cvref_t(); + \end{codeblock} +\end{itemdescr} + +\begin{itemdecl} +template +constexpr optional>> transform(F&& f) const; +\end{itemdecl} + +\begin{itemdescr} + \pnum + Let \tcode{U} be \tcode{remove_cv_t>}. + + \pnum + \mandates + The declaration +\begin{codeblock} +U u(invoke(std::forward(f), *@\exposid{val}@)); +\end{codeblock} +is well-formed for some invented variable \tcode{u}. +\begin{note} +There is no requirement that \tcode{U} is movable\iref{dcl.init.general}. +\end{note} + + \pnum + \returns + If \tcode{*this} contains a value, an \tcode{optional} object + whose contained value is direct-non-list-initialized with + \tcode{invoke(std::forward(f), *\exposid{val})}; + otherwise, \tcode{optional()}. +\end{itemdescr} + +\begin{itemdecl} +template +constexpr optional or_else(F&& f) const; +\end{itemdecl} + +\begin{itemdescr} + \pnum + \constraints + \tcode{F} models \tcode{\libconcept{invocable}}. + + \pnum + \mandates + \tcode{is_same_v>, optional>} is \tcode{true}. + + \pnum + \effects + Equivalent to: + \begin{codeblock} +if (has_value()) + return *@\exposid{val}@; +else + return std::forward(f)(); + \end{codeblock} +\end{itemdescr} + + +\rSec3[optional.ref.mod]{Modifiers} + +\begin{itemdecl} +constexpr void reset() noexcept; +\end{itemdecl} + +\begin{itemdescr} + \pnum + \effects + Assigns \tcode{nullptr} to \exposid{val}. + + \pnum + \ensures + \tcode{*this} does not contain a value. +\end{itemdescr} + +\rSec3[optional.ref.expos]{Exposition only helper functions} +\begin{itemdecl} +template +constexpr void @\exposid{convert-ref-init-val}@(U&& u); // \expos +\end{itemdecl} + +\begin{itemdescr} + \pnum + \effects + Creates a variable \tcode{r} as if by \tcode{T\& r(std::forward(u));} and then initializes \exposid{val} with \tcode{addressof(r)}. +\end{itemdescr} + +\end{addedblock} + +\rSec2[optional.nullopt]{No-value state indicator} +\rSec2[optional.bad.access]{Class \tcode{bad_optional_access}} +\rSec2[optional.relops]{Relational operators} +\rSec2[optional.specalg]{Specialized algorithms} +\indexlibrarymember{swap}{optional}% +\begin{itemdecl} +template + constexpr void swap(optional& x, optional& y) noexcept(noexcept(x.swap(y))); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\begin{removedblock} +\constraints +\tcode{is_move_constructible_v} is \tcode{true} and +\tcode{is_swappable_v} is \tcode{true}. +\end{removedblock} + +\begin{addedblock} +\constraints +\tcode{is_reference_v || (is_move_constructible_v \&\& is_swappable_v)} is \tcode{true}. +\end{addedblock} + +\pnum +\effects +Calls \tcode{x.swap(y)}. +\end{itemdescr} + + +\indexlibraryglobal{make_optional}% +\begin{itemdecl} +template constexpr optional> make_optional(T&& v); +\end{itemdecl} + +\begin{itemdescr} +\begin{addedblock} +\pnum +\constraints +The call to \tcode{make_optional} does not use +an explicit \grammarterm{template-argument-list} that +begins with a type \grammarterm{template-argument}. +\end{addedblock} + +\pnum +\returns +\tcode{optional>(std::forward(v))}. +\end{itemdescr} + +\indexlibraryglobal{make_optional}% +\begin{itemdecl} +template + constexpr optional make_optional(Args&&... args); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: \tcode{return optional(in_place, std::forward(args)...);} +\end{itemdescr} + +\indexlibraryglobal{make_optional}% +\begin{itemdecl} +template + constexpr optional make_optional(initializer_list il, Args&&... args); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: \tcode{return optional(in_place, il, std::forward(args)...);} +\end{itemdescr} + + +\rSec2[optional.hash]{Hash support} + +\begin{addedblock} +\Sec2[version.syn]{Feature-test macro} +Change the values of the following macro definition to [version.syn], header synopsis, with the value selected by the editor to reflect the date of adoption of this paper: + +\begin{codeblock} + #define __cpp_lib_freestanding_optional 20XXXXL // also in + #define __cpp_lib_optional 20XXXXL // also in +\end{codeblock} +\end{addedblock} + +%%% Local Variables: +%%% mode: LaTeX +%%% TeX-master: "new" +%%% End: diff --git a/papers/wg21-latex/requirements.txt b/papers/wg21-latex/requirements.txt new file mode 100644 index 0000000..a9f49e0 --- /dev/null +++ b/papers/wg21-latex/requirements.txt @@ -0,0 +1 @@ +pygments diff --git a/papers/wg21-latex/std.tex b/papers/wg21-latex/std.tex new file mode 100644 index 0000000..dcf8cc1 --- /dev/null +++ b/papers/wg21-latex/std.tex @@ -0,0 +1,89 @@ +%% main file for the C++ standard. +%% + +%%-------------------------------------------------- +%% basics +\documentclass[a4paper,10pt,oneside,openany,final]{memoir} + +\input{common} + +\begin{document} +\chapterstyle{cppstd} +\pagestyle{cpppage} + +%%-------------------------------------------------- +%% configuration +\input{config} + +%%-------------------------------------------------- +%% front matter +\frontmatter +% \include{front} + +%%-------------------------------------------------- +%% main body of the document +\mainmatter +\setglobalstyles + +\include{intro} +% \include{lex} +% \include{basic} +% \include{expressions} +% \include{statements} +% \include{declarations} +% \include{modules} +% \include{classes} +% \include{overloading} +% \include{templates} +% \include{exceptions} +% \include{preprocessor} +% \include{lib-intro} +% \include{support} +% \include{concepts} +% \include{diagnostics} +% \include{memory} +% \include{meta} +% \include{utilities} +% \include{containers} +% \include{iterators} +% \include{ranges} +% \include{algorithms} +% \include{strings} +% \include{text} +% \include{numerics} +% \include{time} +% \include{iostreams} +% \include{threads} +% \include{exec} + +%%-------------------------------------------------- +%% appendices +\appendix +\chapterstyle{cppannex} + +% \include and \addtocontents don't mix; see +% https://tex.stackexchange.com/questions/13914/toc-numbering-problem +\makeatletter +\immediate\write\@auxout{\noexpand\@writefile{toc}{\noexpand\let\noexpand\chapternumberlinebox\noexpand\annexnumberlinebox}} +\makeatother + +\numberwithin{table}{chapter} + +% ... end grammar extraction. +\immediate\closeout\gramout + +% \include{grammar} +% \include{limits} +% \include{compatibility} +% \include{future} +% \include{uax31} + +%%-------------------------------------------------- +%% back matter +\backmatter +\include{back} + + +%%-------------------------------------------------- +%% End of document +\end{document} diff --git a/papers/wg21-latex/stdtex/layout.tex b/papers/wg21-latex/stdtex/layout.tex new file mode 100644 index 0000000..98564d8 --- /dev/null +++ b/papers/wg21-latex/stdtex/layout.tex @@ -0,0 +1,83 @@ +%!TEX root = std.tex +%% layout.tex -- set overall page appearance + +%%-------------------------------------------------- +%% set page size, type block size, type block position + +\setlrmarginsandblock{2.245cm}{2.245cm}{*} +\setulmarginsandblock{2.5cm}{2.5cm}{*} + +%%-------------------------------------------------- +%% set header and footer positions and sizes + +\setheadfoot{3\onelineskip}{4\onelineskip} +\setheaderspaces{*}{2\onelineskip}{*} + +%%-------------------------------------------------- +%% make miscellaneous adjustments, then finish the layout +\setmarginnotes{7pt}{7pt}{0pt} +\checkandfixthelayout + +%%-------------------------------------------------- +%% If there is insufficient stretchable vertical space on a page, +%% TeX will not properly consider penalties for a good page break, +%% even if \raggedbottom (default for oneside, not for twoside) +%% is in effect. +\raggedbottom +\addtolength{\topskip}{0pt plus 20pt} + +%%-------------------------------------------------- +%% Place footnotes at the bottom of the page, rather +%% than immediately following the main text. +\feetatbottom + +%%-------------------------------------------------- +%% Paragraph and bullet numbering + +% create a new counter that resets for each new subclause +\newcommand{\newsubclausecounter}[1]{ +\newcounter{#1} +\counterwithin{#1}{chapter} +\counterwithin{#1}{section} +\counterwithin{#1}{subsection} +\counterwithin{#1}{subsubsection} +\counterwithin{#1}{paragraph} +\counterwithin{#1}{subparagraph} +} + +\newsubclausecounter{Paras} +\newcounter{Bullets1}[Paras] +\newcounter{Bullets2}[Bullets1] +\newcounter{Bullets3}[Bullets2] +\newcounter{Bullets4}[Bullets3] + +\makeatletter +\newcommand{\parabullnum}[2]{% +\stepcounter{#1}% +\noindent\makebox[0pt][l]{\makebox[#2][r]{% +\scriptsize\raisebox{.7ex}% +{% +\ifnum \value{Paras}>0 +\ifnum \value{Bullets1}>0 (\fi% + \arabic{Paras}% +\ifnum \value{Bullets1}>0 .\arabic{Bullets1}% +\ifnum \value{Bullets2}>0 .\arabic{Bullets2}% +\ifnum \value{Bullets3}>0 .\arabic{Bullets3}% +\fi\fi\fi% +\ifnum \value{Bullets1}>0 )\fi% +\fi% +}% +\hspace{\@totalleftmargin}\quad% +}}} +\makeatother + +% Register our intent to number the next paragraph. Don't actually number it +% yet, because we might have a paragraph break before we see its contents (for +% example, if the paragraph begins with a note or example). +\def\pnum{% +\global\def\maybeaddpnum{\global\def\maybeaddpnum{}\parabullnum{Paras}{0pt}}% +\everypar=\expandafter{\the\everypar\maybeaddpnum}% +} + +% Leave more room for section numbers in TOC +\cftsetindents{section}{1.5em}{3.0em} diff --git a/papers/wg21-latex/stdtex/macros.tex b/papers/wg21-latex/stdtex/macros.tex new file mode 100644 index 0000000..f47b653 --- /dev/null +++ b/papers/wg21-latex/stdtex/macros.tex @@ -0,0 +1,803 @@ +%!TEX root = std.tex + +% Definitions and redefinitions of special commands +% +% For macros that are likely to appear inside code blocks, we may provide a +% "macro length" correction value, which can be used to determine the effective +% column number where an emitted character appears, by adding the macro length, +% plus 2 for the escape-'@'s, plus 2 for the braces, to the real column number. +% +% For example: +% +% \newcommand{\foo[1]{#1} % macro length: 4 +% +% \begin{codeblock} +% int a = 10; // comment on column 20 +% int b = @\foo{x}@; // comment also on effective column 20 +% +% Here the real column of the second comment start is offset by 8 (4 + macro length). + +%%-------------------------------------------------- +%% Difference markups +%%-------------------------------------------------- +\definecolor{addclr}{rgb}{0,.6,.6} +\definecolor{remclr}{rgb}{1,0,0} +\definecolor{noteclr}{rgb}{0,0,1} + +\renewcommand{\added}[1]{\textcolor{addclr}{\uline{#1}}} +\newcommand{\removed}[1]{\textcolor{remclr}{\sout{#1}}} +\renewcommand{\changed}[2]{\removed{#1}\added{#2}} + +\newcommand{\nbc}[1]{[#1]\ } +\newcommand{\addednb}[2]{\added{\nbc{#1}#2}} +\newcommand{\removednb}[2]{\removed{\nbc{#1}#2}} +\newcommand{\changednb}[3]{\removednb{#1}{#2}\added{#3}} +\newcommand{\remitem}[1]{\item\removed{#1}} + +\newcommand{\ednote}[1]{\textcolor{noteclr}{[Editor's note: #1] }} + +\newenvironment{addedblock}{\color{addclr}}{\color{black}} +\newenvironment{removedblock}{\color{remclr}}{\color{black}} + +%%------------------------------------------------------------- +%% Grammar extraction. +%% Assumes that the output file \gramout is managed externally. +%%------------------------------------------------------------- +\makeatletter +\newcommand{\gramWrite}[1]{\protected@write\gramout{}{#1}} +\newcommand{\meaningbody}[1]{\expandafter\strip@prefix\meaning#1} +\makeatother + +\newcommand{\gramSec}[2][]{\gramWrite{% +\string\rSec1\string[\string#1\string]\string{\string#2\string}}} + +%%-------------------------------------------------- +% Escaping for index entries. Replaces ! with "! throughout its argument. +%%-------------------------------------------------- +\def\indexescape#1{\doindexescape#1\stopindexescape!\doneindexescape} +\def\doindexescape#1!{#1"!\doindexescape} +\def\stopindexescape#1\doneindexescape{} + +%%-------------------------------------------------- +%% Cross-references. +%%-------------------------------------------------- +\newcommand{\addxref}[1]{% + \hypertarget{#1}{}% + \glossary[xrefindex]{\indexescape{#1}}{(\ref{\indexescape{#1}})}% +} + +%%-------------------------------------------------- +%% Sectioning macros. +% Each section has a depth, an automatically generated section +% number, a name, and a short tag. The depth is an integer in +% the range [0,5]. (If it proves necessary, it wouldn't take much +% programming to raise the limit from 5 to something larger.) +%%-------------------------------------------------- + +% Set the xref label for a clause to be "Clause n", not just "n". +\makeatletter +\newcommand{\customlabel}[3]{% +\@bsphack \protected@write\@auxout{}{\string\newlabel{#1}{{#3}{\thepage}{}{#2.\thechapter}{}}} \@esphack% +} +\makeatother +\newcommand{\clauselabel}[1]{\customlabel{#1}{chapter}{Clause \thechapter}} +\newcommand{\annexlabel}[1]{\customlabel{#1}{appendix}{Annex \thechapter}} + +% Use prefix "Annex" in the table of contents +\newcommand{\annexnumberlinebox}[2]{Annex #2\space} + +% The basic sectioning command. Example: +% \Sec1[intro.scope]{Scope} +% defines a first-level section whose name is "Scope" and whose short +% tag is intro.scope. The square brackets are mandatory. +\def\Sec#1[#2]#3{% +\addxref{#2}% +\ifcase#1\let\s=\chapter\let\l=\clauselabel + \or\let\s=\section\let\l=\label + \or\let\s=\subsection\let\l=\label + \or\let\s=\subsubsection\let\l=\label + \or\let\s=\paragraph\let\l=\label + \or\let\s=\subparagraph\let\l=\label + \fi% +\s[#3]{#3\hfill[#2]}\l{#2}% +} + +% A convenience feature (mostly for the convenience of the Project +% Editor, to make it easy to move around large blocks of text): +% the \rSec macro is just like the \Sec macro, except that depths +% relative to a global variable, SectionDepthBase. So, for example, +% if SectionDepthBase is 1, +% \rSec1[temp.arg.type]{Template type arguments} +% is equivalent to +% \Sec2[temp.arg.type]{Template type arguments} +\newcounter{SectionDepthBase} +\newcounter{SectionDepth} + +\def\rSec#1[#2]#3{% +\setcounter{SectionDepth}{#1} +\addtocounter{SectionDepth}{\value{SectionDepthBase}} +\Sec{\arabic{SectionDepth}}[#2]{#3}} + +%%-------------------------------------------------- +% Bibliography +%%-------------------------------------------------- +\newcommand{\supercite}[1]{\textsuperscript{\cite{#1}}} + +%%-------------------------------------------------- +% Indexing +%%-------------------------------------------------- + +% Layout of general index +\newcommand{\rSecindex}[2]{\section*{#2}\pdfbookmark[1]{#2}{pdf.idx.#1.#2}\label{idx.#1.#2}} + +% locations +\newcommand{\indextext}[1]{\index[generalindex]{#1}} +\newcommand{\indexlibrary}[1]{\index[libraryindex]{#1}} +\newcommand{\indexhdr}[1]{\index[headerindex]{\idxhdr{#1}}} +\newcommand{\indexconcept}[1]{\index[conceptindex]{#1}} +\newcommand{\indexgram}[1]{\index[grammarindex]{#1}} + +% Collation helper: When building an index key, replace all macro definitions +% in the key argument with a no-op for purposes of collation. +\newcommand{\nocode}[1]{#1} +\newcommand{\idxmname}[1]{\_\_#1\_\_} +\newcommand{\idxCpp}{C++} + +% \indeximpldef synthesizes a collation key from the argument; that is, an +% invocation \indeximpldef{arg} emits an index entry `key@arg`, where `key` +% is derived from `arg` by replacing the following list of commands with their +% bare content. This allows, say, collating plain text and code. +\newcommand{\indeximpldef}[1]{% +\let\otextup\textup% +\let\textup\nocode% +\let\otcode\tcode% +\let\tcode\nocode% +\let\oexposid\exposid% +\let\exposid\nocode% +\let\ogrammarterm\grammarterm% +\let\grammarterm\nocode% +\let\omname\mname% +\let\mname\idxmname% +\let\oCpp\Cpp% +\let\Cpp\idxCpp% +\let\oBreakableUnderscore\BreakableUnderscore% See the "underscore" package. +\let\BreakableUnderscore\textunderscore% +\edef\x{#1}% +\let\tcode\otcode% +\let\exposid\oexposid% +\let\grammarterm\gterm% +\let\mname\omname% +\let\Cpp\oCpp% +\let\BreakableUnderscore\oBreakableUnderscore% +\index[impldefindex]{\x@#1}% +\let\grammarterm\ogrammarterm% +\let\textup\otextup% +} + +\newcommand{\indexdefn}[1]{\indextext{#1}} +\newcommand{\idxbfpage}[1]{\textbf{\hyperpage{#1}}} +\newcommand{\indexgrammar}[1]{\indextext{#1}\indexgram{#1|idxbfpage}} +% This command uses the "cooked" \indeximpldef command to emit index +% entries; thus they only work for simple index entries that do not contain +% special indexing instructions. +\newcommand{\impldef}[1]{\indeximpldef{#1}imple\-men\-ta\-tion-defined} +% \impldefplain passes the argument directly to the index, allowing you to +% use special indexing instructions (!, @, |). +\newcommand{\impldefplain}[1]{\index[impldefindex]{#1}implementation-defined} + +% appearance +% avoid \tcode to avoid falling victim of \tcode redefinition in CodeBlockSetup +\newcommand{\idxcode}[1]{#1@\CodeStylex{#1}} +\newcommand{\idxconcept}[1]{#1@\CodeStylex{#1}} +\newcommand{\idxexposconcept}[1]{#1@\CodeStylex{\placeholder{#1}}} +\newcommand{\idxhdr}[1]{#1@\CodeStylex{<#1>}} +\newcommand{\idxgram}[1]{#1@\gterm{#1}} +\newcommand{\idxterm}[1]{#1@\term{#1}} +\newcommand{\idxxname}[1]{__#1@\xname{#1}} + +% library index entries +\newcommand{\indexlibraryglobal}[1]{\indexlibrary{\idxcode{#1}}} +\newcommand{\indexlibrarymisc}[2]{\indexlibrary{#1!#2}} +\newcommand{\indexlibraryctor} [1]{\indexlibrarymisc{\idxcode{#1}}{constructor}} +\newcommand{\indexlibrarydtor} [1]{\indexlibrarymisc{\idxcode{#1}}{destructor}} +\newcommand{\indexlibraryzombie}[1]{\indexlibrarymisc{\idxcode{#1}}{zombie}} + +% class member library index +\newcommand{\indexlibraryboth}[2]{\indexlibrarymisc{#1}{#2}\indexlibrarymisc{#2}{#1}} +\newcommand{\indexlibrarymember}[2]{\indexlibraryboth{\idxcode{#1}}{\idxcode{#2}}} +\newcommand{\indexlibrarymemberexpos}[2]{\indexlibraryboth{\idxcode{#1}}{#2@\exposid{#2}}} +\newcommand{\indexlibrarymemberx}[2]{\indexlibrarymisc{\idxcode{#1}}{\idxcode{#2}}} + +\newcommand{\libglobal}[1]{\indexlibraryglobal{#1}#1} +\newcommand{\libmember}[2]{\indexlibrarymember{#1}{#2}#1} +\newcommand{\libspec}[2]{\indexlibrarymemberx{#1}{#2}#1} + +% index for macros +% \libmacro is suitable for use directly in header synopses to add the macro +% to both the text and the library index. \libxmacro does the same, but +% prepends a well-rendered double underscore. +\newcommand{\libmacro}[1]{\indexlibraryglobal{#1}\CodeStylex{#1}} +\newcommand{\libxmacro}[1]{\indexlibraryglobal{\idxxname{#1}}\CodeStylex{\xname{#1}}} + +% index for library headers +\newcommand{\libheaderx}[2]{\indexhdr{#1}\tcode{<#2>}} +\newcommand{\libheader}[1]{\libheaderx{#1}{#1}} +\newcommand{\indexheader}[1]{\index[headerindex]{\idxhdr{#1}|idxbfpage}} +\newcommand{\libheaderdef}[1]{\indexheader{#1}\tcode{<#1>}} +\newcommand{\libnoheader}[1]{\indextext{\idxhdr{#1}!absence thereof}\tcode{<#1>}} +\newcommand{\libheaderrefxx}[3]{\libheaderx{#1}{#2}\iref{#3}} +\newcommand{\libheaderrefx}[2]{\libheaderrefxx{#1}{#1}{#2}} +\newcommand{\libheaderref}[1]{\libheaderrefx{#1}{#1.syn}} +\newcommand{\libdeprheaderref}[1]{\libheaderrefx{#1}{depr.#1.syn}} + +%%-------------------------------------------------- +% General code style +%%-------------------------------------------------- +\newcommand{\CodeStyle}{\ttfamily} +\newcommand{\CodeStylex}[1]{\texttt{\protect\frenchspacing #1}} +\makeatletter +% Append `\@` to restore proper sentence spacing in text mode. This insertion +% happens only during normal typesetting; it is suppressed for .idx generation. +\newcommand{\CodeStylexGuarded}[1]{\CodeStylex{#1\ifmmode\else\ifx\protect\@typeset@protect\@\fi\fi}} +\makeatother + +\definecolor{grammar-gray}{gray}{0.2} + +% General grammar style +\newcommand{\GrammarStylex}[1]{\textcolor{grammar-gray}{\textsf{\textit{#1}}}} + +% Code and definitions embedded in text. +\newcommand{\tcode}[1]{\CodeStylexGuarded{#1}} +\newcommand{\term}[1]{\textit{#1}} +\newcommand{\gterm}[1]{\GrammarStylex{#1}} +\newcommand{\fakegrammarterm}[1]{\gterm{#1}} +\newcommand{\keyword}[1]{\texorpdfstring{\tcode{#1}\protect\indextext{\idxcode{#1}!keyword}}{#1}} % macro length: 8 +\newcommand{\grammarterm}[1]{\texorpdfstring{\protect\indexgram{\idxgram{#1}}\gterm{#1}}{#1}} +\newcommand{\grammartermnc}[1]{\indexgram{\idxgram{#1}}\gterm{#1\nocorr}} +\newcommand{\regrammarterm}[1]{\textit{#1}} +\newcommand{\placeholder}[1]{\textit{#1}} % macro length: 12 +\newcommand{\placeholdernc}[1]{\textit{#1\nocorr}} % macro length: 14 +\newcommand{\exposid}[1]{\tcode{\placeholder{#1}}} % macro length: 8 +\newcommand{\exposidnc}[1]{\tcode{\placeholdernc{#1}}\itcorr[-1]} % macro length: 10 +\newcommand{\defnxname}[1]{\indextext{\idxxname{#1}}\xname{#1}} +\newcommand{\defnlibxname}[1]{\indexlibrary{\idxxname{#1}}\xname{#1}} + +% Non-compound defined term. +\newcommand{\defn}[1]{\defnx{#1}{#1}} +% Defined term with different index entry. +\newcommand{\defnx}[2]{\indexdefn{#2}\textit{#1}} +% Compound defined term with 'see' for primary term. +% Usage: \defnadj{trivial}{class} +\newcommand{\defnadj}[2]{\indextext{#1 #2|see{#2, #1}}\indexdefn{#2!#1}\textit{#1 #2}} +% Compound defined term with a different form for the primary noun. +% Usage: \defnadjx{scalar}{types}{type} +\newcommand{\defnadjx}[3]{\indextext{#1 #3|see{#3, #1}}\indexdefn{#3!#1}\textit{#1 #2}} + +% Macros used for the grammar of std::format format specifications. +% FIXME: For now, keep the format grammar productions out of the index, since +% they conflict with the main grammar. +% Consider renaming these en masse (to fmt-* ?) to avoid this problem. +\newcommand{\fmtnontermdef}[1]{{\BnfNontermshape#1\itcorr}\textnormal{:}} +\newcommand{\fmtgrammarterm}[1]{\gterm{#1}} + +%%-------------------------------------------------- +%% allow line break if needed for justification +%%-------------------------------------------------- +\newcommand{\brk}{\discretionary{}{}{}} + +%%-------------------------------------------------- +%% Macros for funky text +%%-------------------------------------------------- +\newcommand{\Cpp}{\texorpdfstring{C\kern-0.05em\protect\raisebox{.35ex}{\textsmaller[2]{+\kern-0.05em+}}}{C++}} +\newcommand{\CppIII}{\Cpp{} 2003} +\newcommand{\CppXI}{\Cpp{} 2011} +\newcommand{\CppXIV}{\Cpp{} 2014} +\newcommand{\CppXVII}{\Cpp{} 2017} +\newcommand{\CppXX}{\Cpp{} 2020} +\newcommand{\CppXXIII}{\Cpp{} 2023} +\newcommand{\CppXXVI}{\Cpp{} 2026} +\newcommand{\IsoCUndated}{ISO/IEC 9899} +\newcommand{\IsoC}{\IsoCUndated{}:2024} +\newcommand{\IsoFloatUndated}{ISO/IEC 60559} +\newcommand{\IsoPosixUndated}{ISO/IEC/IEEE 9945} +\newcommand{\IsoPosix}{\IsoPosixUndated{}:2009} +\newcommand{\opt}[1]{#1\ensuremath{_\mathit{\color{black}opt}}} +\newcommand{\bigoh}[1]{\ensuremath{\mathscr{O}(#1)}} + +% Make all tildes a little larger to avoid visual similarity with hyphens. +\renewcommand{\~}{\textasciitilde} +\let\OldTextAsciiTilde\textasciitilde +\renewcommand{\textasciitilde}{\protect\raisebox{-0.17ex}{\larger\OldTextAsciiTilde}} +\newcommand{\caret}{\char`\^} + +%%-------------------------------------------------- +%% States and operators +%%-------------------------------------------------- +\newcommand{\state}[2]{\tcode{#1}\ensuremath{_{#2}}} +\newcommand{\bitand}{\ensuremath{\mathbin{\mathsf{bitand}}}} +\newcommand{\bitor}{\ensuremath{\mathbin{\mathsf{bitor}}}} +\newcommand{\xor}{\ensuremath{\mathbin{\mathsf{xor}}}} +\newcommand{\rightshift}{\ensuremath{\mathbin{\mathsf{rshift}}}} +\newcommand{\leftshift}[1]{\ensuremath{\mathbin{\mathsf{lshift}_{#1}}}} + +%% Notes and examples +\newcommand{\noteintro}[1]{[\textit{#1}:} +\newcommand{\noteoutro}[1]{\textit{\,---\,#1}\kern.5pt]} + +% \newnoteenvironment{ENVIRON}{BEGIN TEXT}{END TEXT} +% Creates a note-like environment beginning with BEGIN TEXT and +% ending with END TEXT. A counter with name ENVIRON indicates the +% number of this kind of note / example that has occurred in this +% subclause. +% Use tailENVIRON to avoid inserting a \par at the end. +\newcommand{\newnoteenvironment}[3]{ +\newsubclausecounter{#1} +\newenvironment{tail#1} +{\par\small\penalty -200\stepcounter{#1}\noteintro{#2}} +{\noteoutro{#3}} +\newenvironment{#1} +{\begin{tail#1}} +% \small\par is for C++20 post-DIS compatibility +{\end{tail#1}\small\par\penalty -200} + +} + +\newnoteenvironment{note}{Note \arabic{note}}{end note} +\newnoteenvironment{example}{Example \arabic{example}}{end example} + +\makeatletter +\let\footnote\@undefined +\global\newsavebox{\@tempfootboxa} % must be global, to escape tables/figures +\newsavebox{\@templfootbox} +\newenvironment{footnote}{% + \unskip\footnotemark% no space before the mark + \normalfont% + \footnotesize% text size for rendering the footnote text + \begin{lrbox}{\@templfootbox}% temporarily save to local box +}{% + \end{lrbox}% + \global\setbox\@tempfootboxa\hbox{\unhbox\@templfootbox}% copy to global box + \footnotetext{\unhbox\@tempfootboxa}% +} +\makeatother + +%% Library function descriptions +\newcommand{\Fundescx}[1]{\textit{#1}} +\newcommand{\Fundesc}[1]{\Fundescx{#1}:\space} +\newcommand{\recommended}{\Fundesc{Recommended practice}} +\newcommand{\required}{\Fundesc{Required behavior}} +\newcommand{\constraints}{\Fundesc{Constraints}} +\newcommand{\mandates}{\Fundesc{Mandates}} +\newcommand{\constantwhen}{\Fundesc{Constant When}} +\newcommand{\expects}{\Fundesc{Preconditions}} +\newcommand{\hardexpects}{\Fundesc{Hardened preconditions}} +\newcommand{\effects}{\Fundesc{Effects}} +\newcommand{\ensures}{\Fundesc{Postconditions}} +\newcommand{\returns}{\Fundesc{Returns}} +\newcommand{\throws}{\Fundesc{Throws}} +\newcommand{\default}{\Fundesc{Default behavior}} +\newcommand{\complexity}{\Fundesc{Complexity}} +\newcommand{\remarks}{\Fundesc{Remarks}} +\newcommand{\errors}{\Fundesc{Error conditions}} +\newcommand{\sync}{\Fundesc{Synchronization}} +\newcommand{\implimits}{\Fundesc{Implementation limits}} +\newcommand{\result}{\Fundesc{Result}} +\newcommand{\returntype}{\Fundesc{Return type}} +\newcommand{\ctype}{\Fundesc{Type}} +\newcommand{\templalias}{\Fundesc{Alias template}} + +%% Cross-reference +\newcommand{\xref}[1]{\textsc{See also:}\space #1} +\newcommand{\xrefc}[1]{\xref{\IsoC{}, #1}} +\newcommand{\termref}[3]{\textit{#2}{#3}\iref{#1}} % in Clause 3 + +%% Inline comma-separated parenthesized references +\ExplSyntaxOn +\NewDocumentCommand \iref { m } { + \clist_set:Nx \l_tmpa_clist { #1 } + \nolinebreak[3] ~ ( + \clist_map_inline:Nn \l_tmpa_clist { + \clist_put_right:Nn \g_tmpa_clist { \ref{##1} } + } + \clist_use:Nn \g_tmpa_clist { ,~ } + ) + \clist_clear:N \g_tmpa_clist +} +\ExplSyntaxOff + +%% Inline non-parenthesized table reference (override memoir's \tref) +\providecommand{\tref}{} +\renewcommand{\tref}[1]{\hyperref[tab:#1]{\tablerefname \nolinebreak[3] \ref*{tab:#1}}} +%% Inline non-parenthesized figure reference (override memoir's \fref) +\providecommand{\fref}{} +\renewcommand{\fref}[1]{\hyperref[fig:#1]{\figurerefname \nolinebreak[3] \ref*{fig:#1}}} + +%% NTBS, etc. +\verbtocs{\StrTextsmaller}|\textsmaller[1]{| +\verbtocs{\StrTextsc}|\textsc{| +\verbtocs{\StrClosingbrace}|}| +\newcommand{\ucode}[1]{% + \textsc{u}% + \textsmaller[2]{\kern-0.05em\protect\raisebox{.25ex}{+}}% + \begingroup% + \def\temp{#1}% + \StrSubstitute{\temp}{0}{X0Z}[\temp]% + \StrSubstitute{\temp}{1}{X1Z}[\temp]% + \StrSubstitute{\temp}{2}{X2Z}[\temp]% + \StrSubstitute{\temp}{3}{X3Z}[\temp]% + \StrSubstitute{\temp}{4}{X4Z}[\temp]% + \StrSubstitute{\temp}{5}{X5Z}[\temp]% + \StrSubstitute{\temp}{6}{X6Z}[\temp]% + \StrSubstitute{\temp}{7}{X7Z}[\temp]% + \StrSubstitute{\temp}{8}{X8Z}[\temp]% + \StrSubstitute{\temp}{9}{X9Z}[\temp]% + \StrSubstitute{\temp}{a}{YaZ}[\temp]% + \StrSubstitute{\temp}{b}{YbZ}[\temp]% + \StrSubstitute{\temp}{c}{YcZ}[\temp]% + \StrSubstitute{\temp}{d}{YdZ}[\temp]% + \StrSubstitute{\temp}{e}{YeZ}[\temp]% + \StrSubstitute{\temp}{f}{YfZ}[\temp]% + \StrSubstitute{\temp}{X}{\StrTextsmaller}[\temp]% + \StrSubstitute{\temp}{Y}{\StrTextsc}[\temp]% + \StrSubstitute{\temp}{Z}{\StrClosingbrace}[\temp]% + \tokenize\temp{\temp}% + \temp% + \endgroup% +} +\newcommand{\uname}[1]{\textsc{#1}} +\newcommand{\unicode}[2]{\ucode{#1} \uname{#2}} +\newcommand{\UAX}[1]{\texorpdfstring{UAX~\textsmaller[1]{\raisebox{0.35ex}{\#}}#1}{UAX \##1}} +\newcommand{\NTS}[1]{\textsc{#1}} +\newcommand{\ntbs}{\NTS{ntbs}} +\newcommand{\ntmbs}{\NTS{ntmbs}} +% The following are currently unused: +% \newcommand{\ntwcs}{\NTS{ntwcs}} +% \newcommand{\ntcxvis}{\NTS{ntc16s}} +% \newcommand{\ntcxxxiis}{\NTS{ntc32s}} + +%% Code annotations +\newcommand{\EXPO}[1]{\textit{#1}} +\newcommand{\expos}{\EXPO{exposition only}} +\newcommand{\impdef}{\EXPO{implementation-defined}} +\newcommand{\impdefnc}{\EXPO{implementation-defined\nocorr}} +\newcommand{\impdefx}[1]{\indeximpldef{#1}\EXPO{implementation-defined}} +\newcommand{\notdef}{\EXPO{not defined}} + +\newcommand{\UNSP}[1]{\textit{\texttt{#1}}} +\newcommand{\UNSPnc}[1]{\textit{\texttt{#1}\nocorr}} +\newcommand{\unspec}{\UNSP{unspecified}} +\newcommand{\unspecnc}{\UNSPnc{unspecified}} +\newcommand{\unspecbool}{\UNSP{unspecified-bool-type}} +\newcommand{\seebelow}{\UNSP{see below}} % macro length: 0 +\newcommand{\seebelownc}{\UNSPnc{see below}} % macro length: 2 +\newcommand{\seeabove}{\UNSP{see above}} % macro length: 0 +\newcommand{\seeabovenc}{\UNSPnc{see above}} % macro length: 2 +\newcommand{\unspecuniqtype}{\UNSP{unspecified unique type}} +\newcommand{\unspecalloctype}{\UNSP{unspecified allocator type}} + +%% Convenience macro for double carets in expressions, +%% particularly within \tcode. +\newcommand{\reflexpr}[1]{\caret\caret#1} + +%% Manual insertion of italic corrections, for aligning in the presence +%% of the above annotations. +\newlength{\itcorrwidth} +\newlength{\itletterwidth} +\newcommand{\itcorr}[1][]{% + \settowidth{\itcorrwidth}{\textit{x\/}}% + \settowidth{\itletterwidth}{\textit{x\nocorr}}% + \addtolength{\itcorrwidth}{-1\itletterwidth}% + \makebox[#1\itcorrwidth]{}% +} + +%% Double underscore +\newcommand{\ungap}{\kern.5pt} +\newcommand{\unun}{\textunderscore\ungap\textunderscore} +\newcommand{\xname}[1]{\tcode{\unun\ungap#1}} +\newcommand{\mname}[1]{\tcode{\unun\ungap#1\ungap\unun}} + +%% An elided code fragment, /* ... */, that is formatted as code. +%% (By default, listings typeset comments as body text.) +%% Produces 9 output characters. +\newcommand{\commentellip}{\tcode{/* ...\ */}} + +%% Concepts +\newcommand{\oldconceptname}[1]{Cpp17#1} +\newcommand{\oldconcept}[1]{\textit{\oldconceptname{#1}}} +\newcommand{\defnoldconcept}[1]{\indexdefn{\idxoldconcept{#1}}\oldconcept{#1}} +\newcommand{\idxoldconcept}[1]{\oldconceptname{#1}@\oldconcept{#1}} +% FIXME: A "new" oldconcept (added after C++17), +% which doesn't get a Cpp17 prefix. +\newcommand{\newoldconcept}[1]{\textit{#1}} +\newcommand{\defnnewoldconcept}[1]{\indexdefn{\idxnewoldconcept{#1}}\newoldconcept{#1}} +\newcommand{\idxnewoldconcept}[1]{#1@\newoldconcept{#1}} + +\newcommand{\cname}[1]{\tcode{#1}} +\newcommand{\ecname}[1]{\tcode{\placeholder{#1}}} +\newcommand{\libconceptx}[2]{\cname{#1}\indexconcept{\idxconcept{#2}}} +\newcommand{\libconcept}[1]{\libconceptx{#1}{#1}} +\newcommand{\deflibconcept}[1]{\cname{#1}\indexlibrary{\idxconcept{#1}}\indexconcept{\idxconcept{#1}|idxbfpage}} +\newcommand{\exposconcept}[1]{\ecname{#1}\indexconcept{\idxexposconcept{#1}}} +\newcommand{\exposconceptx}[2]{\ecname{#1}\indexconcept{\idxexposconcept{#2}}} +\newcommand{\exposconceptnc}[1]{\indexconcept{\idxexposconcept{#1}}\ecname{#1}\itcorr[-1]} % macro length: 15 +\newcommand{\defexposconcept}[1]{\ecname{#1}\indexconcept{\idxexposconcept{#1}|idxbfpage}} % macro length: 16 +\newcommand{\defexposconceptnc}[1]{\ecname{#1}\indexconcept{\idxexposconcept{#1}|idxbfpage}\itcorr[-1]} % macro length: 18 + +%% Ranges +\newcommand{\Range}[4]{\ensuremath{#1}\tcode{#3}\ensuremath{,}\,\penalty2000{}\tcode{#4}\ensuremath{#2}} +\newcommand{\crange}[2]{\Range{[}{]}{#1}{#2}} +\newcommand{\brange}[2]{\Range{(}{]}{#1}{#2}} +\newcommand{\orange}[2]{\Range{(}{)}{#1}{#2}} +\newcommand{\range}[2]{\Range{[}{)}{#1}{#2}} +\newcommand{\countedrange}[2]{$\tcode{#1} + \range{0}{#2}$} + +%% Change descriptions +\newcommand{\diffhead}[1]{\textbf{#1:}\space} +\newcommand{\diffdef}[1]{\ifvmode\else\hfill\break\fi\diffhead{#1}} +\ExplSyntaxOn +\NewDocumentCommand \diffref { m } { + \clist_set:Nx \l_tmpa_clist { #1 } + \pnum + \int_compare:nTF { \clist_count:N \l_tmpa_clist < 2 } { + \textbf{Affected~subclause:} ~ + } { + \textbf{Affected~subclauses:} ~ + } + \clist_map_inline:Nn \l_tmpa_clist { + \clist_put_right:Nn \g_tmpa_clist { \ref{##1} } + } + \clist_use:Nnnn \g_tmpa_clist { ~and~ } { ,~ } { ,~and~ } + \clist_clear:N \g_tmpa_clist +} +\cs_set_eq:NN \diffrefs \diffref +\ExplSyntaxOff +% \nodiffref swallows a following \change and removes the preceding line break. +\def\nodiffref\change{\pnum +\diffhead{Change}} +\newcommand{\change}{\diffdef{Change}} +\newcommand{\rationale}{\diffdef{Rationale}} +\newcommand{\effect}{\diffdef{Effect on original feature}} +\newcommand{\effectafteritemize}{\diffhead{Effect on original feature}} +\newcommand{\difficulty}{\diffdef{Difficulty of converting}} +\newcommand{\howwide}{\diffdef{How widely used}} + +%% Miscellaneous +\newcommand{\stage}[1]{\item[Stage #1:]} +\newcommand{\doccite}[1]{\textit{#1}} +\newcommand{\cvqual}[1]{\textit{#1}} +\newcommand{\cv}{\ifmmode\mathit{cv}\else\cvqual{cv}\fi} +\newcommand{\numconst}[1]{\textsl{#1}} +\newcommand{\logop}[1]{\textsc{#1}} +\DeclareMathOperator{\mullo}{mullo} +\DeclareMathOperator{\mulhi}{mulhi} +\newcommand{\cedef}{\mathrel{\mathop:}=} % "colon-equals definition" + +%%-------------------------------------------------- +%% Environments for code listings. +%%-------------------------------------------------- + +% We use the 'listings' package, with some small customizations. +% The most interesting customization: all TeX commands are available +% within comments. Comments are set in italics, keywords and strings +% don't get special treatment. + +\lstset{language=C++, + basicstyle=\small\CodeStyle, + keywordstyle=, + stringstyle=, + xleftmargin=1em, + showstringspaces=false, + commentstyle=\itshape\rmfamily, + columns=fullflexible, + keepspaces=true, + texcl=true} + +% Our usual abbreviation for 'listings'. Comments are in +% italics. Arbitrary TeX commands can be used if they're +% surrounded by @ signs. +\newcommand{\CodeBlockSetup}{% +\lstset{escapechar=@, aboveskip=\parskip, belowskip=0pt, + midpenalty=500, endpenalty=-50, + emptylinepenalty=-250, semicolonpenalty=0,upquote=true}% +\renewcommand{\tcode}[1]{\textup{\CodeStylexGuarded{##1}}} +\renewcommand{\term}[1]{\textit{##1}}% +\renewcommand{\grammarterm}[1]{\gterm{##1}}% +} + +\lstnewenvironment{codeblock}{\CodeBlockSetup}{} + +\lstnewenvironment{codeblocktu}[1]{% +\lstset{title={%\parabullnum{Bullets1}{0pt} +#1:}}\CodeBlockSetup}{} + +% An environment for command / program output that is not C++ code. +\lstnewenvironment{outputblock}{\lstset{language=}}{} + +% A code block in which single-quotes are digit separators +% rather than character literals. +\lstnewenvironment{codeblockdigitsep}{ + \CodeBlockSetup + \lstset{deletestring=[b]{'}} +}{} + +% Permit use of '@' inside codeblock blocks (don't ask) +\makeatletter +\newcommand{\atsign}{@} +\makeatother + +%%-------------------------------------------------- +%% Formulae +%%-------------------------------------------------- + +% usage: \begin{formula}{XREF} +\newenvironment{formula}[1] +{\begin{equation}\label{eq:#1}} +{\end{equation}} + +\renewcommand{\eqref}[1]{Formula \nolinebreak[3] \hyperref[eq:#1]{\ref*{eq:#1}}} + +%%-------------------------------------------------- +%% Indented text +%%-------------------------------------------------- +\newenvironment{indented}[1][] +{\begin{indenthelper}[#1]\item\relax} +{\end{indenthelper}} + +%%-------------------------------------------------- +%% Library item descriptions +%%-------------------------------------------------- +\lstnewenvironment{itemdecl} +{ + \lstset{escapechar=@, + xleftmargin=0em, + midpenalty=3000, + semicolonpenalty=-50, + endpenalty=2000, + aboveskip=2ex, + belowskip=0ex % leave this alone: it keeps these things out of the + % footnote area + }% + \renewcommand{\tcode}[1]{\textup{\CodeStylexGuarded{##1}}} +} +{ +} + +\newenvironment{itemdescr} +{ + \begin{indented}[beginpenalty=3000, endpenalty=-300]} +{ + \end{indented} +} + + +%%-------------------------------------------------- +%% Bnf environments +%%-------------------------------------------------- +\newlength{\BnfIndent} +\setlength{\BnfIndent}{\leftmargini} +\newlength{\BnfInc} +\setlength{\BnfInc}{\BnfIndent} +\newlength{\BnfRest} +\setlength{\BnfRest}{2\BnfIndent} +\newcommand{\BnfNontermshape}{% + \small% + \color{grammar-gray}% + % The color setting inserts a \pdfcolorstack entry into the vertical list, + % breaking the connection of the \penalty entry from a preceding heading + % with the \glue entries that precede the grammar snippet. + % Add a penalty here that prevents making those \glue entries page-breaking + % opportunities. + \penalty10000% + \sffamily% + \itshape} +\newcommand{\BnfReNontermshape}{\small\rmfamily\itshape} +\newcommand{\BnfTermshape}{\small\ttfamily\upshape} + +\newenvironment{bnfbase} + { + \newcommand{\nontermdef}[1]{{\BnfNontermshape##1\itcorr}\indexgrammar{\idxgram{##1}}\textnormal{:}} + \newcommand{\terminal}[1]{{\BnfTermshape ##1}} + \renewcommand{\keyword}[1]{\terminal{##1}\indextext{\idxcode{##1}!keyword}} + \renewcommand{\exposid}[1]{\terminal{\textit{##1}}} + \renewcommand{\placeholder}[1]{\textrm{\textit{##1}}} + \newcommand{\descr}[1]{\textnormal{##1}} + \newcommand{\bnfindent}{\hspace*{\bnfindentfirst}} + \newcommand{\bnfindentfirst}{\BnfIndent} + \newcommand{\bnfindentinc}{\BnfInc} + \newcommand{\bnfindentrest}{\BnfRest} + \newcommand{\br}{\hfill\\*} + \widowpenalties 1 10000 + \frenchspacing + } + { + \nonfrenchspacing + } + +% "ncbnf" is the non-copied "base" versions of the bnf environment; +% instances of the full "bnf" environment is copied to the grammar +% extraction file. +% (Similarly for "ncsimplebnf", though in fact we never extract any +% hypothetical "simplebnf" environments.) +\newenvironment{ncsimplebnf} +{ + \begin{bnfbase} + \BnfNontermshape + \begin{indented}[before*=\setlength{\rightmargin}{-\leftmargin}] +} +{ + \end{indented} + \end{bnfbase} +} + +\newenvironment{ncbnf} +{ + \begin{bnfbase} + \begin{bnflist} + \BnfNontermshape + \item\relax +} +{ + \end{bnflist} + \end{bnfbase} +} + +% The regex grammar is never copied. +\newenvironment{ncrebnf} +{ + \begin{bnfbase} + \newcommand{\renontermdef}[1]{{\BnfReNontermshape##1\itcorr}\,\textnormal{::}} + \begin{bnflist} + \BnfReNontermshape + \item\relax +} +{ + \end{bnflist} + \end{bnfbase} +} + +\NewEnviron{bnf}{\begin{ncbnf}% +\BODY% +\gramWrite{\string\begin{ncbnf}\meaningbody\BODY\string\end{ncbnf}}% +\end{ncbnf}}{} + +%%-------------------------------------------------- +%% Environment for imported graphics +%%-------------------------------------------------- +% usage: \begin{importgraphic}{CAPTION}{TAG}{FILE}\end{importgraphic} +% \importexample[VERTICAL OFFESET]{FILE} +% +% The filename is relative to the source/assets directory. + +\newenvironment{importgraphic}[3] +{% +\newcommand{\cptn}{#1} +\newcommand{\lbl}{#2} +\begin{figure}[htp]\centering% +\includegraphics[scale=.35]{assets/#3} +} +{ +\caption{\cptn \quad [fig:\lbl]}\label{fig:\lbl}% +\end{figure}} + +\newcommand{\importexample}[2][-0.9pt]{\raisebox{#1}{\includegraphics{assets/#2}}} + +%%-------------------------------------------------- +%% Definitions section for "Terms and definitions" +%%-------------------------------------------------- +\newcommand{\nocontentsline}[3]{} +\newcommand{\definition}[2]{% +\addxref{#2}% +\let\oldcontentsline\addcontentsline% +\let\addcontentsline\nocontentsline% +\ifcase\value{SectionDepth} + \let\s=\section + \or\let\s=\subsection + \or\let\s=\subsubsection + \or\let\s=\paragraph + \or\let\s=\subparagraph + \fi% +\s[#1]{\hfill[#2]}\vspace{-.3\onelineskip}\label{#2} \textbf{#1}\\*% +\let\addcontentsline\oldcontentsline% +} +\newcommand{\defncontext}[1]{\textlangle#1\textrangle} +\newnoteenvironment{defnote}{Note \arabic{defnote} to entry}{end note} diff --git a/papers/wg21-latex/stdtex/paper_macros.tex b/papers/wg21-latex/stdtex/paper_macros.tex new file mode 100644 index 0000000..70a1c76 --- /dev/null +++ b/papers/wg21-latex/stdtex/paper_macros.tex @@ -0,0 +1,45 @@ +%% Cross reference +\renewcommand{\xref}{\textsc{See also:}\xspace} +\renewcommand{\tref}[1]{} +\renewcommand{\iref}[1]{} +\renewcommand{\indexhdr}[1]{} + +\usepackage{amssymb,graphicx,stackengine,xcolor} +\protected\def\ucr{\scalebox{1}{\stackinset{c}{}{c}{-.2pt}{% + \textcolor{white}{\sffamily\bfseries\small ?}}{% + \rotatebox{45}{$\blacksquare$}}}} + +\newenvironment{wording}{ + \chapterstyle{cppstd} + \newcounter{scratchChapter}\setcounter{scratchChapter}{\value{chapter}} + \settocdepth{part} + \renewcommand\thesection{\ucr.\arabic{section}} + \renewcommand\thesubsection{\makebox{$\ucr$}.\makebox{$\ucr$}.\arabic{subsection}} +} { + \setcounter{chapter}{\value{scratchChapter}} + \settocdepth{section} + +} + +\newenvironment{addedcode} +{ +\color{addclr} +\begin{codeblock} +} +{ +\end{codeblock} +\color{black} +} + +\newenvironment{removedcode} +{ +\color{remclr} +\begin{codeblock} +} +{ +\end{codeblock} +\color{black} +} + +\newcommand{\todo}[1]{} +\renewcommand{\todo}[1]{{\color{red} TODO: {#1}}} diff --git a/papers/wg21-latex/stdtex/styles.tex b/papers/wg21-latex/stdtex/styles.tex new file mode 100644 index 0000000..46d99d3 --- /dev/null +++ b/papers/wg21-latex/stdtex/styles.tex @@ -0,0 +1,346 @@ +%!TEX root = std.tex +%% styles.tex -- set styles for: +% chapters +% pages +% footnotes + +%%-------------------------------------------------- +%% create chapter styles + +\makechapterstyle{cppstd}{% + \setlength{\beforechapskip}{\onelineskip} + \setlength{\afterchapskip}{\onelineskip} + \renewcommand{\chapternamenum}{} + \renewcommand{\chapnamefont}{\chaptitlefont} + \renewcommand{\chapnumfont}{\chaptitlefont} + \renewcommand{\printchapternum}{\chapnumfont\thechapter\quad} + \renewcommand{\afterchapternum}{} +} + +\makechapterstyle{cppannex}{% + \setlength{\beforechapskip}{\onelineskip} + \setlength{\afterchapskip}{\onelineskip} + \renewcommand{\chapternamenum}{} + \renewcommand{\chapnamefont}{\chaptitlefont} + \renewcommand{\chapnumfont}{\chaptitlefont} + \renewcommand{\printchapternum}{\chapnumfont\centering\thechapter\protect\\} + \renewcommand{\afterchapternum}{} +} + +%%-------------------------------------------------- +%% create page styles + +\makepagestyle{cpppage} +\makeevenhead{cpppage}{} +\makeoddhead{cpppage}{} +\makeevenfoot{cpppage}{\leftmark}{}{\thepage} +\makeoddfoot{cpppage}{\leftmark}{}{\thepage} + +\makeatletter +\makepsmarks{cpppage}{% + \let\@mkboth\markboth + \def\chaptermark##1{\markboth{##1}{##1}}% + \def\sectionmark##1{\markboth{% + \ifnum \c@secnumdepth>\z@ + \textsection\space\thesection + \fi + }{\rightmark}}% + \def\subsectionmark##1{\markboth{% + \ifnum \c@secnumdepth>\z@ + \textsection\space\thesubsection + \fi + }{\rightmark}}% + \def\subsubsectionmark##1{\markboth{% + \ifnum \c@secnumdepth>\z@ + \textsection\space\thesubsubsection + \fi + }{\rightmark}}% + \def\paragraphmark##1{\markboth{% + \ifnum \c@secnumdepth>\z@ + \textsection\space\theparagraph + \fi + }{\rightmark}}} +\makeatother + +\aliaspagestyle{chapter}{cpppage} + +%%-------------------------------------------------- +%% set heading styles for main matter +\newcommand{\beforeskip}{-.7\onelineskip plus -1ex} +\newcommand{\afterskip}{.3\onelineskip minus .2ex} + +\setbeforesecskip{\beforeskip} +\setsecindent{0pt} +\setsecheadstyle{\large\bfseries\raggedright} +\setaftersecskip{\afterskip} + +\setbeforesubsecskip{\beforeskip} +\setsubsecindent{0pt} +\setsubsecheadstyle{\large\bfseries\raggedright} +\setaftersubsecskip{\afterskip} + +\setbeforesubsubsecskip{\beforeskip} +\setsubsubsecindent{0pt} +\setsubsubsecheadstyle{\normalsize\bfseries\raggedright} +\setaftersubsubsecskip{\afterskip} + +\setbeforeparaskip{\beforeskip} +\setparaindent{0pt} +\setparaheadstyle{\normalsize\bfseries\raggedright} +\setafterparaskip{\afterskip} + +\setbeforesubparaskip{\beforeskip} +\setsubparaindent{0pt} +\setsubparaheadstyle{\normalsize\bfseries\raggedright} +\setaftersubparaskip{\afterskip} + +%%-------------------------------------------------- +% set heading style for annexes +\newcommand{\Annex}[3]{\chapter[#2]{\textnormal{(#3)}\protect\\[3ex]#2\hfill[#1]}\relax\annexlabel{#1}} +\newcommand{\infannex}[2]{\addxref{#1}\Annex{#1}{#2}{informative}} +\newcommand{\normannex}[2]{\addxref{#1}\Annex{#1}{#2}{normative}} + +%%-------------------------------------------------- +%% set footnote style +\footmarkstyle{\smaller#1) } + +%%-------------------------------------------------- +% set style for main text +\setlength{\parindent}{0pt} +\setlength{\parskip}{1ex} + +% set style for lists (itemizations, enumerations) +\setlength{\partopsep}{0pt} +\newlist{indenthelper}{itemize}{1} +\newlist{bnflist}{itemize}{1} +\setlist[itemize]{parsep=\parskip, partopsep=0pt, itemsep=0pt, topsep=0pt, + beginpenalty=10 } +\setlist[enumerate]{parsep=\parskip, partopsep=0pt, itemsep=0pt, topsep=0pt} +\setlist[indenthelper]{parsep=\parskip, partopsep=0pt, itemsep=0pt, topsep=0pt, label={}} +\setlist[bnflist]{parsep=\parskip, partopsep=0pt, itemsep=0pt, topsep=0pt, label={}, + leftmargin=\bnfindentrest, listparindent=-\bnfindentinc, itemindent=\listparindent} + +%%-------------------------------------------------- +%% set caption styles +\DeclareCaptionLabelSeparator{emdash}{ --- } +\captionsetup{justification=centering,labelsep=emdash,font+=bf} +\captionsetup[lstlisting]{justification=raggedright,singlelinecheck=false,font=normal} + +%%-------------------------------------------------- +%% set global styles that get reset by \mainmatter +\newcommand{\setglobalstyles}{ + \counterwithout{footnote}{chapter} + \counterwithout{table}{chapter} + \counterwithout{figure}{chapter} + \renewcommand{\chaptername}{} + \renewcommand{\appendixname}{Annex } +} + +%%-------------------------------------------------- +%% change list item markers to number and em-dash + +\renewcommand{\labelitemi}{---\parabullnum{Bullets1}{\labelsep}} +\renewcommand{\labelitemii}{---\parabullnum{Bullets2}{\labelsep}} +\renewcommand{\labelitemiii}{---\parabullnum{Bullets3}{\labelsep}} +\renewcommand{\labelitemiv}{---\parabullnum{Bullets4}{\labelsep}} + +%%-------------------------------------------------- +%% set section numbering limit, toc limit +\maxsecnumdepth{subparagraph} +\setcounter{tocdepth}{1} + +%%-------------------------------------------------- +%% override some functions from the listings package to avoid bad page breaks +%% (copied verbatim from listings.sty version 1.6 except where commented) +\makeatletter + +\def\lst@Init#1{% + \begingroup + \ifx\lst@float\relax\else + \edef\@tempa{\noexpand\lst@beginfloat{lstlisting}[\lst@float]}% + \expandafter\@tempa + \fi + \ifx\lst@multicols\@empty\else + \edef\lst@next{\noexpand\multicols{\lst@multicols}} + \expandafter\lst@next + \fi + \ifhmode\ifinner \lst@boxtrue \fi\fi + \lst@ifbox + \lsthk@BoxUnsafe + \hbox to\z@\bgroup + $\if t\lst@boxpos \vtop + \else \if b\lst@boxpos \vbox + \else \vcenter \fi\fi + \bgroup \par\noindent + \else + \lst@ifdisplaystyle + \lst@EveryDisplay + \par\lst@beginpenalty % penalty is now configurable + \vspace\lst@aboveskip + \fi + \fi + \normalbaselines + \abovecaptionskip\lst@abovecaption\relax + \belowcaptionskip\lst@belowcaption\relax + \let\savedallowbreak\allowbreak + \let\allowbreak\relax + \lst@MakeCaption t% % neuter \allowbreak before non-existing top caption + \let\allowbreak\savedallowbreak + \lsthk@PreInit \lsthk@Init + \lst@ifdisplaystyle + \global\let\lst@ltxlabel\@empty + \if@inlabel + \lst@ifresetmargins + \leavevmode + \else + \xdef\lst@ltxlabel{\the\everypar}% + \lst@AddTo\lst@ltxlabel{% + \global\let\lst@ltxlabel\@empty + \everypar{\lsthk@EveryLine\lsthk@EveryPar}}% + \fi + \fi + % A section heading might have set \everypar to apply a \clubpenalty + % to the following paragraph, changing \everypar in the process. + % Unconditionally overriding \everypar is a bad idea. + % \everypar\expandafter{\lst@ltxlabel + % \lsthk@EveryLine\lsthk@EveryPar}% + \else + \everypar{}\let\lst@NewLine\@empty + \fi + \lsthk@InitVars \lsthk@InitVarsBOL + \lst@Let{13}\lst@MProcessListing + \let\lst@Backslash#1% + \lst@EnterMode{\lst@Pmode}{\lst@SelectCharTable}% + \lst@InitFinalize} + +\def\lst@DeInit{% + \lst@XPrintToken \lst@EOLUpdate + \global\advance\lst@newlines\m@ne + \lst@ifshowlines + \lst@DoNewLines + \else + \setbox\@tempboxa\vbox{\lst@DoNewLines}% + \fi + \lst@ifdisplaystyle \par\removelastskip \fi + \lsthk@ExitVars\everypar{}\lsthk@DeInit\normalbaselines\normalcolor + \lst@MakeCaption b% + \lst@ifbox + \egroup $\hss \egroup + \vrule\@width\lst@maxwidth\@height\z@\@depth\z@ + \else + \lst@ifdisplaystyle + % make penalty configurable + \par\lst@endpenalty + \vspace\lst@belowskip + \fi + \fi + \ifx\lst@multicols\@empty\else + \def\lst@next{\global\let\@checkend\@gobble + \endmulticols + \global\let\@checkend\lst@@checkend} + \expandafter\lst@next + \fi + \ifx\lst@float\relax\else + \expandafter\lst@endfloat + \fi + \endgroup} + + +\def\lst@NewLine{% + \ifx\lst@OutputBox\@gobble\else + \par + % add configurable penalties + \lst@ifeolsemicolon + \lst@semicolonpenalty + \lst@eolsemicolonfalse + \else + \lst@domidpenalty + \fi + % Manually apply EveryLine and EveryPar; do not depend on \everypar + \noindent \hbox{}\lsthk@EveryLine% + % \lsthk@EveryPar uses \refstepcounter which balloons the PDF + \fi + \global\advance\lst@newlines\m@ne + \lst@newlinetrue} + +% new macro for empty lines, avoiding an \hbox that cannot be discarded +\def\lst@DoEmptyLine{% + \ifvmode\else\par\fi\lst@emptylinepenalty + \vskip\parskip + \vskip\baselineskip + % \lsthk@EveryLine has \lst@parshape, i.e., \parshape, which causes an \hbox + % \lsthk@EveryPar increments line counters; \refstepcounter balloons the PDF + \global\advance\lst@newlines\m@ne + \lst@newlinetrue} + +\def\lst@DoNewLines{ + \@whilenum\lst@newlines>\lst@maxempty \do + {\lst@ifpreservenumber + \lsthk@OnEmptyLine + \global\advance\c@lstnumber\lst@advancelstnum + \fi + \global\advance\lst@newlines\m@ne}% + \@whilenum \lst@newlines>\@ne \do + % special-case empty printing of lines + {\lsthk@OnEmptyLine\lst@DoEmptyLine}% + \ifnum\lst@newlines>\z@ \lst@NewLine \fi} + +% add keys for configuring before/end vertical penalties +\lst@Key{beginpenalty}\relax{\def\lst@beginpenalty{\penalty #1}} +\let\lst@beginpenalty\@empty +\lst@Key{midpenalty}\relax{\def\lst@midpenalty{\penalty #1}} +\let\lst@midpenalty\@empty +\lst@Key{endpenalty}\relax{\def\lst@endpenalty{\penalty #1}} +\let\lst@endpenalty\@empty +\lst@Key{emptylinepenalty}\relax{\def\lst@emptylinepenalty{\penalty #1}} +\let\lst@emptylinepenalty\@empty +\lst@Key{semicolonpenalty}\relax{\def\lst@semicolonpenalty{\penalty #1}} +\let\lst@semicolonpenalty\@empty + +\lst@AddToHook{InitVars}{\let\lst@domidpenalty\@empty} +\lst@AddToHook{InitVarsEOL}{\let\lst@domidpenalty\lst@midpenalty} + +% handle semicolons and closing braces (could be in \lstdefinelanguage as well) +\def\lst@eolsemicolontrue{\global\let\lst@ifeolsemicolon\iftrue} +\def\lst@eolsemicolonfalse{\global\let\lst@ifeolsemicolon\iffalse} +\lst@AddToHook{InitVars}{ + \global\let\lst@eolsemicolonpending\@empty + \lst@eolsemicolonfalse +} +% If we found a semicolon or closing brace while parsing the current line, +% inform the subsequent \lst@NewLine about it for penalties. +\lst@AddToHook{InitVarsEOL}{% + \ifx\lst@eolsemicolonpending\relax + \lst@eolsemicolontrue + \global\let\lst@eolsemicolonpending\@empty + \fi% +} +\lst@AddToHook{SelectCharTable}{% + % In theory, we should only detect trailing semicolons or braces, + % but that would require un-doing the marking for any other character. + % The next best thing is to undo the marking for closing parentheses, + % because loops or if statements are the only places where we will + % reasonably have a semicolon in the middle of a line, and those all + % end with a closing parenthesis. + \lst@DefSaveDef{41}\lstsaved@closeparen{% handle closing parenthesis + \lstsaved@closeparen + \ifnum\lst@mode=\lst@Pmode % regular processing mode (not a comment) + \global\let\lst@eolsemicolonpending\@empty % undo semicolon setting + \fi% + }% + \lst@DefSaveDef{59}\lstsaved@semicolon{% handle semicolon + \lstsaved@semicolon + \ifnum\lst@mode=\lst@Pmode % regular processing mode (not a comment) + \global\let\lst@eolsemicolonpending\relax + \fi% + }% + \lst@DefSaveDef{125}\lstsaved@closebrace{% handle closing brace + \lst@eolsemicolonfalse % do not break before a closing brace + \lstsaved@closebrace % might invoke \lst@NewLine + \ifnum\lst@mode=\lst@Pmode % regular processing mode (not a comment) + \global\let\lst@eolsemicolonpending\relax + \fi% + }% +} + +\makeatother diff --git a/papers/wg21-latex/stdtex/tables.tex b/papers/wg21-latex/stdtex/tables.tex new file mode 100644 index 0000000..ea2f26a --- /dev/null +++ b/papers/wg21-latex/stdtex/tables.tex @@ -0,0 +1,523 @@ +%!TEX root = std.tex +% Definitions of table environments + +%%-------------------------------------------------- +%% Table environments + +% Set parameters for floating tables +\setcounter{totalnumber}{10} + +% Base definitions for tables +\newenvironment{TableBase} +{ + \renewcommand{\tcode}[1]{\CodeStylex{##1}} + \newcommand{\topline}{\hline} + \newcommand{\capsep}{\hline\hline} + \newcommand{\rowsep}{\hline} + \newcommand{\bottomline}{\hline} + +%% vertical alignment + \newcommand{\rb}[1]{\raisebox{1.5ex}[0pt]{##1}} % move argument up half a row + +%% header helpers + \newcommand{\hdstyle}[1]{\textbf{##1}} % set header style + \newcommand{\Head}[3]{\multicolumn{##1}{##2}{\hdstyle{##3}}} % add title spanning multiple columns + \newcommand{\lhdrx}[2]{\Head{##1}{|c}{##2}} % set header for left column spanning #1 columns + \newcommand{\chdrx}[2]{\Head{##1}{c}{##2}} % set header for center column spanning #1 columns + \newcommand{\rhdrx}[2]{\Head{##1}{c|}{##2}} % set header for right column spanning #1 columns + \newcommand{\ohdrx}[2]{\Head{##1}{|c|}{##2}} % set header for only column spanning #1 columns + \newcommand{\lhdr}[1]{\lhdrx{1}{##1}} % set header for single left column + \newcommand{\chdr}[1]{\chdrx{1}{##1}} % set header for single center column + \newcommand{\rhdr}[1]{\rhdrx{1}{##1}} % set header for single right column + \newcommand{\ohdr}[1]{\ohdrx{1}{##1}} + \newcommand{\br}{\hfill\break} % force newline within table entry + +%% column styles + \newcolumntype{x}[1]{>{\raggedright\let\\=\tabularnewline}p{##1}} % word-wrapped ragged-right + % column, width specified by #1 + + % do not number bullets within tables + \renewcommand{\labelitemi}{---} + \renewcommand{\labelitemii}{---} + \renewcommand{\labelitemiii}{---} + \renewcommand{\labelitemiv}{---} +} +{ +} + +% floattablebase without TableBase, used for lib2dtab2base +\newenvironment{floattablebasex}[4] +{ + \protect\hypertarget{tab:#2}{} + \begin{table}[#4] + \caption{\label{tab:#2}#1 \quad [tab:#2]} + \begin{center} + \begin{tabular}{|#3|} +} +{ + \bottomline + \end{tabular} + \end{center} + \end{table} +} + + +% General Usage: TITLE is the title of the table, XREF is the +% cross-reference for the table. LAYOUT is a sequence of column +% type specifiers (e.g., cp{1.0}c), without '|' for the left edge +% or right edge. + +% usage: \begin{floattablebase}{TITLE}{XREF}{LAYOUT}{PLACEMENT} +% produces floating table, location determined within limits +% by LaTeX. +\newenvironment{floattablebase}[4] +{ + \begin{TableBase} + \begin{floattablebasex}{#1}{#2}{#3}{#4} +} +{ + \end{floattablebasex} + \end{TableBase} +} + +% usage: \begin{floattable}{TITLE}{XREF}{LAYOUT} +% produces floating table, location determined within limits +% by LaTeX. +\newenvironment{floattable}[3] +{ + \begin{floattablebase}{#1}{#2}{#3}{htbp} +} +{ + \end{floattablebase} +} + +% a column in a multicolfloattable (internal) +\newenvironment{mcftcol}{% + \renewcommand{\columnbreak}{% + \end{mcftcol} & + \begin{mcftcol} + }% + \setlength{\tabcolsep}{0pt}% + \begin{tabular}[t]{l} +}{ + \end{tabular} +} + +% usage: \begin{multicolfloattable}{TITLE}{XREF}{LAYOUT} +% produces floating table, location determined within limits +% by LaTeX. +\newenvironment{multicolfloattable}[3] +{ + \begin{floattable}{#1}{#2}{#3} + \topline + \begin{mcftcol} +} +{ + \end{mcftcol} \\ + \end{floattable} +} + +% usage: \begin{tokentable}{TITLE}{XREF}{HDR1}{HDR2} +% produces six-column table used for lists of replacement tokens; +% the columns are in pairs -- left-hand column has header HDR1, +% right hand column has header HDR2; pairs of columns are separated +% by vertical lines. Used in the "Alternative tokens" table. +\newenvironment{tokentable}[4] +{ + \begin{floattablebase}{#1}{#2}{cc|cc|cc}{htbp} + \topline + \hdstyle{#3} & \hdstyle{#4} & + \hdstyle{#3} & \hdstyle{#4} & + \hdstyle{#3} & \hdstyle{#4} \\ \capsep +} +{ + \end{floattablebase} +} + +% usage: \begin{libsumtabbase}{TITLE}{XREF}{HDR1}{HDR2} +% produces three-column table with column headers HDR1 and HDR2. +% Used in "Library Categories" table in standard, and used as +% base for other library summary tables. +\newenvironment{libsumtabbase}[5] +{ + \begin{floattable}{#2}{#3}{ll#1} + \topline + & \hdstyle{#4} & \hdstyle{#5} \\ \capsep +} +{ + \end{floattable} +} + +% usage: \begin{libsumtab}[LASTCOLUMN]{TITLE}{XREF} +% produces three-column table with column headers "Subclause" and "Header(s)". +% Used in "C++ Headers for Freestanding Implementations" table in standard. +\newenvironment{libsumtab}[3][l] +{ + \begin{libsumtabbase}{#1}{#2}{#3}{Subclause}{Header} +} +{ + \end{libsumtabbase} +} + +% usage: \begin{concepttable}{TITLE}{XREF}{LAYOUT} +% produces table at current location +\newenvironment{concepttable}[3] +{ + \begin{floattablebase}{#1}{#2}{#3}{!htb} +} +{ + \end{floattablebase} +} + +% usage: \begin{oldconcepttable}{NAME}{EXTRA}{XREF}{LAYOUT} +% produces table at current location +\newenvironment{oldconcepttable}[4] +{ + \indextext{\idxoldconcept{#1}}% + \begin{concepttable}{\oldconcept{#1} requirements#2}{#3}{#4} +} +{ + \end{concepttable} +} + +% usage: \begin{simpletypetable}{TITLE}{XREF}{LAYOUT} +% produces table at current location +\newenvironment{simpletypetable}[3] +{ + \begin{floattablebase}{#1}{#2}{#3}{!htb} +} +{ + \end{floattablebase} +} + +% usage: \begin{LongTable}{TITLE}{XREF}{LAYOUT} +% produces table that handles page breaks sensibly. +% WARNING: Putting two of these on the same page +% does not break sensibly. Avoid this for short tables. +\newenvironment{LongTable}[3] +{ + \newcommand{\continuedcaption}{\caption[]{#1 (continued)}} + \protect\hypertarget{tab:#2}{} + \begin{TableBase} + \begin{longtable}{|#3|} + \caption{#1 \quad [tab:#2]}\label{tab:#2} +} +{ + \bottomline + \end{longtable} + \end{TableBase} +} + +% usage: \begin{libreqtabN}{TITLE}{XREF} +% produces an N-column breakable table. Used in +% most of the library Clauses for requirements tables. +% Example at "Position type requirements" in the standard. + +\newenvironment{libreqtab2}[2] +{ + \begin{LongTable} + {#1}{#2} + {lx{.55\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{shortlibreqtab2}[2] +{ + \begin{floattable} + {#1}{#2} + {lx{.55\hsize}} +} +{ + \end{floattable} +} + +\newenvironment{libreqtab2a}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.30\hsize}x{.64\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{libreqtab2b}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.35\hsize}x{.59\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{libreqtab3}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.28\hsize}x{.18\hsize}x{.43\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{libreqtab3a}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.28\hsize}x{.33\hsize}x{.29\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{libreqtab3b}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.40\hsize}x{.25\hsize}x{.25\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{libreqtab3e}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.38\hsize}x{.27\hsize}x{.25\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{libreqtab3f}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.35\hsize}x{.28\hsize}x{.29\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{libreqtab4a}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.14\hsize}x{.30\hsize}x{.30\hsize}x{.14\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{libreqtab4b}[3][LongTable] +{ + \def\libreqtabenv{#1} + \begin{\libreqtabenv} + {#2}{#3} + {x{.13\hsize}x{.15\hsize}x{.29\hsize}x{.27\hsize}} +} +{ + \end{\libreqtabenv} +} + +\newenvironment{libreqtab4c}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.16\hsize}x{.21\hsize}x{.21\hsize}x{.30\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{libreqtab4d}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.22\hsize}x{.22\hsize}x{.30\hsize}x{.15\hsize}} +} +{ + \end{LongTable} +} + +\newenvironment{libreqtab5}[2] +{ + \begin{LongTable} + {#1}{#2} + {x{.14\hsize}x{.14\hsize}x{.20\hsize}x{.20\hsize}x{.14\hsize}} +} +{ + \end{LongTable} +} + +% usage: \begin{libtab2}{TITLE}{XREF}{LAYOUT}{HDR1}{HDR2} +% produces two-column table with column headers HDR1 and HDR2. +% Used in "seekoff positioning" in the standard. +\newenvironment{libtab2}[5] +{ + \begin{floattable} + {#1}{#2}{#3} + \topline + \lhdr{#4} & \rhdr{#5} \\ \capsep +} +{ + \end{floattable} +} + +% usage: \begin{longlibtab2}{TITLE}{XREF}{LAYOUT}{HDR1}{HDR2} +% produces two-column table with column headers HDR1 and HDR2. +\newenvironment{longlibtab2}[5] +{ + \begin{LongTable}{#1}{#2}{#3} + \\ \topline + \lhdr{#4} & \rhdr{#5} \\ \capsep + \endfirsthead + \continuedcaption\\ + \topline + \lhdr{#4} & \rhdr{#5} \\ \capsep + \endhead +} +{ + \end{LongTable} +} + +% usage: \begin{LibEffTab}{TITLE}{XREF}{HDR2}{WD2} +% produces a two-column table with left column header "Element" +% and right column header HDR2, right column word-wrapped with +% width specified by WD2. +\newenvironment{LibEffTab}[4] +{ + \begin{libtab2}{#1}{#2}{lp{#4}}{Element}{#3} +} +{ + \end{libtab2} +} + +% Same as LibEffTab except that it uses a long table. +\newenvironment{longLibEffTab}[4] +{ + \begin{longlibtab2}{#1}{#2}{lp{#4}}{Element}{#3} +} +{ + \end{longlibtab2} +} + +% usage: \begin{libefftab}{TITLE}{XREF} +% produces a two-column effects table with right column +% header "Effect(s) if set", width 4.5 in. Used in "fmtflags effects" +% table in standard. +\newenvironment{libefftab}[2] +{ + \begin{LibEffTab}{#1}{#2}{Effect(s) if set}{4.5in} +} +{ + \end{LibEffTab} +} + +% Same as libefftab except that it uses a long table. +\newenvironment{longlibefftab}[2] +{ + \begin{longLibEffTab}{#1}{#2}{Effect(s) if set}{4.5in} +} +{ + \end{longLibEffTab} +} + +% usage: \begin{libefftabmean}{TITLE}{XREF} +% produces a two-column effects table with right column +% header "Meaning", width 4.5 in. Used in "seekdir effects" +% table in standard. +\newenvironment{libefftabmean}[2] +{ + \begin{LibEffTab}{#1}{#2}{Meaning}{4.5in} +} +{ + \end{LibEffTab} +} + +% usage: \begin{libefftabvalue}{TITLE}{XREF} +% produces a two-column effects table with right column +% header "Value", width 3 in. Used in "basic_ios::init() effects" +% table in standard. +\newenvironment{libefftabvalue}[2] +{ + \begin{LibEffTab}{#1}{#2}{Value}{3in} +} +{ + \end{LibEffTab} +} + +% Same as libefftabvalue except that it uses a long table and a +% slightly wider column. +\newenvironment{longlibefftabvalue}[2] +{ + \begin{longLibEffTab}{#1}{#2}{Value}{3.5in} +} +{ + \end{longLibEffTab} +} + +% usage: \begin{liberrtab}{TITLE}{XREF} produces a two-column table +% with left column header ``Value'' and right header "Error +% condition", width 4.5 in. Used in regex Clause in the TR. + +\newenvironment{liberrtab}[2] +{ + \begin{libtab2}{#1}{#2}{lp{4.5in}}{Value}{Error condition} +} +{ + \end{libtab2} +} + +% Like liberrtab except that it uses a long table. +\newenvironment{longliberrtab}[2] +{ + \begin{longlibtab2}{#1}{#2}{lp{4.5in}}{Value}{Error condition} +} +{ + \end{longlibtab2} +} + +% usage: \begin{lib2dtab2base}{TITLE}{XREF}{HDR1}{HDR2}{WID1}{WID2}{WID3} +% produces a table with one heading column followed by 2 data columns. +% used for 2D requirements tables, such as optional::operator= effects +% tables. +\newenvironment{lib2dtab2base}[7] +{ + %% no lines in the top-left cell, and leave a gap around the headers + %% FIXME: I tried to use hhline here, but it doesn't appear to support + %% the join between the leftmost top header and the topmost left header, + %% so we fake it with an empty row and column. + \newcommand{\topline}{\cline{3-4}} + \newcommand{\rowsep}{\cline{1-1}\cline{3-4}} + \newcommand{\capsep}{ + \topline + \multicolumn{4}{c}{}\\[-0.8\normalbaselineskip] + \rowsep + } + \newcommand{\bottomline}{\rowsep} + \newcommand{\hdstyle}[1]{\textbf{##1}} + \newcommand{\rowhdr}[1]{\hdstyle{##1}&} + \newcommand{\colhdr}[1]{\multicolumn{1}{>{\centering}m{#6}|}{\hdstyle{##1}}} + \begin{floattablebasex} + {#1}{#2} + {>{\centering}m{#5}|@{}p{0.2\normalbaselineskip}@{}|m{#6}|m{#7} } + {htbp} + %% table header + \topline + \multicolumn{1}{c}{}&&\colhdr{#3}&\colhdr{#4}\\ + \capsep +} +{ + \end{floattablebasex} +} + +\newenvironment{lib2dtab2}[4]{ + \begin{lib2dtab2base}{#1}{#2}{#3}{#4}{1.2in}{1.8in}{1.8in} +}{ + \end{lib2dtab2base} +} diff --git a/papers/wg21-latex/xrefdelta.tex b/papers/wg21-latex/xrefdelta.tex new file mode 100644 index 0000000..b0ed29a --- /dev/null +++ b/papers/wg21-latex/xrefdelta.tex @@ -0,0 +1,515 @@ +%!TEX root = std.tex + +\newcommand{\secref}[1]{\hyperref[\indexescape{#1}]{\indexescape{#1}}} + +%%% Turn off page numbers for this glossary, they're not useful. +\newcommand{\swallow}[1]{} +\changeglossnumformat[xrefdelta]{|swallow} + +\newcommand{\oldxref}[2]{\glossary[xrefdelta]{\indexescape{#1}}{#2}} +\newcommand{\removedxref}[1]{\oldxref{#1}{\textit{removed}}} +\newcommand{\movedxrefs}[2]{\oldxref{#1}{\textit{see} #2}} +\newcommand{\movedxref}[2]{\movedxrefs{#1}{\secref{#2}}} +\newcommand{\movedxrefii}[3]{\movedxrefs{#1}{\secref{#2}, \secref{#3}}} +\newcommand{\movedxrefiii}[4]{\movedxrefs{#1}{\secref{#2}, \secref{#3}, \secref{#4}}} +\newcommand{\deprxref}[1]{\oldxref{#1}{\textit{see} \secref{depr.#1}}} + +%%% Removed features. +%%% Example: +% +% \removedxref{removed.label} + +\movedxref{res.on.expects}{structure.specifications} +\removedxref{variant.traits} + +% [facets.examples] was removed. +\removedxref{facets.examples} + +% P0588 replaced function prototype scope with function parameter scope. +\movedxref{basic.scope.proto}{basic.scope.param} + +\movedxref{expr.pseudo}{expr.prim.id.dtor} + +\movedxref{utility.from.chars}{charconv.from.chars} +\movedxref{utility.to.chars}{charconv.to.chars} + +% [fs.definitions] and its contents were integrated into the main text. +% Note that ISO C++17 does not contain the [fs.def.*] subclauses. +\movedxrefs{fs.definitions}{% + \secref{fs.class.path}, + \secref{fs.conform.os}, + \secref{fs.general}, + \secref{fs.path.fmt.cvt}, + \secref{fs.path.generic}, + \secref{fs.race.behavior}} + +% Single-item array subclauses were dissolved. +\movedxref{array.size}{array.members} +\movedxref{array.data}{array.members} +\movedxref{array.fill}{array.members} +\movedxref{array.swap}{array.members} + +% Contents of [util.smartptr] was integrated into the parent. +\removedxref{util.smartptr} + +% Avoid duplication with synopsis. +\movedxref{re.regex.const}{re.regex} + +% Single-item [insert.iterators] subclauses were dissolved. +\movedxref{back.insert.iter.cons}{back.insert.iter.ops} +\movedxref{back.insert.iter.op=}{back.insert.iter.ops} +\movedxref{back.insert.iter.op*}{back.insert.iter.ops} +\movedxref{back.insert.iter.op++}{back.insert.iter.ops} + +\movedxref{front.insert.iter.cons}{front.insert.iter.ops} +\movedxref{front.insert.iter.op=}{front.insert.iter.ops} +\movedxref{front.insert.iter.op*}{front.insert.iter.ops} +\movedxref{front.insert.iter.op++}{front.insert.iter.ops} + +\movedxref{insert.iter.cons}{insert.iter.ops} +\movedxref{insert.iter.op=}{insert.iter.ops} +\movedxref{insert.iter.op*}{insert.iter.ops} +\movedxref{insert.iter.op++}{insert.iter.ops} + +% Single-item [reverse.iterators] subclauses were dissolved. +\movedxref{reverse.iter.op=}{reverse.iter.cons} + +\movedxref{reverse.iter.op==}{reverse.iter.cmp} +\movedxref{reverse.iter.op<}{reverse.iter.cmp} +\movedxref{reverse.iter.op!=}{reverse.iter.cmp} +\movedxref{reverse.iter.op>}{reverse.iter.cmp} +\movedxref{reverse.iter.op>=}{reverse.iter.cmp} +\movedxref{reverse.iter.op<=}{reverse.iter.cmp} + +\movedxref{reverse.iter.op.star}{reverse.iter.elem} +\movedxref{reverse.iter.opref}{reverse.iter.elem} +\movedxref{reverse.iter.opindex}{reverse.iter.elem} + +\movedxref{reverse.iter.op+}{reverse.iter.nav} +\movedxref{reverse.iter.op-}{reverse.iter.nav} +\movedxref{reverse.iter.op++}{reverse.iter.nav} +\movedxref{reverse.iter.op+=}{reverse.iter.nav} +\movedxref{reverse.iter.op--}{reverse.iter.nav} +\movedxref{reverse.iter.op-=}{reverse.iter.nav} + +\movedxref{reverse.iter.opdiff}{reverse.iter.nonmember} +\movedxref{reverse.iter.opsum}{reverse.iter.nonmember} +\movedxref{reverse.iter.make}{reverse.iter.nonmember} + +\removedxref{reverse.iter.ops} + +% Single-item [move.iterators] subclauses were dissolved. +\movedxref{move.iter.op=}{move.iter.cons} +\movedxref{move.iter.op.const}{move.iter.cons} + +\movedxref{move.iter.op.star}{move.iter.elem} +\movedxref{move.iter.op.ref}{move.iter.elem} +\movedxref{move.iter.op.index}{move.iter.elem} + +\movedxref{move.iter.op.+}{move.iter.nav} +\movedxref{move.iter.op.-}{move.iter.nav} +\movedxref{move.iter.op.incr}{move.iter.nav} +\movedxref{move.iter.op.+=}{move.iter.nav} +\movedxref{move.iter.op.decr}{move.iter.nav} +\movedxref{move.iter.op.-=}{move.iter.nav} + +\removedxref{move.iter.ops} + +% Individual swap sections were removed. +\removedxref{deque.special} +\removedxref{forwardlist.spec} +\removedxref{list.special} +\removedxref{vector.special} +\removedxref{map.special} +\removedxref{multimap.special} +\removedxref{set.special} +\removedxref{multiset.special} +\removedxref{unord.map.swap} +\removedxref{unord.multimap.swap} +\removedxref{unord.set.swap} +\removedxref{unord.multiset.swap} +\movedxref{re.regex.nmswap}{re.regex.nonmemb} + +% Deprecated features were removed. +\removedxref{depr.except.spec} +\removedxref{depr.cpp.headers} +\removedxref{depr.uncaught} +\removedxref{depr.func.adaptor.binding} +\removedxref{depr.weak.result_type} +\removedxref{depr.func.adaptor.typedefs} +\removedxref{depr.negators} +\removedxref{depr.default.allocator} +\removedxref{depr.storage.iterator} +\removedxref{depr.temporary.buffer} +\removedxref{depr.util.smartptr.shared.obs} + +% Deprecated headers were removed for some headers +\removedxref{depr.ccomplex.syn} +\removedxref{depr.cstdalign.syn} +\removedxref{depr.cstdbool.syn} +\removedxref{depr.ctgmath.syn} + +\movedxref{class.copy}{class.mem} + +% Top-level clause merging caused some Annex A subclauses to vanish. +\movedxref{gram.decl}{gram.dcl} +\movedxref{gram.derived}{gram.class} +\movedxref{gram.special}{gram.class} + +% Top-level clause merging caused some Annex C subclauses to vanish, too. +\movedxref{diff.conv}{diff.expr} +\movedxref{diff.decl}{diff.dcl} +\movedxref{diff.special}{diff.class} +\movedxref{diff.cpp03.conv}{diff.cpp03.expr} +\movedxref{diff.cpp03.dcl.decl}{diff.cpp03.dcl.dcl} +\movedxref{diff.cpp03.special}{diff.cpp03.class} +\movedxref{diff.cpp11.dcl.decl}{diff.cpp11.dcl.dcl} +\movedxref{diff.cpp14.decl}{diff.cpp14.dcl.dcl} +\movedxref{diff.cpp14.special}{diff.cpp14.class} + +% P1148R0 consolidated some Clause 20 subclauses. +\movedxref{string.rfind}{string.find} +\movedxref{string.find.first.of}{string.find} +\movedxref{string.find.last.of}{string.find} +\movedxref{string.find.first.not.of}{string.find} +\movedxref{string.find.last.not.of}{string.find} +\movedxref{string.op+=}{string.op.append} +\movedxref{string.op+}{string.op.plus} +\movedxref{string.operator==}{string.cmp} +\movedxref{string.op!=}{string.cmp} +\movedxref{string.op<}{string.cmp} +\movedxref{string.op>}{string.cmp} +\movedxref{string.op<=}{string.cmp} +\movedxref{string.op>=}{string.cmp} + +\movedxref{istream::sentry}{istream.sentry} +\movedxref{ostream::sentry}{ostream.sentry} +\movedxref{ios::failure}{ios.failure} +\movedxref{ios::fmtflags}{ios.fmtflags} +\movedxref{ios::iostate}{ios.iostate} +\movedxref{ios::openmode}{ios.openmode} +\movedxref{ios::seekdir}{ios.seekdir} +\movedxref{ios::Init}{ios.init} + +\removedxref{thread.decaycopy} + +\movedxref{iterator.container}{iterator.range} + +% Remove underscores in stable labels. +\movedxref{alg.all_of}{alg.all.of} +\movedxref{alg.any_of}{alg.any.of} +\movedxref{alg.is_permutation}{alg.is.permutation} +\movedxref{alg.none_of}{alg.none.of} +\movedxref{any.bad_any_cast}{any.bad.any.cast} +\movedxref{char.traits.specializations.char16_t}{char.traits.specializations.char16.t} +\movedxref{char.traits.specializations.char32_t}{char.traits.specializations.char32.t} +\movedxref{comparisons.equal_to}{comparisons.equal.to} +\movedxref{comparisons.greater_equal}{comparisons.greater.equal} +\movedxref{comparisons.less_equal}{comparisons.less.equal} +\movedxref{comparisons.not_equal_to}{comparisons.not.equal.to} +\movedxref{condition_variable.syn}{condition.variable.syn} +\movedxref{depr.static_constexpr}{depr.static.constexpr} +\movedxref{forward_list.syn}{forward.list.syn} +\movedxref{fs.class.directory_entry}{fs.class.directory.entry} +\movedxref{fs.class.directory_iterator}{fs.class.directory.iterator} +\movedxref{fs.class.file_status}{fs.class.file.status} +\movedxref{fs.class.filesystem_error}{fs.class.filesystem.error} +\movedxref{fs.enum.file_type}{fs.enum.file.type} +\movedxref{fs.file_status.cons}{fs.file.status.cons} +\movedxref{fs.file_status.mods}{fs.file.status.mods} +\movedxref{fs.file_status.obs}{fs.file.status.obs} +\movedxref{fs.filesystem_error.members}{fs.filesystem.error.members} +\movedxref{fs.op.copy_file}{fs.op.copy.file} +\movedxref{fs.op.copy_symlink}{fs.op.copy.symlink} +\movedxref{fs.op.create_directories}{fs.op.create.directories} +\movedxref{fs.op.create_directory}{fs.op.create.directory} +\movedxref{fs.op.create_dir_symlk}{fs.op.create.dir.symlk} +\movedxref{fs.op.create_hard_lk}{fs.op.create.hard.lk} +\movedxref{fs.op.create_symlink}{fs.op.create.symlink} +\movedxref{fs.op.current_path}{fs.op.current.path} +\movedxref{fs.op.file_size}{fs.op.file.size} +\movedxref{fs.op.hard_lk_ct}{fs.op.hard.lk.ct} +\movedxref{fs.op.is_block_file}{fs.op.is.block.file} +\movedxref{fs.op.is_char_file}{fs.op.is.char.file} +\movedxref{fs.op.is_directory}{fs.op.is.directory} +\movedxref{fs.op.is_empty}{fs.op.is.empty} +\movedxref{fs.op.is_fifo}{fs.op.is.fifo} +\movedxref{fs.op.is_other}{fs.op.is.other} +\movedxref{fs.op.is_regular_file}{fs.op.is.regular.file} +\movedxref{fs.op.is_socket}{fs.op.is.socket} +\movedxref{fs.op.is_symlink}{fs.op.is.symlink} +\movedxref{fs.op.last_write_time}{fs.op.last.write.time} +\movedxref{fs.op.read_symlink}{fs.op.read.symlink} +\movedxref{fs.op.remove_all}{fs.op.remove.all} +\movedxref{fs.op.resize_file}{fs.op.resize.file} +\movedxref{fs.op.status_known}{fs.op.status.known} +\movedxref{fs.op.symlink_status}{fs.op.symlink.status} +\movedxref{fs.op.temp_dir_path}{fs.op.temp.dir.path} +\movedxref{fs.op.weakly_canonical}{fs.op.weakly.canonical} +\movedxref{func.not_fn}{func.not.fn} +\movedxref{futures.future_error}{futures.future.error} +\movedxref{futures.shared_future}{futures.shared.future} +\movedxref{futures.unique_future}{futures.unique.future} +\movedxref{initializer_list.syn}{initializer.list.syn} +\movedxref{optional.comp_with_t}{optional.comp.with.t} +\movedxref{sf.cmath.assoc_laguerre}{sf.cmath.assoc.laguerre} +\movedxref{sf.cmath.assoc_legendre}{sf.cmath.assoc.legendre} +\movedxref{sf.cmath.comp_ellint_1}{sf.cmath.comp.ellint.1} +\movedxref{sf.cmath.comp_ellint_2}{sf.cmath.comp.ellint.2} +\movedxref{sf.cmath.comp_ellint_3}{sf.cmath.comp.ellint.3} +\movedxref{sf.cmath.cyl_bessel_i}{sf.cmath.cyl.bessel.i} +\movedxref{sf.cmath.cyl_bessel_j}{sf.cmath.cyl.bessel.j} +\movedxref{sf.cmath.cyl_bessel_k}{sf.cmath.cyl.bessel.k} +\movedxref{sf.cmath.cyl_neumann}{sf.cmath.cyl.neumann} +\movedxref{sf.cmath.ellint_1}{sf.cmath.ellint.1} +\movedxref{sf.cmath.ellint_2}{sf.cmath.ellint.2} +\movedxref{sf.cmath.ellint_3}{sf.cmath.ellint.3} +\movedxref{sf.cmath.riemann_zeta}{sf.cmath.riemann.zeta} +\movedxref{sf.cmath.sph_bessel}{sf.cmath.sph.bessel} +\movedxref{sf.cmath.sph_legendre}{sf.cmath.sph.legendre} +\movedxref{sf.cmath.sph_neumann}{sf.cmath.sph.neumann} +\movedxref{shared_mutex.syn}{shared.mutex.syn} +\movedxref{system_error.syn}{system.error.syn} +\movedxref{time.traits.duration_values}{time.traits.duration.values} +\movedxref{time.traits.is_fp}{time.traits.is.fp} +\movedxref{utility.as_const}{utility.as.const} + +% Dissolved subclause. +\movedxref{func.wrap.badcall.const}{func.wrap.badcall} + +% Shortened label +\movedxref{language.support}{support} + +% Other fixes +\removedxref{intro.ack} + +\movedxref{conversions}{locale.convenience} + +% CD and DIS C++20 +\removedxref{fs.norm.ref} +\movedxref{definitions}{intro.defs} +\removedxref{defns.arbitrary.stream} +\removedxref{defns.comparison} +\removedxref{defns.default.behavior.func} +\removedxref{defns.iostream.templates} +\removedxref{defns.repositional.stream} + +% Fix solitary subclauses +\movedxref{unreachable.sentinels}{unreachable.sentinel} +\movedxref{default.sentinels}{default.sentinel} +\movedxref{depr.iterator.primitives}{depr.iterator} +\movedxref{depr.iterator.basic}{depr.iterator} + +\movedxref{re.def}{intro.defs} +\movedxref{basic.scope.declarative}{basic.scope.scope} +\movedxref{basic.funscope}{stmt.label} +\movedxref{basic.scope.hiding}{basic.lookup} +\movedxref{basic.lookup.classref}{basic.lookup.qual} +\movedxref{namespace.memdef}{namespace.def} +\movedxref{class.this}{expr.prim.this} +\movedxref{class.mfct.non-static.general}{class.mfct.non.static} +\movedxref{class.nested.type}{diff.basic} +\movedxref{over.load}{basic.scope.scope} +\movedxref{over.dcl}{basic.link} +\movedxref{temp.nondep}{temp.res} +\movedxref{temp.inject}{temp.friend} + +% P2096R2 Generalized wording for partial specializations +\movedxref{temp.class.spec}{temp.spec.partial} +\movedxref{temp.class.spec.general}{temp.spec.partial.general} +\movedxref{temp.class.spec.match}{temp.spec.partial.match} +\movedxref{temp.class.order}{temp.spec.partial.order} +\movedxref{temp.class.spec.mfunc}{temp.spec.partial.member} + +\movedxref{forwardlist}{forward.list} +\movedxref{forwardlist.overview}{forward.list.overview} +\movedxref{forwardlist.cons}{forward.list.cons} +\movedxref{forwardlist.iter}{forward.list.iter} +\movedxref{forwardlist.access}{forward.list.access} +\movedxref{forwardlist.modifiers}{forward.list.modifiers} +\movedxref{forwardlist.ops}{forward.list.ops} + +% P2186R2 Removing Garbage Collection Support +\removedxref{basic.stc.dynamic.safety} +\removedxref{util.dynamic.safety} +\removedxref{res.on.pointer.storage} + +% LWG2818 "::std::" everywhere rule needs tweaking +\removedxref{fs.req.namespace} +\movedxref{fs.req.general}{fs.req} + +% P2325R3 Views should not be required to be default constructible +% P2494R2 Relaxing range adaptors to allow for move only types +% range.semi.wrap => range.copy.wrap => range.move.wrap +\movedxref{range.semi.wrap}{range.move.wrap} + +% P2210R2 Superior String Splitting +\movedxref{range.split.outer}{range.lazy.split.outer} +\movedxref{range.split.outer.value}{range.lazy.split.outer.value} +\movedxref{range.split.inner}{range.lazy.split.inner} + +% P2128R6 Multidimensional subscript operator +\removedxref{depr.comma.subscript} + +% P2340R1 Clarifying the status of the "C headers" +\movedxref{depr.c.headers}{support.c.headers} +\movedxref{depr.c.headers.general}{support.c.headers.general} +\movedxref{depr.c.headers.other}{support.c.headers.other} +\movedxref{depr.complex.h.syn}{complex.h.syn} +\movedxref{depr.iso646.h.syn}{iso646.h.syn} +\movedxref{depr.stdalign.h.syn}{stdalign.h.syn} +\movedxref{depr.stdbool.h.syn}{stdbool.h.syn} +\movedxref{depr.tgmath.h.syn}{tgmath.h.syn} + +\movedxref{istringstream.assign}{istringstream.swap} +\movedxref{ostringstream.assign}{ostringstream.swap} +\movedxref{stringstream.assign}{stringstream.swap} +\movedxref{ifstream.assign}{ifstream.swap} +\movedxref{ofstream.assign}{ofstream.swap} +\movedxref{fstream.assign}{fstream.swap} + +% P2387R3 Pipe support for user-defined range adaptors +\movedxref{func.bind.front}{func.bind.partial} + +\movedxref{class.mfct.non-static}{class.mfct.non.static} +\movedxref{defns.direct-non-list-init}{dcl.init.list} +\movedxref{defns.expression-equivalent}{defns.expression.equivalent} + +% P1467R9 Extended floating-point types and standard names +\movedxref{complex.special}{complex.members} +\movedxref{cstdint}{support.arith.types} +\removedxref{cstdint.general} + +% LWG3659 Consider ATOMIC_FLAG_INIT undeprecation +\removedxref{depr.atomics.flag} + +% LWG3818 Exposition-only concepts are not described in library intro +\movedxref{expos.only.func}{expos.only.entity} +\removedxref{expos.only.types} + +% P2614R2 Deprecate numeric_limits::has_denorm +\movedxref{denorm.style}{depr.numeric.limits.has.denorm} +\removedxref{fp.style} + +% CD and DIS C++2023 +\movedxref{defns.multibyte}{multibyte.strings} + +% P2864R2 Remove deprecated arithmetic conversions +\removedxref{depr.arith.conv.enum} + +% P2866R5 Remove deprecated array comparisons +\removedxref{depr.array.comp} + +% P2871R3 Remove deprecated header +\removedxref{depr.codecvt.syn} +\removedxref{depr.locale.stdcvt} +\removedxref{depr.locale.stdcvt.general} +\removedxref{depr.locale.stdcvt.req} + +% P2870R3 Remove deprecated typedef from `std::allocator` +\removedxref{depr.default.allocator} + +% P2874R2 Mandating Annex D +\removedxref{depr.res.on.required} + +% P2870R3 Remove `basic_string::reserve()` with no parameters +\removedxref{depr.string.capacity} + +% P2867R2 Remove deprecated strstreams +\removedxref{depr.istrstream} +\removedxref{depr.istrstream.cons} +\removedxref{depr.istrstream.general} +\removedxref{depr.istrstream.members} +\removedxref{depr.ostrstream} +\removedxref{depr.ostrstream.cons} +\removedxref{depr.ostrstream.general} +\removedxref{depr.ostrstream.members} +\removedxref{depr.str.strstreams} +\removedxref{depr.strstream} +\removedxref{depr.strstream.cons} +\removedxref{depr.strstream.dest} +\removedxref{depr.strstream.general} +\removedxref{depr.strstream.oper} +\removedxref{depr.strstream.syn} +\removedxref{depr.strstreambuf} +\removedxref{depr.strstreambuf.cons} +\removedxref{depr.strstreambuf.general} +\removedxref{depr.strstreambuf.members} +\removedxref{depr.strstreambuf.virtuals} + +% P2869R4 Remove deprecated shared_ptr atomic access +\removedxref{depr.util.smartptr.shared.atomic} + +% P2872R3 Remove wstring_convert +\removedxref{depr.conversions} +\removedxref{depr.conversions.buffer} +\removedxref{depr.conversions.general} +\removedxref{depr.conversions.string} + +% Clause restructuring +\removedxref{type.index.overview} +\removedxref{type.index.members} +\removedxref{type.index.hash} + +% CWG 2843 removed [uaxid.def.rfmt] +\removedxref{uaxid.def.rfmt} + +% P3016R6 Resolve inconsistencies in begin/end for valarray and braced intializers +\removedxref{support.initlist.range} + +%%% Renamed sections. +%%% Examples: +% +% \movedxref{old.label}{new.label} +% \movedxrefii{old.label}{new.label.1}{new.label.2} +% \movedxrefiii{old.label}{new.label.1}{new.label.2}{new.label.3} +% \movedxrefs{old.label}{new place (e.g., \tref{blah})} + +% https://github.com/cplusplus/draft/pull/6255 +\movedxref{container.gen.reqmts}{container.requirements.general} + +% P2875R4 Undeprecate polymorphic_allocator::destroy +\movedxref{depr.mem.poly.allocator.mem}{mem.poly.allocator.mem} + +% https://github.com/cplusplus/draft/pull/6653 +\movedxref{mismatch}{alg.mismatch} + +% P2300R10 std::execution +\movedxref{stopsource.nonmembers}{stopsource} +\movedxref{stoptoken.cons}{stopsource} +\movedxref{stoptoken.nonmembers}{stopsource} + +% https://github.com/cplusplus/draft/pull/7276 +\movedxref{except.uncaught}{except.throw} + +% https://github.com/cplusplus/draft/pull/7345 +\movedxref{basic.stc.inherit}{basic.stc.general} + +% https://github.com/cplusplus/draft/pull/7524 +\movedxref{expr.ass}{expr.assign} +\movedxref{over.ass}{over.assign} + +% CWG 2024-11-20 in Wroclaw; https://github.com/cplusplus/draft/pull/7485 +\movedxref{stmt.stmt}{stmt} +\movedxref{dcl.dcl}{dcl} + +% P1494R5 added more to this section and expanded its scope +\movedxref{utility.unreachable}{utility.undefined} + +%%% Deprecated features. +%%% Example: +% +% \deprxref{old.label} (if moved to depr.old.label, otherwise use \movedxref) + +\removedxref{util.smartptr.shared.atomic} +\removedxref{res.on.required} +\deprxref{fs.path.factory} +\movedxref{operators}{depr.relops} + +% P3475R2 Defang and deprecate memory_order::consume +\removedxref{dcl.attr.depend} + +% P3348R3 C++26 should refer to C23 not C17 +\removedxref{diff.header.assert.h} +\removedxref{diff.header.stdalign.h} +\removedxref{diff.header.stdbool.h} From 7af570c5e3de1bd2a51c5d47bdc4e2feadcedad2 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 20:53:30 -0400 Subject: [PATCH 06/15] build: sync copier metadata, presets, coverage, and CI to release state --- .beman-tidy.yaml | 6 +- .copier-answers.yml | 3 +- .gitattributes | 2 - .github/CODEOWNERS | 22 +- .github/workflows/pre-commit-check.yml | 9 +- .github/workflows/vcpkg-release.yml | 1 - .pre-commit-config.yaml | 51 ++--- CMakeLists.txt | 23 +- CMakePresets.json | 60 +++-- CONTRIBUTING.md | 1 - README.md | 67 ++---- cmake/gcovr.cfg.in | 3 +- infra/.beman_submodule | 4 +- .../enable-experimental-import-std.cmake | 208 ++---------------- lockfile.json | 8 +- vcpkg-configuration.json | 4 +- 16 files changed, 136 insertions(+), 336 deletions(-) diff --git a/.beman-tidy.yaml b/.beman-tidy.yaml index 7e74733..24d798b 100644 --- a/.beman-tidy.yaml +++ b/.beman-tidy.yaml @@ -4,5 +4,7 @@ # Check documentation for beman-tidy here: # https://github.com/bemanproject/beman-tidy/blob/main/README.md -disabled_rules: [] -ignored_paths: [] +disabled_rules: + # None +ignored_paths: + # None diff --git a/.copier-answers.yml b/.copier-answers.yml index 06063e0..c4a4691 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # Standard answers and internal state managed by Copier -_commit: 2.0.0-322-gc3c230c +_commit: v2.4.1-14-g66b1f43 _src_path: https://github.com/steve-downey/exemplar.git description: Expected Over References maintainer: steve-downey diff --git a/.gitattributes b/.gitattributes index 9a7df7d..36ceb97 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,4 @@ infra/** linguist-vendored -template/** linguist-vendored -copier/** linguist-vendored *.bib -linguist-detectable *.tex -linguist-detectable papers/* linguist-documentation diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 05cbd3a..3956e64 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,23 +1,3 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# Codeowners for reviews on PRs -# Note(river): -# **Please understand how codeowner file work before uncommenting anything in this section:** -# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -# -# For projects using expected as a template and intend to reuse its infrastructure, -# River (@wusatosi) helped create most of the original infrastructure under the scope described below, -# they are more than happy to help out with any PRs downstream, -# as well as to sync any useful change upstream to expected. -# -# Github Actions: -# .github/workflows/ @wusatosi # Add other project owners here -# -# Devcontainer: -# .devcontainer/ @wusatosi # Add other project owners here -# -# Pre-commit: -# .pre-commit-config.yaml @wusatosi # Add other project owners here -# .markdownlint.yaml @wusatosi # Add other project owners here - -* @ednolan @bretbrownjr @dietmarkuehl @steve-downey @wusatosi +* @steve-downey diff --git a/.github/workflows/pre-commit-check.yml b/.github/workflows/pre-commit-check.yml index b720f15..d65a3cc 100644 --- a/.github/workflows/pre-commit-check.yml +++ b/.github/workflows/pre-commit-check.yml @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception name: Lint Check (pre-commit) on: @@ -8,8 +9,12 @@ on: branches: - main +permissions: + contents: read + checks: write + issues: write + pull-requests: write + jobs: pre-commit: - permissions: - contents: read uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.7.3 diff --git a/.github/workflows/vcpkg-release.yml b/.github/workflows/vcpkg-release.yml index 1837f08..d17c1af 100644 --- a/.github/workflows/vcpkg-release.yml +++ b/.github/workflows/vcpkg-release.yml @@ -6,7 +6,6 @@ on: types: [published] jobs: vcpkg-release: - permissions: {} uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-vcpkg-release.yml@1.7.3 with: port_name: beman-expected diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 556aa08..7c1a556 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,48 +5,45 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files - # Clang-format for C++ - # This brings in a portable version of clang-format. - # See also: https://github.com/ssciwr/clang-format-wheel + # Clang-format for C++ + # This brings in a portable version of clang-format. + # See also: https://github.com/ssciwr/clang-format-wheel - repo: https://github.com/pre-commit/mirrors-clang-format rev: v22.1.5 hooks: - - id: clang-format - types_or: [c++, c] + - id: clang-format + types_or: [c++, c] - # CMake linting and formatting + # CMake linting and formatting - repo: https://github.com/BlankSpruce/gersemi-pre-commit - rev: 0.27.7 + rev: 0.27.6 hooks: - - id: gersemi - name: CMake linting - exclude: ^.*/tests/.*/data/ # Exclude test data directories + - id: gersemi + name: CMake linting + exclude: ^.*/tests/.*/data/ # Exclude test data directories - # Markdown linting - # Config file: .markdownlint.yaml - # Commented out to disable this by default. - # Uncomment to enable markdown linting. + # Markdown linting + # Config file: .markdownlint.yaml + # Commented out to disable this by default. Uncomment to enable markdown linting. # - repo: https://github.com/igorshubovych/markdownlint-cli # rev: v0.42.0 # hooks: - # - id: markdownlint + # - id: markdownlint - repo: https://github.com/codespell-project/codespell rev: v2.4.2 hooks: - id: codespell - additional_dependencies: - - tomli - # # Beman Standard checking via beman-tidy - # - repo: https://github.com/bemanproject/beman-tidy - # rev: v0.3.1 - # hooks: - # - id: beman-tidy + # Beman Standard checking via beman-tidy + - repo: https://github.com/bemanproject/beman-tidy + rev: v0.5.1 + hooks: + - id: beman-tidy -exclude: 'template/|copier/|infra/|port/' +exclude: 'infra/|port/' diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d9b52e..6e773c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,3 @@ -# CMakeLists.txt -*-cmake-*- # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception cmake_minimum_required(VERSION 3.30...4.3) @@ -81,17 +80,6 @@ add_subdirectory(include/beman/expected) beman_install_library(beman.expected TARGETS beman.expected) configure_build_telemetry() -if(BEMAN_EXPECTED_BUILD_TESTS) - find_package(Catch2 CONFIG REQUIRED) - # When Catch2 is fetched via FetchContent, its extras dir needs to be on the module path. - # FetchContent_GetProperties reads global state set during fetch, so works from any scope. - include(FetchContent) - FetchContent_GetProperties(catch2) - if(catch2_POPULATED AND catch2_SOURCE_DIR) - list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) - endif() -endif() - if(BEMAN_EXPECTED_BUILD_TESTS) enable_testing() add_subdirectory(tests/beman/expected) @@ -100,3 +88,14 @@ endif() if(BEMAN_EXPECTED_BUILD_EXAMPLES) add_subdirectory(examples) endif() + +# Coverage +configure_file("cmake/gcovr.cfg.in" gcovr.cfg @ONLY) + +add_custom_target( + process_coverage + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running gcovr to process coverage results" + COMMAND mkdir -p coverage + COMMAND gcovr --config gcovr.cfg . +) diff --git a/CMakePresets.json b/CMakePresets.json index 655058f..09df0e5 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,8 +8,10 @@ "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { "CMAKE_CXX_STANDARD": "20", - "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", - "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "./infra/cmake/use-fetch-content.cmake" + "CMAKE_CXX_EXTENSIONS": false, + "CMAKE_CXX_STANDARD_REQUIRED": true, + "CMAKE_EXPORT_COMPILE_COMMANDS": true, + "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "${sourceDir}/infra/cmake/use-fetch-content.cmake" } }, { @@ -35,7 +37,7 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "infra/cmake/gnu-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/gnu-toolchain.cmake" } }, { @@ -46,7 +48,7 @@ "_release-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "infra/cmake/gnu-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/gnu-toolchain.cmake" } }, { @@ -57,7 +59,12 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/llvm-toolchain.cmake" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" } }, { @@ -68,7 +75,12 @@ "_release-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/llvm-toolchain.cmake" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" } }, { @@ -79,7 +91,12 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/appleclang-toolchain.cmake" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" } }, { @@ -90,7 +107,12 @@ "_release-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/appleclang-toolchain.cmake" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" } }, { @@ -101,8 +123,12 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_CXX_STANDARD": "23", - "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/msvc-toolchain.cmake" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" } }, { @@ -113,8 +139,12 @@ "_release-base" ], "cacheVariables": { - "CMAKE_CXX_STANDARD": "23", - "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/msvc-toolchain.cmake" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" } } ], @@ -122,7 +152,11 @@ { "name": "_root-build", "hidden": true, - "jobs": 0 + "jobs": 0, + "targets": [ + "all_verify_interface_header_sets", + "all" + ] }, { "name": "gcc-debug", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c81c29..8742438 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,7 +106,6 @@ ctest --test-dir build The file `./lockfile.json` configures the list of dependencies and versions that will be acquired by FetchContent. - ## Project-specific configure arguments Project-specific options are prefixed with `BEMAN_EXPECTED`. diff --git a/README.md b/README.md index 8f43eac..6f75e0a 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,20 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception --> -![Library Status](https://raw.githubusercontent.com/bemanproject/beman/refs/heads/main/images/badges/beman_badge-beman_library_under_development.svg) ![Continuous Integration Tests](https://github.com/steve-downey/expected/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/steve-downey/expected/actions/workflows/pre-commit-check.yml/badge.svg) [![Coverage](https://coveralls.io/repos/github/steve-downey/expected/badge.svg?branch=main)](https://coveralls.io/github/steve-downey/expected?branch=main) ![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp29.svg) [![Compiler Explorer Example](https://img.shields.io/badge/Try%20it%20on%20Compiler%20Explorer-grey?logo=compilerexplorer&logoColor=67c52a)](https://www.example.com) +![Library Status](https://raw.githubusercontent.com/bemanproject/beman/refs/heads/main/images/badges/beman_badge-beman_library_under_development.svg) ![Continuous Integration Tests](https://github.com/steve-downey/sandbox-expected/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/steve-downey/sandbox-expected/actions/workflows/pre-commit-check.yml/badge.svg) [![Coverage](https://coveralls.io/repos/github/steve-downey/sandbox-expected/badge.svg?branch=main)](https://coveralls.io/github/steve-downey/sandbox-expected?branch=main) ![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp29.svg) `beman.expected` is a C++ library implementing the std::expected specification conforming to [The Beman Standard](https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md). -**Implements**: `std::expected` proposed in [Expected over References (PnnnnRr)](https://wg21.link/PnnnnRr). +**Implements**: `std::expected` proposed in [Expected over References (D4280R0)](https://wg21.link/D4280R0). **Status**: [Under development and not yet ready for production use.](https://github.com/bemanproject/beman/blob/main/docs/beman_library_maturity_model.md#under-development-and-not-yet-ready-for-production-use) +## Review Guides + +As this implementation proposes extensions targeting standard library adoption (notably supporting reference semantics in `std::expected`), two rigorous review guides have been prepared: +- **[LLM Code Review Guide](docs/llm-code-review-guide.md)**: Specialized prompt instructions and strict standardese criteria for automated reviewers to mechanically verify C++26 constraints and mandates. +- **[Human Design Review Guide](docs/human-design-review-guide.md)**: An analytical guide for human contributors highlighting the compromises, architectural semantics (like assignment-through vs rebinding), and abstraction structures. We encourage reviewers to read this guide before contributing to standard wording or feature requests. + ## License `beman.expected` is licensed under the Apache License v2.0 with LLVM Exceptions. @@ -28,39 +34,20 @@ Full runnable examples can be found in [`examples/`](examples/). This project requires at least the following to build: -* A C++ compiler that conforms to the C++17 standard or greater -* CMake 3.28 or later -* (Test Only) GoogleTest +* A C++ compiler that conforms to the C++20 standard or greater +* CMake 3.30 or later +* (Test Only) Catch2 You can disable building tests by setting CMake option [`BEMAN_EXPECTED_BUILD_TESTS`](#beman_expected_build_tests) to `OFF` when configuring the project. -Even when tests are being built and run, some of them will not be compiled -unless the provided compiler supports **C++20** ranges. - -> [!TIP] -> -> The logs indicate examples disabled due to lack of compiler support. -> -> For example: -> -> ```txt -> -- Looking for __cpp_lib_ranges -> -- Looking for __cpp_lib_ranges - not found -> CMake Warning at examples/CMakeLists.txt:12 (message): -> Missing range support! Skip: identity_as_default_projection -> -> -> Examples to be built: identity_direct_usage -> ``` - ### Supported Platforms This project officially supports: -* GCC versions 11–15 -* LLVM Clang++ (with libstdc++ or libc++) versions 17–21 +* GCC versions 11–16 +* LLVM Clang++ (with libstdc++ or libc++) versions 17–22 * AppleClang version 17.0.0 (i.e., the [latest version on GitHub-hosted macOS runners](https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md)) * MSVC version 19.44.35215.0 (i.e., the [latest version on GitHub-hosted Windows runners](https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-Readme.md)) @@ -140,11 +127,6 @@ VS 2022" by typing it into Windows search bar. This shell environment will provide CMake, Ninja, and MSVC, allowing you to build the library and run the tests. -Note that you will need to use FetchContent to build GoogleTest. To do so, -please see the instructions in the "Build GoogleTest dependency from github.com" -dropdown in the [Project specific configure -arguments](#project-specific-configure-arguments) section. - ### Configure and Build the Project Using CMake Presets @@ -200,25 +182,6 @@ ctest --test-dir build > you will need to specify the C++ version via `CMAKE_CXX_STANDARD` > when manually configuring the project. -### Finding and Fetching GTest from GitHub - -If you do not have GoogleTest installed on your development system, you may -optionally configure this project to download a known-compatible release of -GoogleTest from source and build it as well. - -Example commands: - -```shell -cmake -B build -S . \ - -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake \ - -DCMAKE_CXX_STANDARD=20 -cmake --build build --target all -cmake --build build --target test -``` - -The precise version of GoogleTest that will be used is maintained in -`./lockfile.json`. - ### Project specific configure arguments Project-specific options are prefixed with `BEMAN_EXPECTED`. @@ -244,9 +207,7 @@ cmake -B build -S . -DCMAKE_CXX_STANDARD=20 -DBEMAN_EXPECTED_BUILD_TESTS=OFF ``` > [!TIP] -> Because this project requires GoogleTest for running tests, -> disabling `BEMAN_EXPECTED_BUILD_TESTS` avoids the project from -> cloning GoogleTest from GitHub. +> Disabling `BEMAN_EXPECTED_BUILD_TESTS` skips fetching Catch2. #### `BEMAN_EXPECTED_BUILD_EXAMPLES` diff --git a/cmake/gcovr.cfg.in b/cmake/gcovr.cfg.in index b4e248b..419096a 100644 --- a/cmake/gcovr.cfg.in +++ b/cmake/gcovr.cfg.in @@ -7,7 +7,6 @@ gcov-parallel = yes html-theme = github.dark-blue html-self-contained = yes print-summary = yes -filter = .*/smd/conceptmap/.* -exclude = .*\.t\.cpp +filter = .*/include/beman/expected/.* coveralls = coverage.json coveralls-pretty = yes diff --git a/infra/.beman_submodule b/infra/.beman_submodule index 437c382..628b5d0 100644 --- a/infra/.beman_submodule +++ b/infra/.beman_submodule @@ -1,3 +1,3 @@ [beman_submodule] -remote=https://github.com/steve-downey/infra.git -commit_hash=5ff6d99b926f616aec54b103b5b79506f994f88b +remote=https://github.com/bemanproject/infra.git +commit_hash=7b66b858d7f48428fca936184aef2bd246ccc81a diff --git a/infra/cmake/enable-experimental-import-std.cmake b/infra/cmake/enable-experimental-import-std.cmake index 0ac9604..e41a5f7 100644 --- a/infra/cmake/enable-experimental-import-std.cmake +++ b/infra/cmake/enable-experimental-import-std.cmake @@ -1,194 +1,24 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -if(CMAKE_VERSION VERSION_EQUAL "3.30.0") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.30.1") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.30.2") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.30.3") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.30.4") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.30.5") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.30.6") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.30.7") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.30.8") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.30.9") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.0") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.1") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.10") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.11") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.12") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.2") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.3") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.4") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.5") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.6") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.7") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.8") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "3.31.9") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.0.0") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "a9e1cf81-9932-4810-974b-6eccaf14e457" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.0.1") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "a9e1cf81-9932-4810-974b-6eccaf14e457" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.0.2") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "a9e1cf81-9932-4810-974b-6eccaf14e457" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.0.3") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.0.4") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.0.5") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.0.6") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.0.7") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.1.0") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.1.1") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.1.2") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.1.3") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.1.4") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.1.5") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.1.6") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.2.0") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.2.1") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.2.2") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.2.3") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.2.4") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.2.5") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "d0edc3af-4c50-42ea-a356-e2862fe7a444" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.3.0") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "451f2fe2-a8a2-47c3-bc32-94786d8fc91b" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.3.1") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "451f2fe2-a8a2-47c3-bc32-94786d8fc91b" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.3.2") - set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD - "451f2fe2-a8a2-47c3-bc32-94786d8fc91b" - ) -elseif(CMAKE_VERSION VERSION_EQUAL "4.3.3") +if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.3.0") set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "451f2fe2-a8a2-47c3-bc32-94786d8fc91b" ) +elseif(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30.0") + if(CMAKE_VERSION VERSION_LESS "3.31.8") + set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD + "0e5b6991-d74f-4b3d-a41c-cf096e0b2508" + ) + elseif(CMAKE_VERSION VERSION_LESS "4.0.0") + set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD + "d0edc3af-4c50-42ea-a356-e2862fe7a444" + ) + elseif(CMAKE_VERSION VERSION_LESS "4.0.3") + set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD + "a9e1cf81-9932-4810-974b-6eccaf14e457" + ) + elseif(CMAKE_VERSION VERSION_LESS "4.3.0") + set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD + "d0edc3af-4c50-42ea-a356-e2862fe7a444" + ) + endif() endif() diff --git a/lockfile.json b/lockfile.json index 70fd092..a1f3d8f 100644 --- a/lockfile.json +++ b/lockfile.json @@ -1,14 +1,10 @@ { "dependencies": [ { - "name": "catch2", + "name": "Catch2", "package_name": "Catch2", "git_repository": "https://github.com/catchorg/Catch2.git", - "git_tag": "fee81626d2a4811095c3a39d20fb355eeb954101", - "cmake_args": { - "CATCH_INSTALL_DOCS": "OFF", - "CATCH_BUILD_TESTING": "OFF" - } + "git_tag": "25319fd3047c6bdcf3c0170e76fa526c77f99ca9" } ] } diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json index db23b90..0924600 100644 --- a/vcpkg-configuration.json +++ b/vcpkg-configuration.json @@ -2,13 +2,13 @@ "default-registry": { "kind": "git", "repository": "https://github.com/microsoft/vcpkg.git", - "baseline": "522253caf47268c1724f486a035e927a42a90092" + "baseline": "aa40adda5352e87655b8583cfb2451d5e9e276fd" }, "registries": [ { "kind": "git", "repository": "https://github.com/bemanproject/vcpkg-registry.git", - "baseline": "28992b34d1e39368f5d1214a557fd61949de39fb", + "baseline": "e1aa5c3c2a46486cc1384d70beab939d76bfe128", "packages": ["beman-*"] } ] From 0682bf6ffad138b3e444e42fc77684d1fa23c79d Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 20:53:30 -0400 Subject: [PATCH 07/15] chore: remove todo scaffolding superseded by the library --- examples/todo.cpp | 8 -------- include/beman/expected/CMakeLists.txt | 7 ++++++- include/beman/expected/todo.hpp | 23 ----------------------- 3 files changed, 6 insertions(+), 32 deletions(-) delete mode 100644 examples/todo.cpp delete mode 100644 include/beman/expected/todo.hpp diff --git a/examples/todo.cpp b/examples/todo.cpp deleted file mode 100644 index 542b7c2..0000000 --- a/examples/todo.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#include -#include - -int main() { - // TODO -} diff --git a/include/beman/expected/CMakeLists.txt b/include/beman/expected/CMakeLists.txt index 86ab1bc..bbb92d3 100644 --- a/include/beman/expected/CMakeLists.txt +++ b/include/beman/expected/CMakeLists.txt @@ -19,6 +19,11 @@ else() beman.expected PUBLIC FILE_SET HEADERS - FILES expected.hpp unexpected.hpp bad_expected_access.hpp + FILES + config.hpp + expected.hpp + unexpected.hpp + bad_expected_access.hpp + "${PROJECT_BINARY_DIR}/include/beman/expected/config_generated.hpp" ) endif() diff --git a/include/beman/expected/todo.hpp b/include/beman/expected/todo.hpp deleted file mode 100644 index 2a7f050..0000000 --- a/include/beman/expected/todo.hpp +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#ifndef BEMAN_EXPECTED_TODO_HPP -#define BEMAN_EXPECTED_TODO_HPP - -#include - -#if BEMAN_EXPECTED_USE_MODULES() && !defined(BEMAN_EXPECTED_INCLUDED_FROM_INTERFACE_UNIT) - -import beman.expected; - -#else - -namespace beman::expected { - -// TODO - -} // namespace beman::expected - -#endif // BEMAN_EXPECTED_USE_MODULES() && - // !defined(BEMAN_EXPECTED_INCLUDED_FROM_INTERFACE_UNIT) - -#endif // BEMAN_EXPECTED_TODO_HPP From ef1b5e99daef6210c2f7a805b39a16e469350a1a Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 21:24:08 -0400 Subject: [PATCH 08/15] fix: spell error_or's constraint via the trait, not the member typedef (clang) clang (through 22) rejects an out-of-line constrained member of a partial specialization when its requires-clause names a member typedef of the class: 'out-of-line definition of error_or does not match any declaration in expected'. The primary template is unaffected; only the expected partial specialization's error_or triggered it. Write the two error_or requires-clauses (declaration and definition) as the underlying std::remove_cv_t> expression instead of error_value_type. Semantically identical (error_value_type is that very alias), but no member-typedef reference for clang to choke on. gcc and clang-22 (libc++, gnu++20 and gnu++26) both accept it; full gcc-debug suite still 734/734. Note: this bug is present in the original expected-over-references tip too (this branch was byte-identical to it); the same fix applies there. --- include/beman/expected/expected.hpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 47d9693..51da38c 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -2585,12 +2585,17 @@ class expected { requires(std::is_object_v && !std::is_array_v) constexpr std::remove_cv_t value_or(U&& def) const; + // Constraints spell error_value_type as its underlying trait expression rather than the + // member typedef: clang (through 22) fails to match an out-of-line constrained member of a + // partial specialization when the requires-clause names a member typedef of the class. template - requires(std::is_copy_constructible_v && std::is_convertible_v) + requires(std::is_copy_constructible_v>> && + std::is_convertible_v>>) constexpr error_value_type error_or(G&& def) const&; template - requires(std::is_move_constructible_v && std::is_convertible_v) + requires(std::is_move_constructible_v>> && + std::is_convertible_v>>) constexpr error_value_type error_or(G&& def) &&; // ------------------------------------------------------------------------- @@ -3049,8 +3054,8 @@ constexpr std::remove_cv_t expected::value_or(U&& def) const { template template - requires(std::is_copy_constructible_v::error_value_type> && - std::is_convertible_v::error_value_type>) + requires(std::is_copy_constructible_v>> && + std::is_convertible_v>>) constexpr typename expected::error_value_type expected::error_or(G&& def) const& { if (!has_val_) return unex_.error(); @@ -3059,8 +3064,8 @@ constexpr typename expected::error_value_type expected::error_or(G template template - requires(std::is_move_constructible_v::error_value_type> && - std::is_convertible_v::error_value_type>) + requires(std::is_move_constructible_v>> && + std::is_convertible_v>>) constexpr typename expected::error_value_type expected::error_or(G&& def) && { if (!has_val_) return std::move(unex_).error(); From bafd1827b3cac2870f9e32191547fe1d4192205c Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 21:33:08 -0400 Subject: [PATCH 09/15] fix: drop unused reference_converts_from_temporary_v (clang 18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reference_converts_from_temporary_v trait was defined (via std:: or the __reference_converts_from_temporary builtin) but never used — every dangling check in the library uses reference_constructs_from_temporary_v. Clang 18 provides __reference_constructs_from_temporary but not the converts sibling, so defining the unused alias broke the build there ('T'/'U' does not refer to a value) and cascaded into spurious 'out-of-line definition does not match' errors. Remove the dead trait. No behavioral change; gcc and clang-22 remain green. Pre-existing on the original expected-over-references tip as well. --- include/beman/expected/unexpected.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/beman/expected/unexpected.hpp b/include/beman/expected/unexpected.hpp index 4870520..43516d0 100644 --- a/include/beman/expected/unexpected.hpp +++ b/include/beman/expected/unexpected.hpp @@ -29,15 +29,15 @@ struct is_unexpected_specialization : std::false_type {}; template struct is_unexpected_specialization> : std::true_type {}; -// reference_constructs_from_temporary / reference_converts_from_temporary +// reference_constructs_from_temporary — the only dangling-detection trait this library uses. +// (The sibling reference_converts_from_temporary is intentionally not defined: it is unused, and +// its builtin __reference_converts_from_temporary is absent on Clang 18, which has only the +// __reference_constructs_from_temporary builtin.) #ifdef __cpp_lib_reference_from_temporary using std::reference_constructs_from_temporary_v; -using std::reference_converts_from_temporary_v; #elif __has_builtin(__reference_constructs_from_temporary) template inline constexpr bool reference_constructs_from_temporary_v = __reference_constructs_from_temporary(T, U); -template -inline constexpr bool reference_converts_from_temporary_v = __reference_converts_from_temporary(T, U); #endif } // namespace detail From 1f5df9b12652b948968258cdbdff4c84af540bd7 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 21:39:53 -0400 Subject: [PATCH 10/15] ci: drop gcc 11/12 from the test matrix (below the GCC-13 minimum) Commit f7c44c7 set GCC 13 as the minimum by relying on the __reference_constructs_from_temporary builtin (GCC 13+ / Clang 16+) with no polyfill fallback. gcc 11/12 have neither that builtin nor the C++23 <__cpp_lib_reference_from_temporary> library feature, so they cannot build. Remove them from the CI matrix to match the stated support floor. MSVC is left for a separate decision. --- .github/workflows/ci_tests.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index a297750..1c41474 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -89,14 +89,6 @@ jobs: "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] } ] - }, - { - "versions": ["12", "11"], - "tests": [ - { "cxxversions": ["c++23", "c++20"], - "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] - } - ] } ], "clang": [ From 31067a8ea3f86e88ca85266088d801f1ed7dae64 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 21:44:24 -0400 Subject: [PATCH 11/15] ci: build MSVC presets in C++23 so the dangling trait is available The msvc-debug/msvc-release presets inherited _root-config's C++20 default, where MSVC has neither __cpp_lib_reference_from_temporary (a C++23 library feature) nor the __reference_constructs_from_temporary builtin, leaving detail::reference_constructs_from_temporary_v undefined. Override CMAKE_CXX_STANDARD to 23 for both MSVC presets so std::reference_constructs_ from_temporary_v is picked up. Other presets are unchanged. --- CMakePresets.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 09df0e5..a60c8b3 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -123,7 +123,8 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/msvc-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/msvc-toolchain.cmake", + "CMAKE_CXX_STANDARD": "23" }, "condition": { "type": "equals", @@ -139,7 +140,8 @@ "_release-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/msvc-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/msvc-toolchain.cmake", + "CMAKE_CXX_STANDARD": "23" }, "condition": { "type": "equals", From 4471011b5e8e7b4a29fe0aca2550ae90e4342aad Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 22:08:15 -0400 Subject: [PATCH 12/15] ci: set support floor to gcc-13+/clang-19+, drop clang 17/18 and MSVC The dangling-detection trait relies on __reference_constructs_from_temporary (builtin) or the C++23 <__cpp_lib_reference_from_temporary> feature, and on the compiler canonicalizing an out-of-line constrained member's requires- clause against its in-class declaration. Those hold on GCC 13+ and Clang 19+ but not below: - Clang 17: lacks the builtin entirely (trait undefined). - Clang 18: has the builtin but rejects ~15 out-of-line members whose requires-clause names the self-type/member-typedef with a spelling that differs from the in-class declaration (Clang 22/GCC canonicalize; Clang 18 does not). - MSVC: same constraint-matching strictness (C2244), even in C++23 where the trait itself is available. Rather than rewrite every constrained member's declaration to be token- identical to its definition, set the supported floor. Drop clang 17/18 and the MSVC build-and-test + preset-test jobs from the matrix (gcc 11/12 were already removed), and revert the now-moot MSVC C++23 preset bump. These failures are pre-existing on the original expected-over-references tip; supporting clang<19 / MSVC would be a separate constraint-normalization pass. --- .github/workflows/ci_tests.yml | 40 +--------------------------------- CMakePresets.json | 6 ++--- 2 files changed, 3 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 1c41474..63edd65 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -29,9 +29,7 @@ jobs: {"preset": "llvm-debug", "image": "ghcr.io/bemanproject/infra-containers-clang:latest"}, {"preset": "llvm-release", "image": "ghcr.io/bemanproject/infra-containers-clang:latest"}, {"preset": "appleclang-debug", "runner": "macos-latest"}, - {"preset": "appleclang-release", "runner": "macos-latest"}, - {"preset": "msvc-debug", "runner": "windows-latest"}, - {"preset": "msvc-release", "runner": "windows-latest"} + {"preset": "appleclang-release", "runner": "macos-latest"} ] build-and-test: @@ -129,26 +127,6 @@ jobs: ] } ] - }, - { "versions": ["18"], - "tests": [ - { "cxxversions": ["c++26", "c++23", "c++20"], - "tests": [{"stdlibs": ["libc++"], "tests": ["Release.Default"]}] - }, - { "cxxversions": ["c++23", "c++20"], - "tests": [{"stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] - } - ] - }, - { "versions": ["17"], - "tests": [ - { "cxxversions": ["c++26", "c++23", "c++20"], - "tests": [{"stdlibs": ["libc++"], "tests": ["Release.Default"]}] - }, - { "cxxversions": ["c++20"], - "tests": [{"stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] - } - ] } ], "appleclang": [ @@ -159,22 +137,6 @@ jobs: } ] } - ], - "msvc": [ - { "versions": ["latest"], - "tests": [ - { "cxxversions": ["c++23"], - "tests": [ - { "stdlibs": ["stl"], - "tests": [ - "Debug.Default", "Release.Default", "Release.MaxSan", - "Debug.-DBEMAN_EXPECTED_USE_MODULES=On" - ] - } - ] - } - ] - } ] } diff --git a/CMakePresets.json b/CMakePresets.json index a60c8b3..09df0e5 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -123,8 +123,7 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/msvc-toolchain.cmake", - "CMAKE_CXX_STANDARD": "23" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/msvc-toolchain.cmake" }, "condition": { "type": "equals", @@ -140,8 +139,7 @@ "_release-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/msvc-toolchain.cmake", - "CMAKE_CXX_STANDARD": "23" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/msvc-toolchain.cmake" }, "condition": { "type": "equals", From 9a789d54512146811ae9589b9c55da12189fab4d Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 4 Jul 2026 22:27:59 -0400 Subject: [PATCH 13/15] docs: record the gcc-13+/clang-19+ support floor and MSVC status in README Update Supported Platforms to the enforced CI floor (GCC 13-16, Clang 19-22, AppleClang) and add a 'Compiler floor and unsupported toolchains' subsection explaining why: the reference_constructs_from_temporary builtin lands in GCC 13 / Clang 19, and Clang 18 additionally mismatches out-of-line constrained member definitions. Note that MSVC is unsupported not by design but because we lack MSVC access to develop and verify the out-of-line-definition workarounds it (like Clang 18) needs; contributions with MSVC access welcome. --- README.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6f75e0a..002d004 100644 --- a/README.md +++ b/README.md @@ -46,16 +46,37 @@ when configuring the project. This project officially supports: -* GCC versions 11–16 -* LLVM Clang++ (with libstdc++ or libc++) versions 17–22 +* GCC versions 13–16 +* LLVM Clang++ (with libstdc++ or libc++) versions 19–22 * AppleClang version 17.0.0 (i.e., the [latest version on GitHub-hosted macOS runners](https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md)) -* MSVC version 19.44.35215.0 (i.e., the [latest version on GitHub-hosted Windows runners](https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-Readme.md)) > [!NOTE] > > Versions outside of this range would likely work as well, > especially if you're using a version above the given range > (e.g. HEAD/ nightly). + +#### Compiler floor and unsupported toolchains + +The reference specializations detect temporary-binding hazards with +`reference_constructs_from_temporary` — the C++23 library trait where available, +otherwise the `__reference_constructs_from_temporary` compiler builtin. That +builtin lands in **GCC 13** and **Clang 19**, which sets the floor: + +* **GCC ≤ 12 / Clang ≤ 17** lack the builtin, so the trait is undefined in C++20 + mode. +* **Clang 18** has the builtin but rejects out-of-line definitions of constrained + members whose `requires`-clause names the enclosing `expected<…>` type (or a + member typedef) with a spelling that differs from the in-class declaration — + GCC and Clang 19+ canonicalize these, Clang 18 does not. + +**MSVC is not currently supported.** In C++23 the trait is available, but MSVC +applies the same out-of-line-definition constraint-matching strictness as Clang +18 (`C2244: unable to match function definition to an existing declaration`). +The fix is to make each such member's definition token-identical to its +declaration; that work is not done here simply because we do not have direct +MSVC access to develop and verify the workarounds. Contributions with MSVC +access are welcome. > These development environments are verified using our CI configuration. ## Development From 3991cc3023193610dc3fe67d93e26c1d5bc569c9 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sun, 5 Jul 2026 21:01:45 -0400 Subject: [PATCH 14/15] fix: address libc++-specific CI failures in std::expected parity target Two narrow toolchain issues surfaced in CI on the new .std parity target, neither a beman::expected behavioral difference: - expected.test.cpp: three "value() throw carries the error" tests discard the [[nodiscard]] return of e.value() before catching the exception it throws; cast to void so -Werror builds don't choke on -Wunused-result. - unexpected.test.cpp: std::unexpected == std::unexpected fails to compile on some libc++ versions (clang 19, appleclang c++26) with a cross-specialization friend-access error. Guarded the test #ifndef BEMAN_EXPECTED_TEST_STD; beman's own unexpected doesn't have this problem. Documented both, plus a third known-but-unfixed issue (a pervasive Catch2 decomposition x std::expected constrained-operator== interaction that trips "satisfaction of constraint depends on itself" on some libc++ versions, across potentially dozens of comparison sites in the parameterized suite) in docs/std-parity.md as a known, non-blocking limitation of affected toolchains rather than sweeping every call site. --- docs/std-parity.md | 33 ++++++++++++++++++++++++ tests/beman/expected/expected.test.cpp | 6 ++--- tests/beman/expected/unexpected.test.cpp | 7 +++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/docs/std-parity.md b/docs/std-parity.md index 32dcff1..d8088ed 100644 --- a/docs/std-parity.md +++ b/docs/std-parity.md @@ -57,6 +57,39 @@ constraint) holds identically for `std::expected`. `` transitively; `` does not. Fixed by adding the explicit include (also correct hygiene for the beman build). +## Known CI failures on affected toolchains (not beman behavioral differences) + +The `.std` target compiles the exact same behavioral sources against the +platform's ``, so it also inherits that standard library's own +bugs. Two are known to break specific libc++ versions in CI; neither reflects +a `beman::expected` defect, and both are toolchain issues outside this +project's control. + +1. **`std::unexpected == std::unexpected` — cross-specialization + friend-access bug.** On some libc++ versions (observed: clang 19, + appleclang c++26) this heterogeneous comparison fails to compile with + `'__unex_' is a private member of 'std::unexpected'`. The + `unexpected: equality different types` test in `unexpected.test.cpp` is + guarded `#ifndef BEMAN_EXPECTED_TEST_STD` for this reason; beman's own + `unexpected` does not have this problem. + +2. **Catch2 decomposition × `std::expected`'s constrained heterogeneous + `operator==` — self-referential constraint check.** On some libc++ + versions (observed: clang 21, clang 22, appleclang c++26) essentially any + `CHECK`/`REQUIRE` that compares an `expected`/`unexpected` value can fail + with `satisfaction of constraint '...' depends on itself`. Catch2's + `CHECK(a == b)` first wraps `a` in an internal `Catch::ExprLhs` proxy, and + `std::expected`'s generic constrained `operator==(expected, U)` is picked + up via ADL with `U = ExprLhs<...>`; checking whether `*x == u` is valid + then re-enters the same constrained templates and libc++ gives up. This is + a documented category of Catch2 gotcha (the fix at each call site is the + extra-parens idiom, `CHECK((a == b))`, which bypasses decomposition), but + here it is triggered pervasively enough across the parameterized suite + (potentially dozens of sites across all six files) that it has not been + swept file-by-file. Left as a known, non-blocking failure on the affected + libc++ versions until it is worth the width of that diff; the `beman` + build (and `.std` on unaffected libc++/libstdc++ versions) is unaffected. + ## Not yet covered - **`tl::expected`** — deferred. It is a pre-standard implementation (no diff --git a/tests/beman/expected/expected.test.cpp b/tests/beman/expected/expected.test.cpp index 817912c..8b4a0e0 100644 --- a/tests/beman/expected/expected.test.cpp +++ b/tests/beman/expected/expected.test.cpp @@ -507,7 +507,7 @@ TEST_CASE("expected: value() throws bad_expected_access from const lvalue", "[Ex TEST_CASE("expected: value() throw carries the error value", "[ExpectedTest]") { expt::expected e(expt::unexpected("oops")); try { - e.value(); + (void)e.value(); FAIL("should have thrown"); } catch (const expt::bad_expected_access& ex) { CHECK(ex.error() == "oops"); @@ -522,7 +522,7 @@ TEST_CASE("expected: value() rvalue throws bad_expected_access", "[ExpectedTest] TEST_CASE("expected: value() throw catchable as std::exception", "[ExpectedTest]") { expt::expected e(expt::unexpected(99)); try { - e.value(); + (void)e.value(); FAIL("should have thrown"); } catch (const std::exception& ex) { // what() is an implementation-defined NTBS; exact text is beman-specific. @@ -536,7 +536,7 @@ TEST_CASE("expected: value() throw catchable as std::exception", "[ExpectedTest] TEST_CASE("expected: value() throw catchable as bad_expected_access", "[ExpectedTest]") { expt::expected e(expt::unexpected("e")); try { - e.value(); + (void)e.value(); FAIL("should have thrown"); } catch (const expt::bad_expected_access& ex) { CHECK(ex.what() != nullptr); diff --git a/tests/beman/expected/unexpected.test.cpp b/tests/beman/expected/unexpected.test.cpp index 50d3528..d315bc2 100644 --- a/tests/beman/expected/unexpected.test.cpp +++ b/tests/beman/expected/unexpected.test.cpp @@ -132,11 +132,18 @@ TEST_CASE("unexpected: equality same type", "[UnexpectedTest]") { CHECK_FALSE(a == c); } +#ifndef BEMAN_EXPECTED_TEST_STD +// Beman-only: some libc++ versions (e.g. clang 19, appleclang c++26) reject +// this heterogeneous std::unexpected == std::unexpected comparison +// with "'__unex_' is a private member of 'std::unexpected'" — a +// cross-specialization friend-access bug in their std::unexpected, not a +// beman::expected behavioral difference. TEST_CASE("unexpected: equality different types", "[UnexpectedTest]") { expt::unexpected a(42); expt::unexpected b(42L); CHECK(a == b); } +#endif TEST_CASE("unexpected: CTAD from int", "[UnexpectedTest]") { expt::unexpected u(42); From 1348b93874af098343aab12a73bfeec286466339 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sun, 5 Jul 2026 21:20:58 -0400 Subject: [PATCH 15/15] build: skip the std::expected parity target on libc++ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .std target exists to catch beman::expected behavioral differences from std::expected, not to chase the standard library's own bugs. libc++ has (at least) two independent bugs of its own that surface running this suite against it (see docs/std-parity.md): a cross-specialization friend-access error in std::unexpected's heterogeneous operator==, and a pervasive Catch2-decomposition-triggered self-referential constraint check in std::expected's constrained operator==. Detect libc++ (_LIBCPP_VERSION, via check_cxx_symbol_exists — covers both explicit -stdlib=libc++ and AppleClang's implicit default) and skip building the .std target there entirely; gcc/libstdc++ and clang-with-libstdc++ configurations are unaffected and keep running it. Verified the skip takes effect under clang-19 + libc++ (BEMAN_EXPECTED_USING_LIBCXX is set and beman.expected.tests.expected.std is absent from the ninja target list), and that make TOOLCHAIN=gcc-15 test still builds and runs the .std target normally (1079/1079 tests pass). --- docs/std-parity.md | 38 ++++++++++--------- tests/beman/expected/CMakeLists.txt | 59 ++++++++++++++++++----------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/docs/std-parity.md b/docs/std-parity.md index d8088ed..8f191fd 100644 --- a/docs/std-parity.md +++ b/docs/std-parity.md @@ -22,6 +22,7 @@ aliases the selected namespace as `test_ns`: beman-only tests). - `beman.expected.tests.expected.std` — the behavioral subset only, against `std::expected`. Its ctest cases are prefixed `std.` and labelled `std`. + Skipped on libc++ (see below) — it is not built at all there. Behavioral sources (parameterized): `bad_expected_access.test.cpp`, `unexpected.test.cpp`, `expected.test.cpp`, `expected_void.test.cpp`, @@ -57,21 +58,24 @@ constraint) holds identically for `std::expected`. `` transitively; `` does not. Fixed by adding the explicit include (also correct hygiene for the beman build). -## Known CI failures on affected toolchains (not beman behavioral differences) +## `.std` target is skipped on libc++ -The `.std` target compiles the exact same behavioral sources against the -platform's ``, so it also inherits that standard library's own -bugs. Two are known to break specific libc++ versions in CI; neither reflects -a `beman::expected` defect, and both are toolchain issues outside this -project's control. +`tests/beman/expected/CMakeLists.txt` detects libc++ (`_LIBCPP_VERSION`, via +`check_cxx_symbol_exists`) — covering both an explicit `-stdlib=libc++` and +AppleClang's implicit default — and does not build +`beman.expected.tests.expected.std` at all in that case. This is a scope +decision, not a beman::expected defect: the `.std` target exists to catch +*beman* behavioral differences from the standard, not to chase the standard +library's own bugs, and libc++ has at least two independent bugs of its own +that surfaced running this exact suite against it: 1. **`std::unexpected == std::unexpected` — cross-specialization friend-access bug.** On some libc++ versions (observed: clang 19, appleclang c++26) this heterogeneous comparison fails to compile with - `'__unex_' is a private member of 'std::unexpected'`. The + `'__unex_' is a private member of 'std::unexpected'`. (The `unexpected: equality different types` test in `unexpected.test.cpp` is - guarded `#ifndef BEMAN_EXPECTED_TEST_STD` for this reason; beman's own - `unexpected` does not have this problem. + also guarded `#ifndef BEMAN_EXPECTED_TEST_STD` independent of this + skip, since beman's own `unexpected` does not have this problem.) 2. **Catch2 decomposition × `std::expected`'s constrained heterogeneous `operator==` — self-referential constraint check.** On some libc++ @@ -81,14 +85,14 @@ project's control. `CHECK(a == b)` first wraps `a` in an internal `Catch::ExprLhs` proxy, and `std::expected`'s generic constrained `operator==(expected, U)` is picked up via ADL with `U = ExprLhs<...>`; checking whether `*x == u` is valid - then re-enters the same constrained templates and libc++ gives up. This is - a documented category of Catch2 gotcha (the fix at each call site is the - extra-parens idiom, `CHECK((a == b))`, which bypasses decomposition), but - here it is triggered pervasively enough across the parameterized suite - (potentially dozens of sites across all six files) that it has not been - swept file-by-file. Left as a known, non-blocking failure on the affected - libc++ versions until it is worth the width of that diff; the `beman` - build (and `.std` on unaffected libc++/libstdc++ versions) is unaffected. + then re-enters the same constrained templates and libc++ gives up. This + is triggered pervasively enough across the parameterized suite + (potentially dozens of sites across all six files) that per-call-site + workarounds (the standard Catch2 fix is extra parens, `CHECK((a == b))`) + weren't worth chasing given the target's purpose above. + +`gcc`/libstdc++ and clang-with-libstdc++ configurations are unaffected by +either bug and continue to run the `.std` target normally. ## Not yet covered diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 4841cb6..8b9c100 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -4,6 +4,16 @@ find_package(Catch2 3 REQUIRED) list(APPEND CMAKE_MODULE_PATH "${Catch2_SOURCE_DIR}/extras") +# Detect libc++ (explicit -stdlib=libc++, or AppleClang's implicit default) so +# the std::expected parity target below can be skipped on it. See the +# "Known CI failures" section of docs/std-parity.md: several libc++ versions +# have their own std::expected/std::unexpected bugs (a cross-specialization +# friend-access error, and a Catch2-decomposition-triggered self-referential +# constraint check) that are toolchain defects, not beman::expected behavioral +# differences, and are not worth chasing here. +include(CheckCXXSymbolExists) +check_cxx_symbol_exists(_LIBCPP_VERSION "version" BEMAN_EXPECTED_USING_LIBCXX) + # Portable behavioral tests: run against every implementation under test. # Parameterized over the implementation via test_expected.hpp (see that header). set(beman_expected_behavioral_tests @@ -57,29 +67,32 @@ catch_discover_tests(beman.expected.tests.expected) # behavioral difference from beman::expected is expected except those that # involve reference specializations, which are not yet parameterized. Any # beman-specific assertion is guarded with #ifndef BEMAN_EXPECTED_TEST_STD. -add_executable(beman.expected.tests.expected.std) -target_sources( - beman.expected.tests.expected.std - PRIVATE ${beman_expected_behavioral_tests} -) -target_compile_definitions( - beman.expected.tests.expected.std - PRIVATE BEMAN_EXPECTED_TEST_STD -) -target_compile_features(beman.expected.tests.expected.std PRIVATE cxx_std_23) -target_include_directories( - beman.expected.tests.expected.std - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/tests -) -target_link_libraries( - beman.expected.tests.expected.std - PRIVATE Catch2::Catch2WithMain -) -catch_discover_tests( - beman.expected.tests.expected.std - TEST_PREFIX "std." - PROPERTIES LABELS std -) +# Skipped on libc++ — see docs/std-parity.md. +if(NOT BEMAN_EXPECTED_USING_LIBCXX) + add_executable(beman.expected.tests.expected.std) + target_sources( + beman.expected.tests.expected.std + PRIVATE ${beman_expected_behavioral_tests} + ) + target_compile_definitions( + beman.expected.tests.expected.std + PRIVATE BEMAN_EXPECTED_TEST_STD + ) + target_compile_features(beman.expected.tests.expected.std PRIVATE cxx_std_23) + target_include_directories( + beman.expected.tests.expected.std + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/tests + ) + target_link_libraries( + beman.expected.tests.expected.std + PRIVATE Catch2::Catch2WithMain + ) + catch_discover_tests( + beman.expected.tests.expected.std + TEST_PREFIX "std." + PROPERTIES LABELS std + ) +endif() # ============================================================================= # Negative compile tests (WILL_FAIL = compile failure is the expected outcome)