From ab2198262f55c719f625768ed76113ed71538c76 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 13:33:42 -0400 Subject: [PATCH 01/42] feat: implement expected reference specialization (Step 7) Partial specialization with P2988 rebind semantics: - Pointer storage in union with E, bool discriminant - Dangling prevention via reference_constructs_from_temporary_v - Shallow const (const expected still yields mutable T&) - Trivial copy/move/assign/destroy when E is trivial - Full constraint parity with optional and primary template - Monadic ops (and_then, or_else, transform, transform_error) - value_or with LWG4304 constraint (is_object_v && !is_array_v) Test infrastructure improvements: - add_fail_test macro now uses PASS_REGULAR_EXPRESSION instead of WILL_FAIL, matching diagnostics against expected static_assert messages - All _fail.cpp files annotated with // EXPECT: "regex" documenting the specific diagnostic that confirms correct failure reason - New expected_ref_constraints.test.cpp with SFINAE coverage for all requires clauses (copy/move, assignment, swap, emplace, monadic) - Plan docs updated: standing conventions require negative test for every constraint and mandate; stale GoogleTest references removed 313 tests, all passing. --- docs/plan/handoff.md | 4 +- docs/plan/index.md | 23 +- docs/plan/tests-overview.md | 68 +- include/beman/expected/expected.hpp | 690 ++++++++++++++++++ tests/beman/expected/CMakeLists.txt | 103 ++- .../expected/and_then_not_expected_fail.cpp | 1 + .../and_then_wrong_error_type_fail.cpp | 2 +- ...ted_bool_value_ctor_from_expected_fail.cpp | 5 +- tests/beman/expected/expected_e_ref_fail.cpp | 4 +- .../expected_emplace_throwing_fail.cpp | 4 +- tests/beman/expected/expected_ref.test.cpp | 477 ++++++++++++ .../expected_ref_constraints.test.cpp | 269 +++++++ .../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 + .../expected/expected_ref_temporary_fail.cpp | 8 + .../beman/expected/expected_t_array_fail.cpp | 4 +- tests/beman/expected/expected_t_ref_fail.cpp | 4 +- .../expected_unexpected_value_type_fail.cpp | 3 +- .../expected/expected_void_array_fail.cpp | 3 +- .../beman/expected/expected_void_ref_fail.cpp | 3 +- .../or_else_wrong_value_type_fail.cpp | 1 + .../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 +- 31 files changed, 1699 insertions(+), 49 deletions(-) create mode 100644 tests/beman/expected/expected_ref.test.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_temporary_fail.cpp diff --git a/docs/plan/handoff.md b/docs/plan/handoff.md index ee4e285..12bd2dd 100644 --- a/docs/plan/handoff.md +++ b/docs/plan/handoff.md @@ -27,7 +27,7 @@ tests are breathing tests only (`EXPECT_EQ(true, true)`). ### 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 @@ -40,7 +40,7 @@ tests are breathing tests only (`EXPECT_EQ(true, true)`). - Angle-bracket includes with full paths: `` - Functions defined out-of-line within the header (body after class) - `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 diff --git a/docs/plan/index.md b/docs/plan/index.md index 767dcce..e1fe8df 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) diff --git a/docs/plan/tests-overview.md b/docs/plan/tests-overview.md index fdc8f4b..3779be8 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. --- @@ -118,6 +116,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/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index e14de53..8e34584 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -83,6 +83,23 @@ constexpr void reinit_expected(NewVal& newval, CurVal& oldval, Args&&... args) { } } +// 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; +#else +template +concept reference_converts_from_temporary_v = + std::is_reference_v && + ((!std::is_reference_v && std::is_convertible_v*, std::remove_cvref_t*>) || + (std::is_lvalue_reference_v && std::is_const_v> && + std::is_convertible_v&&> && + !std::is_convertible_v&>)); + +template +concept reference_constructs_from_temporary_v = reference_converts_from_temporary_v; +#endif + } // namespace detail template @@ -1986,6 +2003,679 @@ constexpr auto expected::transform_error(F&& f) const&& { return expected(unexpect, std::invoke(std::forward(f), std::move(unex_))); } +// ============================================================================= +// Partial specialization: expected — reference value type (P2988) +// ============================================================================= + +template + requires std::is_lvalue_reference_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_object_v, "E must be an object type"); + static_assert(std::is_same_v, E>, "E must not be cv-qualified"); + + 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) + : has_val_(rhs.has_val_) { + if (has_val_) + val_ = rhs.val_; + else + std::construct_at(std::addressof(unex_), rhs.unex_); + } + + // 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) + : has_val_(rhs.has_val_) { + if (has_val_) + val_ = rhs.val_; + else + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); + } + + // 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 : has_val_(true) { + T& r = std::forward(u); + val_ = std::addressof(r); + } + + // Deleted constructor — prevent binding temporaries + template + requires(detail::reference_constructs_from_temporary_v) + constexpr expected(U&&) = delete; + + // Converting constructor from expected (copy) + template + requires(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) + : has_val_(rhs.has_value()) { + if (has_val_) { + T& r = *rhs; + val_ = std::addressof(r); + } else { + std::construct_at(std::addressof(unex_), rhs.error()); + } + } + + // Converting constructor from expected (move) + template + requires(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) + : 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())); + } + } + + // Constructor from unexpected const& + template + requires std::is_constructible_v + constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); + } + + // Constructor from unexpected&& + template + requires std::is_constructible_v + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::move(e.error())); + } + + // In-place constructor for error + template + requires std::is_constructible_v + constexpr explicit expected(unexpect_t, Args&&... args) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::forward(args)...); + } + + // 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) : has_val_(false) { + std::construct_at(std::addressof(unex_), il, std::forward(args)...); + } + + // ------------------------------------------------------------------------- + // Destructor + // ------------------------------------------------------------------------- + + constexpr ~expected() + requires std::is_trivially_destructible_v + = default; + + constexpr ~expected() + requires(!std::is_trivially_destructible_v) + { + if (!has_val_) + std::destroy_at(std::addressof(unex_)); + } + + // ------------------------------------------------------------------------- + // 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)) + { + 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; + } + + // 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)) + { + 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; + } + + // 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 { + std::destroy_at(std::addressof(unex_)); + T& r = std::forward(u); + val_ = std::addressof(r); + has_val_ = true; + } + return *this; + } + + // Assignment from unexpected const& + template + requires(std::is_constructible_v && std::is_assignable_v) + constexpr expected& operator=(const unexpected& e) { + if (!has_val_) { + unex_ = e.error(); + } else { + std::construct_at(std::addressof(unex_), e.error()); + has_val_ = false; + } + return *this; + } + + // Assignment from unexpected&& + template + requires(std::is_constructible_v && std::is_assignable_v) + constexpr expected& operator=(unexpected&& e) { + if (!has_val_) { + unex_ = std::move(e.error()); + } else { + std::construct_at(std::addressof(unex_), std::move(e.error())); + has_val_ = false; + } + return *this; + } + + // emplace — rebind the reference + template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr T& emplace(U&& u) noexcept { + if (!has_val_) { + std::destroy_at(std::addressof(unex_)); + has_val_ = true; + } + T& r = std::forward(u); + val_ = std::addressof(r); + return *val_; + } + + // ------------------------------------------------------------------------- + // 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) + { + 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); + } + } + + friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } + + // ------------------------------------------------------------------------- + // Observers + // ------------------------------------------------------------------------- + + constexpr T* operator->() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return val_; + } + + constexpr T& operator*() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return *val_; + } + + constexpr explicit operator bool() const noexcept { return has_val_; } + constexpr bool has_value() const noexcept { return has_val_; } + + constexpr T& value() const& { + static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); + if (!has_val_) + throw bad_expected_access(unex_); + return *val_; + } + + constexpr T& value() && { + 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_)); + return *val_; + } + + 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_); + } + + template > + requires(std::is_object_v && !std::is_array_v) + constexpr std::remove_cv_t 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 + constexpr E 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"); + if (!has_val_) + return unex_; + return static_cast(std::forward(def)); + } + + template + constexpr E 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"); + if (!has_val_) + return std::move(unex_); + return static_cast(std::forward(def)); + } + + // ------------------------------------------------------------------------- + // Monadic operations + // ------------------------------------------------------------------------- + + // and_then + template + requires std::is_constructible_v + constexpr auto 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_); + } + + template + requires std::is_constructible_v + constexpr auto 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_)); + } + + template + requires std::is_constructible_v + constexpr auto 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_); + } + + template + requires std::is_constructible_v + constexpr auto 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_)); + } + + // or_else + template + constexpr auto 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"); + if (has_val_) + return G(*val_); + return std::invoke(std::forward(f), unex_); + } + + template + constexpr auto 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"); + if (has_val_) + return G(*val_); + return std::invoke(std::forward(f), std::move(unex_)); + } + + template + constexpr auto 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"); + if (has_val_) + return G(*val_); + return std::invoke(std::forward(f), unex_); + } + + template + constexpr auto 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"); + if (has_val_) + return G(*val_); + return std::invoke(std::forward(f), std::move(unex_)); + } + + // transform + template + requires std::is_constructible_v + constexpr auto 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_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, unex_); + } + } + + template + requires std::is_constructible_v + constexpr auto 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_)); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, std::move(unex_)); + } + } + + template + requires std::is_constructible_v + constexpr auto 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_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, unex_); + } + } + + template + requires std::is_constructible_v + constexpr auto 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_)); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, std::move(unex_)); + } + } + + // transform_error + template + constexpr auto 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_)); + } + + template + constexpr auto 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_))); + } + + template + constexpr auto 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_)); + } + + template + constexpr auto 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_))); + } + + // ------------------------------------------------------------------------- + // 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_; + E unex_; + }; +}; + } // namespace expected } // namespace beman diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 87025b0..a6d161b 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -14,6 +14,8 @@ target_sources( expected_constraints.test.cpp expected_trivial.test.cpp expected_monadic_constraints.test.cpp + expected_ref.test.cpp + expected_ref_constraints.test.cpp todo.test.cpp ) target_link_libraries( @@ -28,8 +30,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) @@ -43,41 +47,100 @@ 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_e_ref_fail expected_e_ref_fail.cpp) -add_fail_test(expected_t_array_fail expected_t_array_fail.cpp) +add_fail_test(expected_t_ref_fail expected_t_ref_fail.cpp + "delete" +) +add_fail_test(expected_e_ref_fail expected_e_ref_fail.cpp + "E must not be a reference" +) +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" +) # 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) +add_fail_test(expected_void_ref_fail expected_void_ref_fail.cpp + "E must not be a reference" +) +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" +) # 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 + "no matching function|delete" +) +add_fail_test(expected_ref_no_default_fail expected_ref_no_default_fail.cpp + "delete" +) +add_fail_test(expected_ref_inplace_value_fail expected_ref_inplace_value_fail.cpp + "no matching function" +) + +# Step 7 — expected Mandates: E constraints +add_fail_test(expected_ref_e_ref_fail expected_ref_e_ref_fail.cpp + "E must not be a 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" +) # ============================================================================= # 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/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_e_ref_fail.cpp b/tests/beman/expected/expected_e_ref_fail.cpp index 857207f..7d354f2 100644 --- a/tests/beman/expected/expected_e_ref_fail.cpp +++ b/tests/beman/expected/expected_e_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 -// E must not be a reference type. +// NEGATIVE: expected is ill-formed [expected.object.general] para 2 +// EXPECT: "E must not be a reference" #include beman::expected::expected e; // must not compile 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_ref.test.cpp b/tests/beman/expected/expected_ref.test.cpp new file mode 100644 index 0000000..fc5b7cb --- /dev/null +++ b/tests/beman/expected/expected_ref.test.cpp @@ -0,0 +1,477 @@ +// tests/beman/expected/expected_ref.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include + +#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: 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); +} + +// ============================================================================= +// 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); +} + +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); +} + +// ============================================================================= +// 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); +} + +// ============================================================================= +// 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"); +} 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..a27faa1 --- /dev/null +++ b/tests/beman/expected/expected_ref_constraints.test.cpp @@ -0,0 +1,269 @@ +// 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..e5bbf8d --- /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..6a1a5e3 --- /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..14ce519 --- /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 where E is a reference type is ill-formed +// EXPECT: "E must not be a reference" +#include +void test() { + int x = 0; + beman::expected::expected e(x); // 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..fdf1968 --- /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..62cef32 --- /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_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_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_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_ref_fail.cpp b/tests/beman/expected/expected_void_ref_fail.cpp index 41bffaa..dd5c8cc 100644 --- a/tests/beman/expected/expected_void_ref_fail.cpp +++ b/tests/beman/expected/expected_void_ref_fail.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// Negative compile test: expected where E is a reference is ill-formed. +// NEGATIVE: expected where E is a reference is ill-formed +// EXPECT: "E must not be a reference" #include beman::expected::expected x; // should fail 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/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 6224d1b7b8fc04d48eb7ac15cd61a2692535990e Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 13:35:30 -0400 Subject: [PATCH 02/42] docs: document EXPECT/PASS_REGULAR_EXPRESSION fail test pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update tests-overview.md §3 to prescribe the // EXPECT: "regex" form for all future _fail.cpp files, with the add_fail_test(name source reason) macro as the required CMake pattern. Apply clang-format to new test files. --- docs/plan/tests-overview.md | 60 +++++++--- tests/beman/expected/expected_ref.test.cpp | 112 +++++++++--------- .../expected_ref_constraints.test.cpp | 58 ++++----- .../expected/expected_ref_e_array_fail.cpp | 2 +- .../beman/expected/expected_ref_e_cv_fail.cpp | 2 +- .../expected/expected_ref_e_ref_fail.cpp | 2 +- .../expected/expected_ref_e_void_fail.cpp | 2 +- .../expected_ref_inplace_value_fail.cpp | 2 +- 8 files changed, 129 insertions(+), 111 deletions(-) diff --git a/docs/plan/tests-overview.md b/docs/plan/tests-overview.md index 3779be8..f6bd196 100644 --- a/docs/plan/tests-overview.md +++ b/docs/plan/tests-overview.md @@ -29,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 diff --git a/tests/beman/expected/expected_ref.test.cpp b/tests/beman/expected/expected_ref.test.cpp index fc5b7cb..2c9058b 100644 --- a/tests/beman/expected/expected_ref.test.cpp +++ b/tests/beman/expected/expected_ref.test.cpp @@ -47,7 +47,7 @@ static_assert(!std::is_trivially_destructible_v>); // ============================================================================= TEST_CASE("expected: construct from lvalue reference", "[expected_ref]") { - int x = 42; + int x = 42; expected e(x); REQUIRE(e.has_value()); CHECK(&*e == &x); @@ -67,7 +67,7 @@ TEST_CASE("expected: construct from unexpect_t in-place error", "[expected_r } TEST_CASE("expected: copy construct (copies pointer)", "[expected_ref]") { - int x = 1; + int x = 1; expected a(x); expected b = a; REQUIRE(b.has_value()); @@ -75,7 +75,7 @@ TEST_CASE("expected: copy construct (copies pointer)", "[expected_ref]") { } TEST_CASE("expected: move construct (copies pointer)", "[expected_ref]") { - int x = 2; + int x = 2; expected a(x); expected b = std::move(a); REQUIRE(b.has_value()); @@ -98,7 +98,7 @@ TEST_CASE("expected: construct from derived expected", "[expected_ref Derived(int i) { v = i; } }; - Derived d{99}; + Derived d{99}; expected src(d); expected dst = src; REQUIRE(dst.has_value()); @@ -111,7 +111,7 @@ TEST_CASE("expected: construct from derived expected", "[expected_ref // ============================================================================= TEST_CASE("expected: rebind reference on assignment from lvalue", "[expected_ref]") { - int x = 1, y = 2; + int x = 1, y = 2; expected e(x); e = y; CHECK(&*e == &y); @@ -120,7 +120,7 @@ TEST_CASE("expected: rebind reference on assignment from lvalue", "[expected } TEST_CASE("expected: rebind does NOT assign through reference", "[expected_ref]") { - int x = 100, y = 200; + int x = 100, y = 200; expected e(x); e = y; CHECK(x == 100); @@ -128,7 +128,7 @@ TEST_CASE("expected: rebind does NOT assign through reference", "[expected_r } TEST_CASE("expected: assign from unexpected transitions to error state", "[expected_ref]") { - int x = 5; + int x = 5; expected e(x); e = unexpected(99); REQUIRE(!e.has_value()); @@ -137,15 +137,15 @@ TEST_CASE("expected: assign from unexpected transitions to error state", "[e } TEST_CASE("expected: assign lvalue rebinds from error state", "[expected_ref]") { - int x = 7; + int x = 7; expected e = unexpected(1); - e = x; + e = x; REQUIRE(e.has_value()); CHECK(&*e == &x); } TEST_CASE("expected: copy assignment value-value rebinds", "[expected_ref]") { - int x = 1, y = 2; + int x = 1, y = 2; expected a(x), b(y); a = b; CHECK(&*a == &y); @@ -155,24 +155,24 @@ TEST_CASE("expected: copy assignment value-value rebinds", "[expected_ref]") TEST_CASE("expected: copy assignment error-error copies error", "[expected_ref]") { expected a = unexpected(1); expected b = unexpected(2); - a = b; + a = b; CHECK(a.error() == 2); } TEST_CASE("expected: copy assignment value-to-error", "[expected_ref]") { - int x = 5; + int x = 5; expected a(x); expected b = unexpected(42); - a = b; + a = b; REQUIRE(!a.has_value()); CHECK(a.error() == 42); } TEST_CASE("expected: copy assignment error-to-value", "[expected_ref]") { - int x = 5; + int x = 5; expected a = unexpected(42); expected b(x); - a = b; + a = b; REQUIRE(a.has_value()); CHECK(&*a == &x); } @@ -182,14 +182,14 @@ TEST_CASE("expected: copy assignment error-to-value", "[expected_ref]") { // ============================================================================= TEST_CASE("expected: shallow const allows mutation of referent", "[expected_ref]") { - int x = 10; + 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; + int x = 5; const expected e(x); static_assert(std::is_same_v()), int*>); *e.operator->() = 99; @@ -201,7 +201,7 @@ TEST_CASE("expected: operator-> on const returns T*", "[expected_ref]") { // ============================================================================= TEST_CASE("expected: operator* returns T&", "[expected_ref]") { - int x = 42; + int x = 42; expected e(x); static_assert(std::is_same_v); *e = 99; @@ -212,7 +212,7 @@ TEST_CASE("expected: operator-> returns T*", "[expected_ref]") { struct S { int v; }; - S s{7}; + S s{7}; expected e(s); CHECK(e->v == 7); e->v = 99; @@ -220,7 +220,7 @@ TEST_CASE("expected: operator-> returns T*", "[expected_ref]") { } TEST_CASE("expected: value() returns T& or throws", "[expected_ref]") { - int x = 1; + int x = 1; expected e(x); static_assert(std::is_same_v); CHECK(e.value() == 1); @@ -239,15 +239,15 @@ TEST_CASE("expected: error() returns error", "[expected_ref]") { } TEST_CASE("expected: value_or returns referred value when has value", "[expected_ref]") { - int x = 42; + int x = 42; expected e(x); - int result = e.value_or(0); + 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); + expected e = unexpected(0); + int result = e.value_or(99); CHECK(result == 99); } @@ -257,13 +257,13 @@ TEST_CASE("expected: error_or returns error when has error", "[expected_ref] } TEST_CASE("expected: error_or returns default when has value", "[expected_ref]") { - int x = 5; + int x = 5; expected e(x); CHECK(e.error_or(99) == 99); } TEST_CASE("expected: bool conversion", "[expected_ref]") { - int x = 1; + int x = 1; expected val(x); expected err = unexpected(0); CHECK(static_cast(val)); @@ -275,7 +275,7 @@ TEST_CASE("expected: bool conversion", "[expected_ref]") { // ============================================================================= TEST_CASE("expected: emplace rebinds from value", "[expected_ref]") { - int x = 1, y = 2; + int x = 1, y = 2; expected e(x); e.emplace(y); CHECK(&*e == &y); @@ -283,7 +283,7 @@ TEST_CASE("expected: emplace rebinds from value", "[expected_ref]") { } TEST_CASE("expected: emplace rebinds from error", "[expected_ref]") { - int x = 5; + int x = 5; expected e = unexpected(42); e.emplace(x); REQUIRE(e.has_value()); @@ -295,7 +295,7 @@ TEST_CASE("expected: emplace rebinds from error", "[expected_ref]") { // ============================================================================= TEST_CASE("expected: swap value-value rebinds pointers", "[expected_ref]") { - int x = 1, y = 2; + int x = 1, y = 2; expected a(x), b(y); a.swap(b); CHECK(&*a == &y); @@ -305,7 +305,7 @@ TEST_CASE("expected: swap value-value rebinds pointers", "[expected_ref]") { } TEST_CASE("expected: swap value-error", "[expected_ref]") { - int x = 1; + int x = 1; expected a(x), b(unexpect, 99); a.swap(b); REQUIRE(!a.has_value()); @@ -322,7 +322,7 @@ TEST_CASE("expected: swap error-error", "[expected_ref]") { } TEST_CASE("expected: friend swap", "[expected_ref]") { - int x = 10, y = 20; + int x = 10, y = 20; expected a(x), b(y); swap(a, b); CHECK(&*a == &y); @@ -334,14 +334,14 @@ TEST_CASE("expected: friend swap", "[expected_ref]") { // ============================================================================= TEST_CASE("expected: equality with expected", "[expected_ref]") { - int x = 5, y = 5, z = 6; + 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; + int x = 42; expected e(x); CHECK(e == 42); CHECK(!(e == 99)); @@ -354,7 +354,7 @@ TEST_CASE("expected: equality with unexpected", "[expected_ref]") { } TEST_CASE("expected: inequality value vs error", "[expected_ref]") { - int x = 5; + int x = 5; expected val(x); expected err = unexpected(5); CHECK(!(val == err)); @@ -365,39 +365,39 @@ TEST_CASE("expected: inequality value vs error", "[expected_ref]") { // ============================================================================= TEST_CASE("expected: and_then passes T& to callable", "[expected_ref]") { - int x = 5; + int x = 5; expected e(x); - auto r = e.and_then([](int& v) -> expected { return v * 2; }); + 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; }); + 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; + int x = 3; expected e(x); - auto r = e.transform([](int& v) { return v + 1; }); + 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; }); + 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 { + int x = 0; + auto r = e.or_else([&](int v) -> expected { x = v; return unexpected(v); }); @@ -405,34 +405,34 @@ TEST_CASE("expected: or_else passes error to callable", "[expected_ref]") { } TEST_CASE("expected: or_else on value returns value", "[expected_ref]") { - int x = 42; + int x = 42; expected e(x); - auto r = e.or_else([](int) -> expected { return unexpected(0); }); + 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); }); + 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; + int x = 42; expected e(x); - auto r = e.transform_error([](int v) -> std::string { return std::to_string(v); }); + 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; + int x = 5; + int out = 0; expected e(x); - auto r = e.transform([&](int& v) { out = v; }); + auto r = e.transform([&](int& v) { out = v; }); REQUIRE(r.has_value()); CHECK(out == 5); } @@ -442,17 +442,17 @@ TEST_CASE("expected: transform to void", "[expected_ref]") { // ============================================================================= TEST_CASE("expected: const and_then", "[expected_ref]") { - int x = 10; + int x = 10; const expected e(x); - auto r = e.and_then([](int& v) -> expected { return v + 1; }); + 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; + int x = 3; const expected e(x); - auto r = e.transform([](int& v) { return v * 3; }); + auto r = e.transform([](int& v) { return v * 3; }); REQUIRE(r.has_value()); CHECK(*r == 9); } @@ -462,9 +462,9 @@ TEST_CASE("expected: const transform", "[expected_ref]") { // ============================================================================= TEST_CASE("expected: rvalue and_then", "[expected_ref]") { - int x = 5; + int x = 5; expected e(x); - auto r = std::move(e).and_then([](int& v) -> expected { return v * 3; }); + auto r = std::move(e).and_then([](int& v) -> expected { return v * 3; }); REQUIRE(r.has_value()); CHECK(*r == 15); } diff --git a/tests/beman/expected/expected_ref_constraints.test.cpp b/tests/beman/expected/expected_ref_constraints.test.cpp index a27faa1..9805c73 100644 --- a/tests/beman/expected/expected_ref_constraints.test.cpp +++ b/tests/beman/expected/expected_ref_constraints.test.cpp @@ -26,12 +26,12 @@ struct MoveOnly { }; 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; + 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 @@ -68,10 +68,8 @@ static_assert(!std::is_copy_constructible_v>, 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"); +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 @@ -79,13 +77,10 @@ static_assert(std::is_move_constructible_v>, 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_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"); +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 @@ -126,7 +121,7 @@ static_assert(std::is_constructible_v, const expected, const ex // =========================================================================== // Value assignment from int& works -static_assert(std::is_assignable_v&, int&>, - "expected must be assignable from int&"); +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&>, @@ -156,18 +150,15 @@ static_assert(std::is_assignable_v&, unexpected>, // S1: swap requires E swappable and move-constructible // =========================================================================== -static_assert(has_swap>, - "expected must be swappable"); +static_assert(has_swap>, "expected must be swappable"); -static_assert(!has_swap>, - "expected must not 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&"); +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>, @@ -177,8 +168,7 @@ static_assert(!has_emplace_from, int>, // V1: value_or requires is_object_v && !is_array_v (LWG4304) // =========================================================================== -static_assert(has_value_or, int>, - "expected must support value_or"); +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. @@ -189,7 +179,7 @@ static_assert(has_value_or, int>, // MC1/MC2: and_then/transform constrained by E constructibility // =========================================================================== -using RefMoveOnlyErr = expected; +using RefMoveOnlyErr = expected; [[maybe_unused]] auto ref_dummy_and_then = [](int&) { return expected(); }; static_assert(!has_and_then, @@ -231,8 +221,8 @@ static_assert(has_transform_error; -[[maybe_unused]] auto ref_normal_and_then = [](int&) { return expected(42); }; -[[maybe_unused]] auto ref_normal_or_else = [](int) -> 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); }; @@ -253,17 +243,17 @@ static_assert(has_transform_error e(x); - auto r = std::move(e).and_then([](int& v) { return expected(v + 1); }); + 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; + int x = 42; expected e(x); - auto r = std::move(e).transform([](int& v) { return v * 2; }); + 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 index e5bbf8d..503d817 100644 --- a/tests/beman/expected/expected_ref_e_array_fail.cpp +++ b/tests/beman/expected/expected_ref_e_array_fail.cpp @@ -4,6 +4,6 @@ // EXPECT: "E must not be an array type" #include void test() { - int x = 0; + 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 index 6a1a5e3..ce0b69a 100644 --- a/tests/beman/expected/expected_ref_e_cv_fail.cpp +++ b/tests/beman/expected/expected_ref_e_cv_fail.cpp @@ -4,6 +4,6 @@ // EXPECT: "E must not be cv-qualified" #include void test() { - int x = 0; + 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 index 14ce519..13705ec 100644 --- a/tests/beman/expected/expected_ref_e_ref_fail.cpp +++ b/tests/beman/expected/expected_ref_e_ref_fail.cpp @@ -4,6 +4,6 @@ // EXPECT: "E must not be a reference" #include void test() { - int x = 0; + int x = 0; beman::expected::expected e(x); // 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 index fdf1968..00efaca 100644 --- a/tests/beman/expected/expected_ref_e_void_fail.cpp +++ b/tests/beman/expected/expected_ref_e_void_fail.cpp @@ -4,6 +4,6 @@ // EXPECT: "E must not be void" #include void test() { - int x = 0; + 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 index 62cef32..0c4d8d0 100644 --- a/tests/beman/expected/expected_ref_inplace_value_fail.cpp +++ b/tests/beman/expected/expected_ref_inplace_value_fail.cpp @@ -4,6 +4,6 @@ // EXPECT: "no matching function" #include void test() { - int x = 5; + int x = 5; beman::expected::expected e(std::in_place, x); // must not compile } From d170e55bcc44db383dd13c26540463a91fcc9064 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 13:49:33 -0400 Subject: [PATCH 03/42] docs: mark Step 7 complete, write Step 8 handoff Update index.md checklist: Step 7 done (313 tests passing). Replace stale handoff-next.md with Step 7 completion summary and Step 8 (expected) context for the next agent. --- docs/plan/handoff-next.md | 127 +++++++++++++++++++++++++------------- docs/plan/index.md | 2 +- 2 files changed, 86 insertions(+), 43 deletions(-) diff --git a/docs/plan/handoff-next.md b/docs/plan/handoff-next.md index b1b347e..c08f598 100644 --- a/docs/plan/handoff-next.md +++ b/docs/plan/handoff-next.md @@ -1,63 +1,106 @@ -# 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). -All constraint, Mandates, trivial SMF, monadic SFINAE, and precondition gaps -identified in the audit are resolved. +Step 8 is the mirror image: value is owned (same as primary template), error +is a reference (pointer to error object). The union is: + +```cpp +union { + T val_; // active when has_val_ == true + E* unex_ptr_; // active when has_val_ == false +}; +bool has_val_; +``` + +### What to reuse from Step 7 + +- `detail::reference_constructs_from_temporary_v` — already exists, reuse for + preventing temporaries binding to `E&` +- Mandate static_asserts pattern for T (T must not be reference, void, array, + cv-qual — same as primary template); add E constraints for reference case +- Same monadic operation structure (2 ref-qual overloads each) +- Same negative-compile test pattern (CMakeLists, WILL_FAIL) + +### New test file + +`tests/beman/expected/expected_ref_e.test.cpp` — analogous to expected_ref.test.cpp +but testing the error-reference behavior: +- Error rebind: assigning `unexpected(new_err)` changes what error is pointed + to, does not assign through the error reference +- `error()` returns `E&` (dereferencing `unex_ptr_`) +- Shallow const on error: `const expected` still lets you mutate + through `.error()` +- Default constructor: works (has_value == true, value-inits T) +- Dangling: construction from temporary via `unexpect_t` must be deleted diff --git a/docs/plan/index.md b/docs/plan/index.md index e1fe8df..e27bc38 100644 --- a/docs/plan/index.md +++ b/docs/plan/index.md @@ -86,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 +- [x] 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 From 4ac2a3395139c4dc9bb5766dc2a2ccd9ba20d4c3 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 13:56:00 -0400 Subject: [PATCH 04/42] =?UTF-8?q?docs:=20update=20plan=20=E2=80=94=20base?= =?UTF-8?q?=20branch=20is=20expected-over-references,=20update=20handoff?= =?UTF-8?q?=20to=20current=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plan/handoff.md | 34 +++++++++++++------------ 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 +- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/plan/handoff.md b/docs/plan/handoff.md index 12bd2dd..acfa4bd 100644 --- a/docs/plan/handoff.md +++ b/docs/plan/handoff.md @@ -1,28 +1,29 @@ -# 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). +## Working Branch + +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. + ## Current State -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)`). +Steps 1–7 are complete and merged into `expected-over-references`. +The implementation includes the full conformant `expected` primary template, +`expected`, monadic operations for both, and `expected` (P2988 +reference-value specialization). 313 tests pass. -### Files +### Key Files -- `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 +- `include/beman/expected/expected.hpp` — full implementation: + `unexpected`, `bad_expected_access`, `expected`, `expected`, + `expected` (with monadic ops for all three) +- `tests/beman/expected/` — comprehensive test suite (313 tests) ### Build System @@ -79,4 +80,5 @@ worked before writing any tests. ## What Comes Next -Step 1: Implement `unexpected` — see `docs/plan/step1-unexpected.md`. +Step 8: `expected` error-reference specialization — see `docs/plan/step8-expected-ref-e.md`. +Create branch `step8-expected-ref-e` from `expected-over-references`. 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) From 2704e84be2181ceaa3267ca311f9223c9a0ae931 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 15:17:36 -0400 Subject: [PATCH 05/42] feat: implement expected error-reference specialization (Step 8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds expected partial specialization where the error type is a non-owning reference. Error is stored as E* with rebind semantics on assignment — changing which error object is referred to, not assigning through the reference. error() returns E& with shallow const. Also updates two pre-existing negative-compile tests whose expected diagnostics changed because expected is now valid (routes to the new specialization) and expected is now ambiguous. 349 tests pass. --- include/beman/expected/expected.hpp | 595 ++++++++++++++++++ tests/beman/expected/CMakeLists.txt | 17 +- tests/beman/expected/expected_ref_e.test.cpp | 349 ++++++++++ .../expected_ref_e_const_lvalue_fail.cpp | 10 + .../expected/expected_ref_e_ref_fail.cpp | 4 +- .../expected_ref_e_temporary_error_fail.cpp | 9 + .../beman/expected/expected_void_ref_fail.cpp | 4 +- 7 files changed, 979 insertions(+), 9 deletions(-) create mode 100644 tests/beman/expected/expected_ref_e.test.cpp create mode 100644 tests/beman/expected/expected_ref_e_const_lvalue_fail.cpp create mode 100644 tests/beman/expected/expected_ref_e_temporary_error_fail.cpp diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 8e34584..14e455b 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -2676,6 +2676,601 @@ class expected { }; }; +// ============================================================================= +// Partial specialization: expected — reference error type (P2988) +// ============================================================================= + +template +class expected { + static_assert(!std::is_void_v, "T must not be void in expected; use expected"); + static_assert(!std::is_reference_v, "T must not be a reference in expected; use expected"); + static_assert(!std::is_array_v, "T must not be an array type in expected"); + static_assert(std::is_object_v, "T must be an object type in expected"); + 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_object_v, "E must be an object type in expected"); + static_assert(!std::is_array_v, "E must not be an array type in expected"); + + public: + using value_type = T; + using error_type = E&; + using unexpected_type = unexpected>; + + template + using rebind = expected; + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + // Default constructor (value-initializes T) + constexpr expected() noexcept(std::is_nothrow_default_constructible_v) + requires std::is_default_constructible_v + : has_val_(true) { + std::construct_at(std::addressof(val_)); + } + + // 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) + : has_val_(rhs.has_val_) { + if (has_val_) + std::construct_at(std::addressof(val_), rhs.val_); + else + unex_ptr_ = rhs.unex_ptr_; + } + + // 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) + : has_val_(rhs.has_val_) { + if (has_val_) + std::construct_at(std::addressof(val_), std::move(rhs.val_)); + else + unex_ptr_ = rhs.unex_ptr_; + } + + // Value constructor + template + requires(!std::is_same_v, std::in_place_t> && + !std::is_same_v, expected> && + !detail::is_unexpected_specialization>::value && std::is_constructible_v) + constexpr explicit(!std::is_convertible_v) expected(U&& v) noexcept(std::is_nothrow_constructible_v) + : has_val_(true) { + std::construct_at(std::addressof(val_), std::forward(v)); + } + + // In-place value construction + template + requires std::is_constructible_v + constexpr explicit expected(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : has_val_(true) { + std::construct_at(std::addressof(val_), std::forward(args)...); + } + + template + requires std::is_constructible_v&, Args...> + constexpr explicit expected(std::in_place_t, std::initializer_list il, Args&&... args) : has_val_(true) { + std::construct_at(std::addressof(val_), il, std::forward(args)...); + } + + // Error constructor — binds E& from lvalue (prevents temporaries via deleted rvalue overload) + constexpr explicit expected(unexpect_t, E& err) noexcept : has_val_(false) { unex_ptr_ = std::addressof(err); } + + // Deleted: prevent binding rvalue (temporary) to E& + constexpr expected(unexpect_t, std::remove_const_t&&) = delete; + + // ------------------------------------------------------------------------- + // Destructor + // ------------------------------------------------------------------------- + + constexpr ~expected() + requires std::is_trivially_destructible_v + = default; + + constexpr ~expected() + requires(!std::is_trivially_destructible_v) + { + if (has_val_) + std::destroy_at(std::addressof(val_)); + } + + // ------------------------------------------------------------------------- + // Assignment + // ------------------------------------------------------------------------- + + // 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)) + { + if (has_val_ && rhs.has_val_) { + val_ = rhs.val_; + } else if (!has_val_ && !rhs.has_val_) { + unex_ptr_ = rhs.unex_ptr_; + } else if (has_val_) { + std::destroy_at(std::addressof(val_)); + unex_ptr_ = rhs.unex_ptr_; + has_val_ = false; + } else { + std::construct_at(std::addressof(val_), rhs.val_); + has_val_ = true; + } + return *this; + } + + // 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)) + { + if (has_val_ && rhs.has_val_) { + val_ = std::move(rhs.val_); + } else if (!has_val_ && !rhs.has_val_) { + unex_ptr_ = rhs.unex_ptr_; + } else if (has_val_) { + std::destroy_at(std::addressof(val_)); + unex_ptr_ = rhs.unex_ptr_; + has_val_ = false; + } else { + std::construct_at(std::addressof(val_), std::move(rhs.val_)); + has_val_ = true; + } + return *this; + } + + // Value assignment + template + requires(!std::is_same_v, expected> && + !detail::is_unexpected_specialization>::value && + std::is_constructible_v && std::is_assignable_v) + constexpr expected& operator=(U&& v) { + if (has_val_) { + val_ = std::forward(v); + } else { + std::construct_at(std::addressof(val_), std::forward(v)); + has_val_ = true; + } + return *this; + } + + // emplace — construct T in-place (nothrow required for exception safety when T is destroyed) + template + requires std::is_nothrow_constructible_v + constexpr T& emplace(Args&&... args) noexcept { + if (has_val_) + std::destroy_at(std::addressof(val_)); + has_val_ = false; + std::construct_at(std::addressof(val_), std::forward(args)...); + has_val_ = true; + return val_; + } + + template + requires std::is_nothrow_constructible_v&, Args...> + constexpr T& emplace(std::initializer_list il, Args&&... args) noexcept { + if (has_val_) + std::destroy_at(std::addressof(val_)); + has_val_ = false; + std::construct_at(std::addressof(val_), il, std::forward(args)...); + has_val_ = true; + return val_; + } + + // ------------------------------------------------------------------------- + // 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) + { + if (has_val_ && rhs.has_val_) { + using std::swap; + swap(val_, rhs.val_); + } else if (!has_val_ && !rhs.has_val_) { + std::swap(unex_ptr_, rhs.unex_ptr_); + } else if (has_val_) { + // this has value, rhs has error — exchange + E* tmp = rhs.unex_ptr_; + std::construct_at(std::addressof(rhs.val_), std::move(val_)); + std::destroy_at(std::addressof(val_)); + unex_ptr_ = tmp; + has_val_ = false; + rhs.has_val_ = true; + } else { + rhs.swap(*this); + } + } + + friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } + + // ------------------------------------------------------------------------- + // Observers + // ------------------------------------------------------------------------- + + constexpr T* operator->() noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::addressof(val_); + } + + constexpr const T* operator->() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::addressof(val_); + } + + constexpr T& operator*() & noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return val_; + } + + constexpr const T& operator*() const& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return val_; + } + + constexpr T&& operator*() && noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(val_); + } + + constexpr const T&& operator*() const&& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(val_); + } + + constexpr explicit operator bool() const noexcept { return has_val_; } + constexpr bool has_value() const noexcept { return has_val_; } + + constexpr T& value() & { + static_assert(std::is_copy_constructible_v>, + "value() requires E to be copy constructible"); + if (!has_val_) + throw bad_expected_access>(*unex_ptr_); + return val_; + } + + constexpr const T& value() const& { + static_assert(std::is_copy_constructible_v>, + "value() requires E to be copy constructible"); + if (!has_val_) + throw bad_expected_access>(*unex_ptr_); + return val_; + } + + constexpr T&& 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_ptr_)); + return std::move(val_); + } + + constexpr const T&& value() const&& { + static_assert(std::is_copy_constructible_v> && + std::is_move_constructible_v>, + "value() const&& requires E to be copy and move constructible"); + if (!has_val_) + throw bad_expected_access>(std::move(*unex_ptr_)); + return std::move(val_); + } + + // error() returns E& (shallow const — does not propagate const to the referent) + constexpr E& error() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return *unex_ptr_; + } + + template + requires(std::is_copy_constructible_v && std::is_convertible_v) + constexpr T value_or(U&& def) const& { + return has_val_ ? val_ : static_cast(std::forward(def)); + } + + template + requires(std::is_move_constructible_v && std::is_convertible_v) + constexpr T value_or(U&& def) && { + return has_val_ ? std::move(val_) : static_cast(std::forward(def)); + } + + template > + requires(std::is_copy_constructible_v> && std::is_convertible_v>) + constexpr std::remove_cv_t error_or(G&& def) const { + if (!has_val_) + return *unex_ptr_; + return static_cast>(std::forward(def)); + } + + // ------------------------------------------------------------------------- + // Monadic operations — all overloads pass E& to callables + // ------------------------------------------------------------------------- + + // and_then: f receives T (value); error propagates as E& + template + constexpr auto 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_ptr_); + } + + template + constexpr auto 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), std::move(val_)); + return U(unexpect, *unex_ptr_); + } + + template + constexpr auto 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_ptr_); + } + + template + constexpr auto 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), std::move(val_)); + return U(unexpect, *unex_ptr_); + } + + // or_else: f receives E& (the referenced error); value propagates + template + constexpr auto 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_ptr_); + } + + template + constexpr auto 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(std::move(val_)); + return std::invoke(std::forward(f), *unex_ptr_); + } + + template + constexpr auto 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_ptr_); + } + + template + constexpr auto 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(std::move(val_)); + return std::invoke(std::forward(f), *unex_ptr_); + } + + // transform: f receives T (value); error propagates as E& + template + constexpr auto transform(F&& f) & { + using U = std::remove_cv_t>; + 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 (has_val_) + return expected(std::invoke(std::forward(f), val_)); + return expected(unexpect, *unex_ptr_); + } + + template + constexpr auto transform(F&& f) && { + using U = std::remove_cv_t>; + 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 (has_val_) + return expected(std::invoke(std::forward(f), std::move(val_))); + return expected(unexpect, *unex_ptr_); + } + + template + constexpr auto transform(F&& f) const& { + using U = std::remove_cv_t>; + 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 (has_val_) + return expected(std::invoke(std::forward(f), val_)); + return expected(unexpect, *unex_ptr_); + } + + template + constexpr auto transform(F&& f) const&& { + using U = std::remove_cv_t>; + 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 (has_val_) + return expected(std::invoke(std::forward(f), std::move(val_))); + return expected(unexpect, *unex_ptr_); + } + + // transform_error: f receives E& (the referenced error); value propagates + template + constexpr auto 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_ptr_)); + } + + template + constexpr auto 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(std::move(val_)); + return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + } + + template + constexpr auto 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_ptr_)); + } + + template + constexpr auto 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(std::move(val_)); + return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + } + + // ------------------------------------------------------------------------- + // 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_; + E* unex_ptr_; + }; +}; + } // namespace expected } // namespace beman diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index a6d161b..0b1de38 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -16,6 +16,7 @@ target_sources( expected_monadic_constraints.test.cpp expected_ref.test.cpp expected_ref_constraints.test.cpp + expected_ref_e.test.cpp todo.test.cpp ) target_link_libraries( @@ -65,9 +66,7 @@ add_fail_test(unexpected_self_fail unexpected_self_fail.cpp add_fail_test(expected_t_ref_fail expected_t_ref_fail.cpp "delete" ) -add_fail_test(expected_e_ref_fail expected_e_ref_fail.cpp - "E must not be a 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" ) @@ -79,7 +78,7 @@ 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 - "E must not be a reference" + "T must not be void" ) add_fail_test(expected_void_array_fail expected_void_array_fail.cpp "E must not be an array type" @@ -128,9 +127,17 @@ add_fail_test(expected_ref_inplace_value_fail expected_ref_inplace_value_fail.cp "no matching function" ) +# Step 8 — expected reference error: dangling prevention +add_fail_test(expected_ref_e_temporary_error_fail expected_ref_e_temporary_error_fail.cpp + "delete|no matching function" +) +add_fail_test(expected_ref_e_const_lvalue_fail expected_ref_e_const_lvalue_fail.cpp + "no matching function|cannot bind" +) + # Step 7 — expected Mandates: E constraints add_fail_test(expected_ref_e_ref_fail expected_ref_e_ref_fail.cpp - "E must not be a reference" + "ambiguous|E must not be a reference" ) add_fail_test(expected_ref_e_void_fail expected_ref_e_void_fail.cpp "E must not be void" 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..2cd5b22 --- /dev/null +++ b/tests/beman/expected/expected_ref_e.test.cpp @@ -0,0 +1,349 @@ +// tests/beman/expected/expected_ref_e.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include + +#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) +static_assert(!std::is_constructible_v, unexpect_t, int&&>); + +// ============================================================================= +// 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); +} + +// ============================================================================= +// 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); +} + +// ============================================================================= +// 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); +} 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_ref_fail.cpp b/tests/beman/expected/expected_ref_e_ref_fail.cpp index 13705ec..615866b 100644 --- a/tests/beman/expected/expected_ref_e_ref_fail.cpp +++ b/tests/beman/expected/expected_ref_e_ref_fail.cpp @@ -1,7 +1,7 @@ // tests/beman/expected/expected_ref_e_ref_fail.cpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE: expected where E is a reference type is ill-formed -// EXPECT: "E must not be a reference" +// NEGATIVE: expected is ambiguous — both expected and expected match +// EXPECT: "ambiguous|E must not be a reference" #include void test() { int x = 0; 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_void_ref_fail.cpp b/tests/beman/expected/expected_void_ref_fail.cpp index dd5c8cc..ba531c9 100644 --- a/tests/beman/expected/expected_void_ref_fail.cpp +++ b/tests/beman/expected/expected_void_ref_fail.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE: expected where E is a reference is ill-formed -// EXPECT: "E must not be a reference" +// NEGATIVE: expected is ill-formed — T=void not allowed in expected +// EXPECT: "T must not be void" #include beman::expected::expected x; // should fail From e654ea05d8fb36a5149c6ff2681e47508997c72c Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 15:19:21 -0400 Subject: [PATCH 06/42] docs: mark Step 8 complete, update handoff for Step 9 --- docs/plan/handoff.md | 30 +++++++++++++++++++++++------- docs/plan/index.md | 2 +- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/docs/plan/handoff.md b/docs/plan/handoff.md index acfa4bd..662a3f1 100644 --- a/docs/plan/handoff.md +++ b/docs/plan/handoff.md @@ -13,17 +13,32 @@ the integration branch for this work. ## Current State -Steps 1–7 are complete and merged into `expected-over-references`. +Steps 1–8 are complete and merged into `expected-over-references` +(Step 8 is on branch `step8-expected-ref-e`, ready to merge). The implementation includes the full conformant `expected` primary template, -`expected`, monadic operations for both, and `expected` (P2988 -reference-value specialization). 313 tests pass. +`expected`, monadic operations for both, `expected` (P2988 +reference-value specialization), and `expected` (P2988 reference-error +specialization). 349 tests pass. ### Key Files - `include/beman/expected/expected.hpp` — full implementation: `unexpected`, `bad_expected_access`, `expected`, `expected`, - `expected` (with monadic ops for all three) -- `tests/beman/expected/` — comprehensive test suite (313 tests) + `expected`, `expected` (with monadic ops for all four) +- `tests/beman/expected/` — comprehensive test suite (349 tests) + +### Step 8 Design Notes + +- `expected` stores the error as `E*` (pointer to the referent); value T + is owned (same as primary template) +- `error()` returns `E&` with **shallow const** — `const expected` still + permits mutation of the error referent through `.error()` +- Assignment between `expected` objects rebinds the error pointer, never + assigns through the reference +- `(unexpect_t, E&&)` constructor is deleted to prevent binding temporaries +- `unexpected` forms (where both T and E are references) are ambiguous + between `expected` and `expected`; the negative test for this was + updated to match the "ambiguous" diagnostic ### Build System @@ -80,5 +95,6 @@ worked before writing any tests. ## What Comes Next -Step 8: `expected` error-reference specialization — see `docs/plan/step8-expected-ref-e.md`. -Create branch `step8-expected-ref-e` from `expected-over-references`. +Step 9: `expected` both-reference specialization — see `docs/plan/step9-expected-ref-both.md`. +Create branch `step9-expected-ref-both` from `expected-over-references` (after merging +`step8-expected-ref-e` into `expected-over-references`). diff --git a/docs/plan/index.md b/docs/plan/index.md index e27bc38..937ea9d 100644 --- a/docs/plan/index.md +++ b/docs/plan/index.md @@ -87,6 +87,6 @@ Steps 7-10 are the reference specializations (the novel work in this proposal). - [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 - [x] 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 +- [x] 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 From c07e8bba1e5fa6b2d3735cdfb1f6f9bd52762a23 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 15:43:22 -0400 Subject: [PATCH 07/42] fix: align API surface of expected with expected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four gaps identified by comparing the two reference specializations: 1. expected: add converting constructors from expected (mirrors expected's converting constructors from expected) 2. expected: add if constexpr void-U branch in transform() (mirrors expected; returns expected — active once Step 10 lands) 3. expected: add value_type static_assert to all 4 or_else() overloads (was present in expected and primary template, missing from step 7) 4. Add negative compile test for the new or_else value_type mandate on expected 354 tests pass. --- include/beman/expected/expected.hpp | 144 ++++++++++++++---- tests/beman/expected/CMakeLists.txt | 5 + tests/beman/expected/expected_ref_e.test.cpp | 42 ++++- ...cted_ref_or_else_wrong_value_type_fail.cpp | 15 ++ 4 files changed, 172 insertions(+), 34 deletions(-) create mode 100644 tests/beman/expected/expected_ref_or_else_wrong_value_type_fail.cpp diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 14e455b..dc55552 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -2454,6 +2454,8 @@ class expected { 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_); @@ -2464,6 +2466,8 @@ class expected { 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_)); @@ -2474,6 +2478,8 @@ class expected { 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_); @@ -2484,6 +2490,8 @@ class expected { 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_)); @@ -2772,6 +2780,34 @@ class expected { // Deleted: prevent binding rvalue (temporary) to E& constexpr expected(unexpect_t, std::remove_const_t&&) = delete; + // Converting constructor from expected (copy) — mirrors expected's from expected + template + requires(std::is_constructible_v && std::is_convertible_v) + constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) + expected(const expected& rhs) + : has_val_(rhs.has_value()) { + if (has_val_) + std::construct_at(std::addressof(val_), *rhs); + else { + E& r = rhs.error(); + unex_ptr_ = std::addressof(r); + } + } + + // Converting constructor from expected (move) — moves owned value, rebinds error pointer + template + requires(std::is_constructible_v && std::is_convertible_v) + constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) + expected(expected&& rhs) + : has_val_(rhs.has_value()) { + if (has_val_) + std::construct_at(std::addressof(val_), std::move(*rhs)); + else { + E& r = rhs.error(); + unex_ptr_ = std::addressof(r); + } + } + // ------------------------------------------------------------------------- // Destructor // ------------------------------------------------------------------------- @@ -3136,53 +3172,97 @@ class expected { template constexpr auto transform(F&& f) & { using U = std::remove_cv_t>; - 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 (has_val_) - return expected(std::invoke(std::forward(f), val_)); - return expected(unexpect, *unex_ptr_); + 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_ptr_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), val_)); + return expected(unexpect, *unex_ptr_); + } } template constexpr auto transform(F&& f) && { using U = std::remove_cv_t>; - 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 (has_val_) - return expected(std::invoke(std::forward(f), std::move(val_))); - return expected(unexpect, *unex_ptr_); + 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), std::move(val_)); + if (has_val_) + return expected(); + return expected(unexpect, *unex_ptr_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), std::move(val_))); + return expected(unexpect, *unex_ptr_); + } } template constexpr auto transform(F&& f) const& { using U = std::remove_cv_t>; - 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 (has_val_) - return expected(std::invoke(std::forward(f), val_)); - return expected(unexpect, *unex_ptr_); + 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_ptr_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), val_)); + return expected(unexpect, *unex_ptr_); + } } template constexpr auto transform(F&& f) const&& { using U = std::remove_cv_t>; - 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 (has_val_) - return expected(std::invoke(std::forward(f), std::move(val_))); - return expected(unexpect, *unex_ptr_); + 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), std::move(val_)); + if (has_val_) + return expected(); + return expected(unexpect, *unex_ptr_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), std::move(val_))); + return expected(unexpect, *unex_ptr_); + } } // transform_error: f receives E& (the referenced error); value propagates diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 0b1de38..0d24df1 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -135,6 +135,11 @@ add_fail_test(expected_ref_e_const_lvalue_fail expected_ref_e_const_lvalue_fail. "no matching function|cannot bind" ) +# 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 7 — expected Mandates: E constraints add_fail_test(expected_ref_e_ref_fail expected_ref_e_ref_fail.cpp "ambiguous|E must not be a reference" diff --git a/tests/beman/expected/expected_ref_e.test.cpp b/tests/beman/expected/expected_ref_e.test.cpp index 2cd5b22..8740ea9 100644 --- a/tests/beman/expected/expected_ref_e.test.cpp +++ b/tests/beman/expected/expected_ref_e.test.cpp @@ -52,6 +52,9 @@ static_assert(!std::is_trivially_destructible_v>); // Cannot construct from temporary error (rvalue deleted) static_assert(!std::is_constructible_v, unexpect_t, int&&>); +// Converting construction from expected +static_assert(std::is_constructible_v, const expected&>); + // ============================================================================= // Construction — value side (same as primary template) // ============================================================================= @@ -122,7 +125,7 @@ TEST_CASE("expected: rebind does NOT assign through error reference", "[ex expected a(unexpect, err1); expected b(unexpect, err2); a = b; - CHECK(err1 == 10); // err1 unchanged + CHECK(err1 == 10); // err1 unchanged CHECK(a.error() == 20); } @@ -333,11 +336,46 @@ TEST_CASE("expected: transform_error transforms E&", "[expected_ref_e]") { 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); }); + 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 // ============================================================================= 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..b731e86 --- /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; }); +} From 712a4e493d70a9952ed59f67aea950cfc96cd110 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 16:48:31 -0400 Subject: [PATCH 08/42] feat: implement expected both-reference specialization (Step 9) - Add expected partial specialization with dual-pointer storage (union { T* val_; E* unex_; } + bool has_val_) - Fully trivial: copy/move/destroy all defaulted (just pointers + bool) - No default constructor (T& cannot be default-initialized) - Shallow const on both sides: operator*() and error() return T& and E& regardless of const on the expected object - Dangling prevention: deleted constructors for rvalue T and rvalue E - Value rebind and error rebind via copy/move assignment (pointer swap) - emplace() rebinds T& reference - All 4 ref-qual overloads of and_then, or_else, transform, transform_error - transform_error returns expected (step-7 specialization) - transform returns expected (step-8 specialization) - Update expected_ref_e_ref_fail.cpp: expected is now valid; test now uses expected (rvalue-ref E, still invalid) - 401 tests pass, lint clean --- docs/plan/handoff.md | 56 +- docs/plan/index.md | 2 +- include/beman/expected/expected.hpp | 500 ++++++++++++++++++ tests/beman/expected/CMakeLists.txt | 12 + .../beman/expected/expected_ref_both.test.cpp | 468 ++++++++++++++++ .../expected_ref_both_no_default_fail.cpp | 9 + .../expected_ref_both_temporary_err_fail.cpp | 9 + .../expected_ref_both_temporary_val_fail.cpp | 9 + .../expected/expected_ref_e_ref_fail.cpp | 8 +- ...cted_ref_or_else_wrong_value_type_fail.cpp | 4 +- 10 files changed, 1048 insertions(+), 29 deletions(-) create mode 100644 tests/beman/expected/expected_ref_both.test.cpp create mode 100644 tests/beman/expected/expected_ref_both_no_default_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 diff --git a/docs/plan/handoff.md b/docs/plan/handoff.md index 662a3f1..eaff0f4 100644 --- a/docs/plan/handoff.md +++ b/docs/plan/handoff.md @@ -13,32 +13,43 @@ the integration branch for this work. ## Current State -Steps 1–8 are complete and merged into `expected-over-references` -(Step 8 is on branch `step8-expected-ref-e`, ready to merge). +Steps 1–9 are complete. Steps 1–8 are merged into `expected-over-references`. +Step 9 is on branch `step9-expected-ref-both`, ready to merge. The implementation includes the full conformant `expected` primary template, `expected`, monadic operations for both, `expected` (P2988 -reference-value specialization), and `expected` (P2988 reference-error -specialization). 349 tests pass. +reference-value specialization), `expected` (P2988 reference-error +specialization), and `expected` (P2988 both-reference specialization). +401 tests pass. ### Key Files - `include/beman/expected/expected.hpp` — full implementation: `unexpected`, `bad_expected_access`, `expected`, `expected`, - `expected`, `expected` (with monadic ops for all four) -- `tests/beman/expected/` — comprehensive test suite (349 tests) - -### Step 8 Design Notes - -- `expected` stores the error as `E*` (pointer to the referent); value T - is owned (same as primary template) -- `error()` returns `E&` with **shallow const** — `const expected` still - permits mutation of the error referent through `.error()` -- Assignment between `expected` objects rebinds the error pointer, never - assigns through the reference -- `(unexpect_t, E&&)` constructor is deleted to prevent binding temporaries -- `unexpected` forms (where both T and E are references) are ambiguous - between `expected` and `expected`; the negative test for this was - updated to match the "ambiguous" diagnostic + `expected`, `expected`, `expected` (with monadic ops for all) +- `tests/beman/expected/` — comprehensive test suite (401 tests) + +### Step 9 Design Notes + +- `expected` stores both sides as pointers: `T* val_`, `E* unex_` + in a union with `bool has_val_` +- **Fully trivial**: both union members are pointers (same size, trivially + copyable), so copy, move, destructor are all `= default` +- **No default constructor**: deleted (T& cannot be default-initialized) +- **Shallow const on both sides**: `operator*()`, `error()` return the + underlying reference regardless of const on the `expected` object +- **Value rebind**: `operator=(U&&)` rebinds the T* pointer; no destructor call +- **Error rebind**: done via copy/move assignment of another `expected` + (assigning from `unexpected` was not added — `unexpected` stores by value + so its `error()` returns `const G&` which can't bind to non-const `E&`) +- The `expected_ref_e_ref_fail.cpp` negative compile test was updated: it now + tests `expected` (rvalue reference as E, still invalid in + `expected`) instead of the previously-tested `expected` + which is now valid via the `expected` specialization +- `transform_error(F)` returns `expected` — the step-7 specialization + which accepts T& in its constructor +- `transform(F)` returns `expected` — step-8 specialization; + void-return case returns `expected` (step 10's specialization, + not yet implemented but won't fail until actually instantiated with void F) ### Build System @@ -95,6 +106,7 @@ worked before writing any tests. ## What Comes Next -Step 9: `expected` both-reference specialization — see `docs/plan/step9-expected-ref-both.md`. -Create branch `step9-expected-ref-both` from `expected-over-references` (after merging -`step8-expected-ref-e` into `expected-over-references`). +Step 10: `expected` void+reference-error specialization — see +`docs/plan/step10-expected-void-ref-e.md`. +Create branch `step10-expected-void-ref-e` from `expected-over-references` +(after merging `step9-expected-ref-both` into `expected-over-references`). diff --git a/docs/plan/index.md b/docs/plan/index.md index 937ea9d..338a167 100644 --- a/docs/plan/index.md +++ b/docs/plan/index.md @@ -88,5 +88,5 @@ Steps 7-10 are the reference specializations (the novel work in this proposal). - [x] Step 6: `expected` monadic — and_then, or_else, transform, transform_error - [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 -- [ ] Step 9: `expected` — both pointer storage, rebind both, observers, monadic ops +- [x] Step 9: `expected` — both pointer storage, rebind both, observers, monadic ops - [ ] Step 10: `expected` — no value storage, error pointer, rebind error, observers, monadic ops diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index dc55552..4361f00 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -3351,6 +3351,506 @@ class expected { }; }; +// ============================================================================= +// Partial specialization: expected — both value and error are references (P2988) +// ============================================================================= + +template +class expected { + static_assert(!std::is_array_v, "T must not be an array type in expected"); + static_assert(std::is_object_v, "T must be an object type in expected"); + 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_object_v, "E must be an object type in expected"); + static_assert(!std::is_array_v, "E must not be an array type in expected"); + + public: + using value_type = T&; + using error_type = E&; + using unexpected_type = unexpected>; + + template + using rebind = expected; + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + expected() = delete; + + // Copy/move constructors — trivial (union holds only pointers + bool has_val_) + constexpr expected(const expected&) = default; + constexpr expected(expected&&) = default; + + // Value constructor — binds T& from lvalue (dangling prevention via deleted rvalue overload) + template + requires(!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 : has_val_(true) { + T& r = std::forward(u); + val_ = std::addressof(r); + } + + // Deleted: prevent binding rvalue (temporary) to T& + template + requires(detail::reference_constructs_from_temporary_v) + constexpr expected(U&&) = delete; + + // Error constructor — binds E& from lvalue (prevents temporaries via deleted rvalue overload) + constexpr explicit expected(unexpect_t, E& err) noexcept : has_val_(false) { unex_ = std::addressof(err); } + + // Deleted: prevent binding rvalue (temporary) to E& + constexpr expected(unexpect_t, std::remove_const_t&&) = delete; + + // Converting constructor from expected (copy) + template + requires(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) + : has_val_(rhs.has_value()) { + if (has_val_) { + T& r = *rhs; + val_ = std::addressof(r); + } else { + E& e = rhs.error(); + unex_ = std::addressof(e); + } + } + + // Converting constructor from expected (move — pointers, so same as copy) + template + requires(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) + : has_val_(rhs.has_value()) { + if (has_val_) { + T& r = *rhs; + val_ = std::addressof(r); + } else { + E& e = rhs.error(); + unex_ = std::addressof(e); + } + } + + // ------------------------------------------------------------------------- + // Destructor — trivial (union holds only pointers) + // ------------------------------------------------------------------------- + + constexpr ~expected() = default; + + // ------------------------------------------------------------------------- + // Assignment + // ------------------------------------------------------------------------- + + // Copy/move — trivial (just pointers + bool) + constexpr expected& operator=(const expected&) = default; + constexpr expected& operator=(expected&&) = default; + + // Value rebind — rebinds T* (no destruction needed, pointer is trivial) + 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) noexcept { + T& r = std::forward(u); + val_ = std::addressof(r); + has_val_ = true; + return *this; + } + + // emplace — rebind T& (pointer transition is trivial) + template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr T& emplace(U&& u) noexcept { + T& r = std::forward(u); + val_ = std::addressof(r); + has_val_ = true; + return *val_; + } + + // ------------------------------------------------------------------------- + // Swap + // ------------------------------------------------------------------------- + + constexpr void swap(expected& rhs) noexcept { + if (has_val_ && rhs.has_val_) { + std::swap(val_, rhs.val_); + } else if (!has_val_ && !rhs.has_val_) { + std::swap(unex_, rhs.unex_); + } else if (has_val_) { + T* my_val = val_; + E* rhs_err = rhs.unex_; + unex_ = rhs_err; + rhs.val_ = my_val; + has_val_ = false; + rhs.has_val_ = true; + } else { + rhs.swap(*this); + } + } + + friend constexpr void swap(expected& x, expected& y) noexcept { x.swap(y); } + + // ------------------------------------------------------------------------- + // Observers — shallow const on both sides (references don't propagate const) + // ------------------------------------------------------------------------- + + constexpr T* operator->() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return val_; + } + + constexpr T& operator*() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return *val_; + } + + constexpr explicit operator bool() const noexcept { return has_val_; } + constexpr bool has_value() const noexcept { return has_val_; } + + constexpr T& value() const& { + static_assert(std::is_copy_constructible_v>, + "value() requires E to be copy constructible"); + if (!has_val_) + throw bad_expected_access>(*unex_); + return *val_; + } + + constexpr T& value() && { + static_assert(std::is_copy_constructible_v>, + "value() requires E to be copy constructible"); + if (!has_val_) + throw bad_expected_access>(*unex_); + return *val_; + } + + // error() — shallow const: always returns E& regardless of const on expected + constexpr E& error() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return *unex_; + } + + template > + requires(std::is_object_v && !std::is_array_v) + constexpr std::remove_cv_t 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 > + constexpr std::remove_cv_t error_or(G&& def) const { + static_assert(std::is_copy_constructible_v>, + "error_or requires E to be copy constructible"); + static_assert(std::is_convertible_v>, + "error_or requires is_convertible_v>"); + if (!has_val_) + return *unex_; + return static_cast>(std::forward(def)); + } + + // ------------------------------------------------------------------------- + // Monadic operations — T& value side, E& error side (shallow const on both) + // ------------------------------------------------------------------------- + + // and_then: f receives T& (value); error propagates as E& + template + constexpr auto 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_); + } + + template + constexpr auto 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_); + } + + template + constexpr auto 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_); + } + + template + constexpr auto 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_); + } + + // or_else: f receives E& (the referenced error); value propagates as T& + template + constexpr auto 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_); + } + + template + constexpr auto 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_); + } + + template + constexpr auto 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_); + } + + template + constexpr auto 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_); + } + + // transform: f receives T& (value); error propagates as E&; result is expected + template + constexpr auto 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_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, *unex_); + } + } + + template + constexpr auto 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_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, *unex_); + } + } + + template + constexpr auto 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_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, *unex_); + } + } + + template + constexpr auto 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_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, *unex_); + } + } + + // transform_error: f receives E& (the referenced error); value propagates as T&; result is expected + template + constexpr auto 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_)); + } + + template + constexpr auto 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_)); + } + + template + constexpr auto 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_)); + } + + template + constexpr auto 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_)); + } + + // ------------------------------------------------------------------------- + // 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_; + E* unex_; + }; +}; + } // namespace expected } // namespace beman diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 0d24df1..c6e000d 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -17,6 +17,7 @@ target_sources( expected_ref.test.cpp expected_ref_constraints.test.cpp expected_ref_e.test.cpp + expected_ref_both.test.cpp todo.test.cpp ) target_link_libraries( @@ -140,6 +141,17 @@ add_fail_test(expected_ref_or_else_wrong_value_type_fail expected_ref_or_else_wr "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 + "delete|no matching function" +) +add_fail_test(expected_ref_both_temporary_err_fail expected_ref_both_temporary_err_fail.cpp + "delete|no matching function" +) +add_fail_test(expected_ref_both_no_default_fail expected_ref_both_no_default_fail.cpp + "delete|no matching function" +) + # Step 7 — expected Mandates: E constraints add_fail_test(expected_ref_e_ref_fail expected_ref_e_ref_fail.cpp "ambiguous|E must not be a reference" 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..ece6344 --- /dev/null +++ b/tests/beman/expected/expected_ref_both.test.cpp @@ -0,0 +1,468 @@ +// tests/beman/expected/expected_ref_both.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include + +#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 (E& rvalue deleted) +static_assert(!std::is_constructible_v, unexpect_t, int&&>); + +// 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); +} + +// ============================================================================= +// 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); +} 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_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_e_ref_fail.cpp b/tests/beman/expected/expected_ref_e_ref_fail.cpp index 615866b..575d3d8 100644 --- a/tests/beman/expected/expected_ref_e_ref_fail.cpp +++ b/tests/beman/expected/expected_ref_e_ref_fail.cpp @@ -1,9 +1,9 @@ // tests/beman/expected/expected_ref_e_ref_fail.cpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE: expected is ambiguous — both expected and expected match -// EXPECT: "ambiguous|E must not be a reference" +// NEGATIVE: expected — rvalue reference as E in expected must fail +// EXPECT: "E must not be a reference" #include void test() { - int x = 0; - beman::expected::expected e(x); // must not compile + int x = 0; + beman::expected::expected e(x); // E is rvalue ref — 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 index b731e86..c257c7c 100644 --- 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 @@ -8,8 +8,8 @@ using E = beman::expected::expected; using G = beman::expected::expected; } // namespace void test() { - double d = 0; - E e = beman::expected::unexpected(0); + 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; }); } From 4edd670ebb79b2bdf6db1aacee7b3160d0a60425 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 17:39:58 -0400 Subject: [PATCH 09/42] fix: align API surface of expected with expected and expected All differences are intentional design decisions, documented by new tests: No operator=(unexpected...) or constructor from unexpected in expected and expected: unexpected stores G by value, so binding E& to it would create a dangling reference when the unexpected object is destroyed. Added 4 negative compile tests. Added positive tests showing the safe idiom for rebinding the error reference: move-assign from a named expected(unexpect, new_err). Added or_else wrong value_type mandate test for expected. Summary of all intentional differences: - No default ctor / no in_place_t ctor in T& specializations (can't default-construct or in-place-construct a reference) - operator*/value() have fewer overloads where T is a reference (shallow const: reference types don't propagate const to the referent) - error() has fewer overloads where E is a reference (same shallow const) - value_or has 2 overloads only when T is owned; error_or has 2 only when E is owned (move semantics apply only to owned sides) - and_then/transform have requires on E construction in expected (propagating owned E requires constructibility); not needed for E& - or_else F arg type varies by const-qualifier only for owned E; for E& it is always E& (shallow const) - transform result is expected or expected based on E; transform_error result is expected or expected based on T 408 tests pass. --- tests/beman/expected/CMakeLists.txt | 28 ++++ .../beman/expected/expected_ref_both.test.cpp | 140 ++++++++++-------- ...pected_ref_both_assign_unexpected_fail.cpp | 13 ++ ...ef_both_construct_from_unexpected_fail.cpp | 10 ++ ...ref_both_or_else_wrong_value_type_fail.cpp | 15 ++ tests/beman/expected/expected_ref_e.test.cpp | 11 ++ .../expected_ref_e_assign_unexpected_fail.cpp | 12 ++ ...d_ref_e_construct_from_unexpected_fail.cpp | 10 ++ 8 files changed, 174 insertions(+), 65 deletions(-) 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_or_else_wrong_value_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_construct_from_unexpected_fail.cpp diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index c6e000d..580081d 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -136,6 +136,17 @@ add_fail_test(expected_ref_e_const_lvalue_fail expected_ref_e_const_lvalue_fail. "no matching function|cannot bind" ) +# 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 + "conversion from|no matching function" +) +add_fail_test(expected_ref_e_assign_unexpected_fail + expected_ref_e_assign_unexpected_fail.cpp + "no match for|no matching function" +) + # 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" @@ -152,6 +163,23 @@ add_fail_test(expected_ref_both_no_default_fail expected_ref_both_no_default_fai "delete|no matching function" ) +# 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 + "conversion from|no matching function" +) +add_fail_test(expected_ref_both_assign_unexpected_fail + expected_ref_both_assign_unexpected_fail.cpp + "no match for|no matching function" +) + +# 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 "ambiguous|E must not be a reference" diff --git a/tests/beman/expected/expected_ref_both.test.cpp b/tests/beman/expected/expected_ref_both.test.cpp index ece6344..3f00fc6 100644 --- a/tests/beman/expected/expected_ref_both.test.cpp +++ b/tests/beman/expected/expected_ref_both.test.cpp @@ -62,7 +62,7 @@ static_assert(std::is_constructible_v, const expected: construct from lvalue reference (value)", "[expected_ref_both]") { - int x = 42; + int x = 42; expected e(x); REQUIRE(e.has_value()); CHECK(&*e == &x); @@ -70,7 +70,7 @@ TEST_CASE("expected: construct from lvalue reference (value)", "[expected } TEST_CASE("expected: operator-> returns T*", "[expected_ref_both]") { - std::string s = "hello"; + std::string s = "hello"; expected e(s); REQUIRE(e.has_value()); CHECK(e->size() == 5); @@ -81,7 +81,7 @@ TEST_CASE("expected: operator-> returns T*", "[expected_ref_both]") { // ============================================================================= TEST_CASE("expected: construct from error lvalue reference", "[expected_ref_both]") { - int err = 7; + int err = 7; expected e(unexpect, err); REQUIRE(!e.has_value()); CHECK(&e.error() == &err); @@ -93,7 +93,7 @@ TEST_CASE("expected: construct from error lvalue reference", "[expected_r // ============================================================================= TEST_CASE("expected: copy construct preserves value pointer", "[expected_ref_both]") { - int x = 42; + int x = 42; expected a(x); expected b = a; REQUIRE(b.has_value()); @@ -101,7 +101,7 @@ TEST_CASE("expected: copy construct preserves value pointer", "[expected_ } TEST_CASE("expected: copy construct preserves error pointer", "[expected_ref_both]") { - int err = 5; + int err = 5; expected a(unexpect, err); expected b = a; REQUIRE(!b.has_value()); @@ -109,7 +109,7 @@ TEST_CASE("expected: copy construct preserves error pointer", "[expected_ } TEST_CASE("expected: move construct preserves value pointer", "[expected_ref_both]") { - int x = 42; + int x = 42; expected a(x); expected b = std::move(a); REQUIRE(b.has_value()); @@ -117,7 +117,7 @@ TEST_CASE("expected: move construct preserves value pointer", "[expected_ } TEST_CASE("expected: move construct preserves error pointer", "[expected_ref_both]") { - int err = 5; + int err = 5; expected a(unexpect, err); expected b = std::move(a); REQUIRE(!b.has_value()); @@ -129,7 +129,7 @@ TEST_CASE("expected: move construct preserves error pointer", "[expected_ // ============================================================================= TEST_CASE("expected: value rebind via copy assignment", "[expected_ref_both]") { - int x1 = 1, x2 = 2; + int x1 = 1, x2 = 2; expected a(x1); expected b(x2); a = b; @@ -140,25 +140,25 @@ TEST_CASE("expected: value rebind via copy assignment", "[expected_ref_bo } TEST_CASE("expected: rebind does NOT assign through value reference", "[expected_ref_both]") { - int x1 = 10, x2 = 20; + int x1 = 10, x2 = 20; expected a(x1); expected b(x2); a = b; - CHECK(x1 == 10); // x1 unchanged + CHECK(x1 == 10); // x1 unchanged CHECK(*a == 20); } TEST_CASE("expected: value rebind operator=(U&&)", "[expected_ref_both]") { - int x1 = 1, x2 = 99; + int x1 = 1, x2 = 99; expected e(x1); e = x2; REQUIRE(e.has_value()); CHECK(&*e == &x2); - CHECK(x1 == 1); // x1 unchanged + CHECK(x1 == 1); // x1 unchanged } TEST_CASE("expected: transition from error to value via rebind", "[expected_ref_both]") { - int x = 42, err = 5; + int x = 42, err = 5; expected e(unexpect, err); REQUIRE(!e.has_value()); e = x; @@ -171,7 +171,7 @@ TEST_CASE("expected: transition from error to value via rebind", "[expect // ============================================================================= TEST_CASE("expected: error rebind via copy assignment", "[expected_ref_both]") { - int e1 = 1, e2 = 2; + int e1 = 1, e2 = 2; expected a(unexpect, e1); expected b(unexpect, e2); a = b; @@ -182,7 +182,7 @@ TEST_CASE("expected: error rebind via copy assignment", "[expected_ref_bo } TEST_CASE("expected: transition from value to error via copy assignment", "[expected_ref_both]") { - int x = 42, err = 5; + int x = 42, err = 5; expected a(x); expected b(unexpect, err); a = b; @@ -190,22 +190,33 @@ TEST_CASE("expected: transition from value to error via copy assignment", 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; + int x1 = 1, x2 = 99; expected e(x1); - int& r = e.emplace(x2); + int& r = e.emplace(x2); REQUIRE(e.has_value()); CHECK(&r == &x2); CHECK(&*e == &x2); - CHECK(x1 == 1); // x1 unchanged + CHECK(x1 == 1); // x1 unchanged } TEST_CASE("expected: emplace from error state", "[expected_ref_both]") { - int x = 42, err = 5; + int x = 42, err = 5; expected e(unexpect, err); e.emplace(x); REQUIRE(e.has_value()); @@ -217,14 +228,14 @@ TEST_CASE("expected: emplace from error state", "[expected_ref_both]") { // ============================================================================= TEST_CASE("expected: shallow const allows mutation of value referent", "[expected_ref_both]") { - int x = 10; + 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; + int err = 10; const expected e(unexpect, err); e.error() = 20; CHECK(err == 20); @@ -235,27 +246,27 @@ TEST_CASE("expected: shallow const allows mutation of error referent", "[ // ============================================================================= TEST_CASE("expected: operator*() returns T& (mutation visible)", "[expected_ref_both]") { - int x = 42; + 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; + 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; + 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; + int err = 7; expected e(unexpect, err); static_assert(std::is_same_v); e.error() = 99; @@ -263,7 +274,7 @@ TEST_CASE("expected: error() returns E& (mutation visible)", "[expected_r } TEST_CASE("expected: value_or returns T by value", "[expected_ref_both]") { - int x = 42, err = 0; + int x = 42, err = 0; expected a(x); expected b(unexpect, err); CHECK(a.value_or(0) == 42); @@ -271,7 +282,7 @@ TEST_CASE("expected: value_or returns T by value", "[expected_ref_both]") } TEST_CASE("expected: error_or returns E by value", "[expected_ref_both]") { - int err = 7, x = 0; + int err = 7, x = 0; expected a(unexpect, err); expected b(x); CHECK(a.error_or(0) == 7); @@ -283,17 +294,17 @@ TEST_CASE("expected: error_or returns E by value", "[expected_ref_both]") // ============================================================================= TEST_CASE("expected: swap value-value rebinds pointers", "[expected_ref_both]") { - int x1 = 1, x2 = 2; + 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(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; + int e1 = 1, e2 = 2; expected a(unexpect, e1), b(unexpect, e2); a.swap(b); CHECK(&a.error() == &e2); @@ -301,7 +312,7 @@ TEST_CASE("expected: swap error-error rebinds pointers", "[expected_ref_b } TEST_CASE("expected: swap value-error", "[expected_ref_both]") { - int x = 42, err = 5; + int x = 42, err = 5; expected a(x), b(unexpect, err); a.swap(b); REQUIRE(!a.has_value()); @@ -311,7 +322,7 @@ TEST_CASE("expected: swap value-error", "[expected_ref_both]") { } TEST_CASE("expected: swap error-value", "[expected_ref_both]") { - int x = 42, err = 5; + int x = 42, err = 5; expected a(unexpect, err), b(x); a.swap(b); REQUIRE(a.has_value()); @@ -325,33 +336,33 @@ TEST_CASE("expected: swap error-value", "[expected_ref_both]") { // ============================================================================= TEST_CASE("expected: equality of two value-holding instances", "[expected_ref_both]") { - int x1 = 42, x2 = 42; + 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; + 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; + 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; + 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; + int x = 0, err = 0; expected a(x), b(unexpect, err); CHECK(!(a == b)); } @@ -361,26 +372,26 @@ TEST_CASE("expected: value vs error always unequal", "[expected_ref_both] // ============================================================================= TEST_CASE("expected: and_then works on value side", "[expected_ref_both]") { - int x = 5; + int x = 5; expected e(x); - auto r = e.and_then([](int& v) -> expected { return v * 2; }); + 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; + int err = 3; expected e(unexpect, err); - auto r = e.and_then([](int& v) -> expected { return v; }); + 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; + int err = 3; expected e(unexpect, err); - int result_val = 0; - auto r = e.or_else([&result_val](int& v) -> expected { + int result_val = 0; + auto r = e.or_else([&result_val](int& v) -> expected { result_val = v * 10; return result_val; }); @@ -389,44 +400,43 @@ TEST_CASE("expected: or_else receives E& and can inspect error", "[expect } TEST_CASE("expected: or_else propagates value ref", "[expected_ref_both]") { - int x = 42; + int x = 42; expected e(x); - int other_err = 0; - auto r = e.or_else([&other_err](int&) -> expected { - return expected(unexpect, other_err); - }); + 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; + int x = 5; expected e(x); - auto r = e.transform([](int& v) { return v * 2; }); + 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; + int err = 5; expected e(unexpect, err); - auto r = e.transform([](int& v) { return v * 2; }); + 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; + int err = 5; expected e(unexpect, err); - auto r = e.transform_error([](int& v) -> std::string { return std::to_string(v); }); + 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; + int x = 42; expected e(x); - auto r = e.transform_error([](int& v) -> std::string { return std::to_string(v); }); + auto r = e.transform_error([](int& v) -> std::string { return std::to_string(v); }); REQUIRE(r.has_value()); CHECK(&*r == &x); } @@ -436,17 +446,17 @@ TEST_CASE("expected: transform_error propagates value ref", "[expected_re // ============================================================================= TEST_CASE("expected: converting copy construct with value", "[expected_ref_both]") { - int x = 42; - expected src(x); - expected e(src); + 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); + int err = 7; + expected src(unexpect, err); + expected e(src); REQUIRE(!e.has_value()); CHECK(&e.error() == &err); } @@ -456,13 +466,13 @@ TEST_CASE("expected: converting copy construct with error", "[expected_re // ============================================================================= TEST_CASE("expected: lvalue value reference compiles and is addressable", "[expected_ref_both]") { - int x = 1; + 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; + int err = 1; expected e(unexpect, err); CHECK(&e.error() == &err); } 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..411f1aa --- /dev/null +++ b/tests/beman/expected/expected_ref_both_construct_from_unexpected_fail.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected cannot be constructed from unexpected. +// unexpected stores G by value; binding E& to it would create a dangling +// reference when the unexpected object is destroyed. +// 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_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..efd4471 --- /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_e.test.cpp b/tests/beman/expected/expected_ref_e.test.cpp index 8740ea9..000c12a 100644 --- a/tests/beman/expected/expected_ref_e.test.cpp +++ b/tests/beman/expected/expected_ref_e.test.cpp @@ -146,6 +146,17 @@ TEST_CASE("expected: assign to error state via expected copy", "[expected_ 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 // ============================================================================= 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_construct_from_unexpected_fail.cpp b/tests/beman/expected/expected_ref_e_construct_from_unexpected_fail.cpp new file mode 100644 index 0000000..c351d10 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_construct_from_unexpected_fail.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected cannot be constructed from unexpected. +// unexpected stores G by value; binding E& to it would create a dangling +// reference when the unexpected object is destroyed. +// EXPECT: "no matching function" +#include +using namespace beman::expected; +void test() { + expected e = unexpected(7); // must not compile +} From 2ee3e2e127b6ca8566a425594bf5149f494b378d Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 22:47:30 -0400 Subject: [PATCH 10/42] fix: apply reference_constructs_from_temporary_v to E& error constructors Steps 8 and 9 used the simpler fixed-function + deleted-rvalue-overload pattern for E& error-side constructors, while step 7 and step 9's T& value-side already used the principled template + reference_constructs_ from_temporary_v constraint. Both approaches are functionally equivalent (the rvalue-ref deleted overload wins over const-ref in overload resolution), but the template pattern is the P2988-recommended idiom, gives clearer "deleted function" diagnostics, and is consistent across T& and E& sides. Change: replace fixed (unexpect_t, E&) + deleted (unexpect_t, remove_ const_t&&) with template + requires(!reference_constructs_from_ temporary_v) / deleted template + requires(reference_constructs _from_temporary_v). Add static_asserts covering the cross-type temporary case: expected cannot be constructed from float (creates a temp double that E& would bind to). expected same check for step 9. 408 tests pass. --- include/beman/expected/expected.hpp | 30 ++++++++++++++----- .../beman/expected/expected_ref_both.test.cpp | 6 +++- ...ref_both_or_else_wrong_value_type_fail.cpp | 6 ++-- tests/beman/expected/expected_ref_e.test.cpp | 6 +++- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 4361f00..c9ac66e 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -2774,11 +2774,18 @@ class expected { std::construct_at(std::addressof(val_), il, std::forward(args)...); } - // Error constructor — binds E& from lvalue (prevents temporaries via deleted rvalue overload) - constexpr explicit expected(unexpect_t, E& err) noexcept : has_val_(false) { unex_ptr_ = std::addressof(err); } + // Error constructor — binds E& (no temporary allowed) + template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr explicit expected(unexpect_t, G&& err) noexcept : has_val_(false) { + E& r = std::forward(err); + unex_ptr_ = std::addressof(r); + } - // Deleted: prevent binding rvalue (temporary) to E& - constexpr expected(unexpect_t, std::remove_const_t&&) = delete; + // Deleted: prevent binding temporaries to E& + template + requires(detail::reference_constructs_from_temporary_v) + constexpr expected(unexpect_t, G&&) = delete; // Converting constructor from expected (copy) — mirrors expected's from expected template @@ -3399,11 +3406,18 @@ class expected { requires(detail::reference_constructs_from_temporary_v) constexpr expected(U&&) = delete; - // Error constructor — binds E& from lvalue (prevents temporaries via deleted rvalue overload) - constexpr explicit expected(unexpect_t, E& err) noexcept : has_val_(false) { unex_ = std::addressof(err); } + // Error constructor — binds E& (no temporary allowed) + template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr explicit expected(unexpect_t, G&& err) noexcept : has_val_(false) { + E& r = std::forward(err); + unex_ = std::addressof(r); + } - // Deleted: prevent binding rvalue (temporary) to E& - constexpr expected(unexpect_t, std::remove_const_t&&) = delete; + // Deleted: prevent binding temporaries to E& + template + requires(detail::reference_constructs_from_temporary_v) + constexpr expected(unexpect_t, G&&) = delete; // Converting constructor from expected (copy) template diff --git a/tests/beman/expected/expected_ref_both.test.cpp b/tests/beman/expected/expected_ref_both.test.cpp index 3f00fc6..93bdd5e 100644 --- a/tests/beman/expected/expected_ref_both.test.cpp +++ b/tests/beman/expected/expected_ref_both.test.cpp @@ -51,8 +51,12 @@ static_assert(std::is_same_v>() // Cannot construct from temporary value (T& rvalue deleted) static_assert(!std::is_constructible_v, int&&>); -// Cannot construct from temporary error (E& rvalue deleted) +// 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&>); 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 index efd4471..6ac1584 100644 --- 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 @@ -7,9 +7,9 @@ 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); + 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_e.test.cpp b/tests/beman/expected/expected_ref_e.test.cpp index 000c12a..2a7a18d 100644 --- a/tests/beman/expected/expected_ref_e.test.cpp +++ b/tests/beman/expected/expected_ref_e.test.cpp @@ -49,8 +49,12 @@ static_assert(!std::is_trivially_copy_constructible_v>); static_assert(!std::is_trivially_destructible_v>); -// Cannot construct from temporary error (rvalue deleted) +// 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&>); From 8fb347d2bcbb68d3ea2ea9dc3498e47f8703d191 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 2 Jun 2026 23:05:07 -0400 Subject: [PATCH 11/42] test: add negative tests covering all concept checks in reference specializations Systematic audit identified 18 concept checks without corresponding negative compile tests. All gaps are now covered: Type constraint mandates (static_asserts) for expected: T must not be an array type (expected_ref_e_t_array_fail) E must not be an array type (expected_ref_e_e_array_fail) T must not be in_place_t (expected_ref_e_t_inplace_fail) T must not be unexpect_t (expected_ref_e_t_unexpect_fail) T must not be unexpected (expected_ref_e_t_unexpected_fail) Type constraint mandates for expected: T must not be an array type (expected_ref_both_t_array_fail) E must not be an array type (expected_ref_both_e_array_fail) T must not be in_place_t (expected_ref_both_t_inplace_fail) T must not be unexpect_t (expected_ref_both_t_unexpect_fail) T must not be unexpected (expected_ref_both_t_unexpected_fail) no in_place_t value ctor (expected_ref_both_inplace_value_fail) Monadic operation mandates: expected and_then wrong error_type (expected_ref_and_then_wrong_error_type_fail) expected and_then wrong error_type (expected_ref_e_and_then_wrong_error_type_fail) expected and_then wrong error_type (expected_ref_both_and_then_wrong_error_type_fail) expected or_else wrong value_type (expected_ref_e_or_else_wrong_value_type_fail) expected transform_error ref G (expected_ref_transform_error_ref_result_fail) expected transform_error ref G (expected_ref_e_transform_error_ref_result_fail) expected transform_error ref G (expected_ref_both_transform_error_ref_result_fail) 426 tests pass. --- tests/beman/expected/CMakeLists.txt | 82 +++++++++++++++++++ ...ted_ref_and_then_wrong_error_type_fail.cpp | 13 +++ ...ef_both_and_then_wrong_error_type_fail.cpp | 10 +++ .../expected_ref_both_e_array_fail.cpp | 9 ++ .../expected_ref_both_inplace_value_fail.cpp | 9 ++ .../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 ++ ...f_both_transform_error_ref_result_fail.cpp | 10 +++ ...d_ref_e_and_then_wrong_error_type_fail.cpp | 10 +++ .../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 | 7 ++ .../expected_ref_e_t_inplace_fail.cpp | 8 ++ .../expected_ref_e_t_unexpect_fail.cpp | 7 ++ .../expected_ref_e_t_unexpected_fail.cpp | 7 ++ ..._ref_e_transform_error_ref_result_fail.cpp | 10 +++ ...ed_ref_transform_error_ref_result_fail.cpp | 10 +++ 19 files changed, 247 insertions(+) create mode 100644 tests/beman/expected/expected_ref_and_then_wrong_error_type_fail.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_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_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_transform_error_ref_result_fail.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_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_transform_error_ref_result_fail.cpp create mode 100644 tests/beman/expected/expected_ref_transform_error_ref_result_fail.cpp diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 580081d..60fe78d 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -194,6 +194,88 @@ 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 matching function" +) + +# 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" +) + # ============================================================================= # Hardened precondition tests (compiled with -DBEMAN_EXPECTED_HARDENED) # ============================================================================= 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..b4573f1 --- /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_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..23fd4c3 --- /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_e_array_fail.cpp b/tests/beman/expected/expected_ref_both_e_array_fail.cpp new file mode 100644 index 0000000..451cb2a --- /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..2344a9c --- /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_t_array_fail.cpp b/tests/beman/expected/expected_ref_both_t_array_fail.cpp new file mode 100644 index 0000000..b7e9108 --- /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..a5173c0 --- /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..7906dbf --- /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..55d9931 --- /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_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..f489ad6 --- /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_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..4ef4933 --- /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_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..2d333a0 --- /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..8371600 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_t_array_fail.cpp @@ -0,0 +1,7 @@ +// 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..093402f --- /dev/null +++ b/tests/beman/expected/expected_ref_e_t_inplace_fail.cpp @@ -0,0 +1,8 @@ +// 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..3520857 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_t_unexpect_fail.cpp @@ -0,0 +1,7 @@ +// 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..f27e669 --- /dev/null +++ b/tests/beman/expected/expected_ref_e_t_unexpected_fail.cpp @@ -0,0 +1,7 @@ +// 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_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..6a601b4 --- /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/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; }); +} From 09653eaccddf2c614e0e306c7bafab7f17049739 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Wed, 3 Jun 2026 00:38:45 -0400 Subject: [PATCH 12/42] feat: implement expected void+error-reference specialization (Step 10) Adds expected as a partial specialization (template), the final reference specialization combining void value semantics with P2988 reference-error semantics. Storage is E* + bool; trivially copyable/destructible. Error is a non-owning reference with rebind semantics on assignment. Removes the now-obsolete expected_void_ref_fail negative compile test (expected is now valid). 470 tests pass. --- include/beman/expected/expected.hpp | 427 ++++++++++++++++++ tests/beman/expected/CMakeLists.txt | 20 +- ...ted_ref_and_then_wrong_error_type_fail.cpp | 4 +- ...ef_both_and_then_wrong_error_type_fail.cpp | 4 +- .../expected_ref_both_e_array_fail.cpp | 4 +- .../expected_ref_both_inplace_value_fail.cpp | 2 +- .../expected_ref_both_t_array_fail.cpp | 4 +- .../expected_ref_both_t_inplace_fail.cpp | 4 +- .../expected_ref_both_t_unexpect_fail.cpp | 4 +- .../expected_ref_both_t_unexpected_fail.cpp | 4 +- ...f_both_transform_error_ref_result_fail.cpp | 4 +- ...d_ref_e_and_then_wrong_error_type_fail.cpp | 4 +- ...ed_ref_e_or_else_wrong_value_type_fail.cpp | 4 +- .../expected/expected_ref_e_t_array_fail.cpp | 4 +- .../expected_ref_e_t_inplace_fail.cpp | 4 +- .../expected_ref_e_t_unexpect_fail.cpp | 4 +- .../expected_ref_e_t_unexpected_fail.cpp | 4 +- ..._ref_e_transform_error_ref_result_fail.cpp | 4 +- .../expected/expected_void_ref_e.test.cpp | 414 +++++++++++++++++ .../expected_void_ref_e_const_lvalue_fail.cpp | 7 + .../expected_void_ref_e_no_value_or_fail.cpp | 7 + .../expected_void_ref_e_temporary_fail.cpp | 6 + 22 files changed, 905 insertions(+), 38 deletions(-) create mode 100644 tests/beman/expected/expected_void_ref_e.test.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_no_value_or_fail.cpp create mode 100644 tests/beman/expected/expected_void_ref_e_temporary_fail.cpp diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index c9ac66e..4949e8c 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -3865,6 +3865,433 @@ class expected { }; }; +// ============================================================================= +// Partial specialization: expected — void value + reference error type (P2988) +// ============================================================================= + +template +class expected { + static_assert(std::is_object_v, "E must be an object type in expected"); + static_assert(!std::is_array_v, "E must not be an array type in expected"); + + public: + using value_type = void; + using error_type = E&; + using unexpected_type = unexpected>; + + template + using rebind = expected; + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + // Default constructor — void/success state + constexpr expected() noexcept : unex_ptr_(nullptr), has_val_(true) {} + + // Copy/move — trivial (just pointer + bool) + constexpr expected(const expected&) = default; + constexpr expected(expected&&) noexcept = default; + + // In-place value constructor + constexpr explicit expected(std::in_place_t) noexcept : unex_ptr_(nullptr), has_val_(true) {} + + // Error constructor from unexpected const& — E& binds to G's stored value via its lvalue accessor + template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) noexcept : has_val_(false) { + E& r = const_cast(e.error()); + unex_ptr_ = std::addressof(r); + } + + // Error constructor from unexpected&& (non-const lvalue accessor gives G&, binds to E&) + template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) noexcept : has_val_(false) { + E& r = e.error(); + unex_ptr_ = std::addressof(r); + } + + // In-place error constructor — binds E& directly (no temporary allowed) + template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr explicit expected(unexpect_t, G&& err) noexcept : has_val_(false) { + E& r = std::forward(err); + unex_ptr_ = std::addressof(r); + } + + // Deleted: prevent binding temporaries to E& + template + requires(detail::reference_constructs_from_temporary_v) + constexpr expected(unexpect_t, G&&) = delete; + + // Converting constructor from expected + template + requires std::is_convertible_v + constexpr explicit(!std::is_convertible_v) expected(const expected& rhs) + : has_val_(rhs.has_value()) { + if (!has_val_) { + E& r = rhs.error(); + unex_ptr_ = std::addressof(r); + } + } + + // ------------------------------------------------------------------------- + // Destructor — trivial (pointer + bool only) + // ------------------------------------------------------------------------- + + constexpr ~expected() = default; + + // ------------------------------------------------------------------------- + // Assignment + // ------------------------------------------------------------------------- + + // Copy/move — trivial + constexpr expected& operator=(const expected&) = default; + constexpr expected& operator=(expected&&) noexcept = default; + + // emplace — transition to void success state (always noexcept) + constexpr void emplace() noexcept { has_val_ = true; } + + // ------------------------------------------------------------------------- + // Swap + // ------------------------------------------------------------------------- + + constexpr void swap(expected& rhs) noexcept { + if (has_val_ && rhs.has_val_) { + // both success: nothing to do + } else if (!has_val_ && !rhs.has_val_) { + std::swap(unex_ptr_, rhs.unex_ptr_); + } else if (has_val_) { + unex_ptr_ = rhs.unex_ptr_; + rhs.unex_ptr_ = nullptr; + has_val_ = false; + rhs.has_val_ = true; + } else { + rhs.swap(*this); + } + } + + friend constexpr void swap(expected& x, expected& y) noexcept { x.swap(y); } + + // ------------------------------------------------------------------------- + // Observers + // ------------------------------------------------------------------------- + + constexpr explicit operator bool() const noexcept { return has_val_; } + constexpr bool has_value() const noexcept { return has_val_; } + + constexpr void operator*() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + } + + constexpr void value() const& { + static_assert(std::is_copy_constructible_v>, + "value() requires E to be copy constructible"); + if (!has_val_) + throw bad_expected_access>(*unex_ptr_); + } + + constexpr void 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>(*unex_ptr_); + } + + // error() — shallow const: always returns E& regardless of const on expected + constexpr E& error() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return *unex_ptr_; + } + + template > + requires(std::is_copy_constructible_v> && std::is_convertible_v>) + constexpr std::remove_cv_t error_or(G&& def) const { + if (!has_val_) + return *unex_ptr_; + return static_cast>(std::forward(def)); + } + + // ------------------------------------------------------------------------- + // Monadic operations — void value + E& error + // ------------------------------------------------------------------------- + + // and_then: F called with no args (void value); error propagates as E& + template + constexpr auto 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)); + return U(unexpect, *unex_ptr_); + } + + template + constexpr auto 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)); + return U(unexpect, *unex_ptr_); + } + + template + constexpr auto 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)); + return U(unexpect, *unex_ptr_); + } + + template + constexpr auto 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)); + return U(unexpect, *unex_ptr_); + } + + // or_else: F receives E& (the referenced error); value propagates as void success + template + constexpr auto 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_void_v, "or_else: F must return expected with void value_type"); + if (has_val_) + return G(); + return std::invoke(std::forward(f), *unex_ptr_); + } + + template + constexpr auto 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_void_v, "or_else: F must return expected with void value_type"); + if (has_val_) + return G(); + return std::invoke(std::forward(f), *unex_ptr_); + } + + template + constexpr auto 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_void_v, "or_else: F must return expected with void value_type"); + if (has_val_) + return G(); + return std::invoke(std::forward(f), *unex_ptr_); + } + + template + constexpr auto 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_void_v, "or_else: F must return expected with void value_type"); + if (has_val_) + return G(); + return std::invoke(std::forward(f), *unex_ptr_); + } + + // transform: F called with no args; error propagates as E& + template + constexpr auto 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)); + if (has_val_) + return expected(); + return expected(unexpect, *unex_ptr_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f))); + return expected(unexpect, *unex_ptr_); + } + } + + template + constexpr auto 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)); + if (has_val_) + return expected(); + return expected(unexpect, *unex_ptr_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f))); + return expected(unexpect, *unex_ptr_); + } + } + + template + constexpr auto 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)); + if (has_val_) + return expected(); + return expected(unexpect, *unex_ptr_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f))); + return expected(unexpect, *unex_ptr_); + } + } + + template + constexpr auto 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)); + if (has_val_) + return expected(); + return expected(unexpect, *unex_ptr_); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f))); + return expected(unexpect, *unex_ptr_); + } + } + + // transform_error: F receives E&; value propagates as void success + template + constexpr auto 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(); + return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + } + + template + constexpr auto 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(); + return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + } + + template + constexpr auto 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(); + return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + } + + template + constexpr auto 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(); + return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + } + + // ------------------------------------------------------------------------- + // 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 true; + return x.error() == y.error(); + } + + template + friend constexpr bool operator==(const expected& x, const unexpected& e) { + return !x.has_value() && static_cast(x.error() == e.error()); + } + + private: + E* unex_ptr_; + bool has_val_; +}; + } // namespace expected } // namespace beman diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 60fe78d..645a1c2 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -18,6 +18,7 @@ target_sources( expected_ref_constraints.test.cpp expected_ref_e.test.cpp expected_ref_both.test.cpp + expected_void_ref_e.test.cpp todo.test.cpp ) target_link_libraries( @@ -78,9 +79,8 @@ 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 - "T must not be void" -) +# 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" ) @@ -276,6 +276,20 @@ add_fail_test(expected_ref_both_transform_error_ref_result_fail "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 + "no matching function|call to deleted" +) +add_fail_test(expected_void_ref_e_const_lvalue_fail + expected_void_ref_e_const_lvalue_fail.cpp + "no matching function|call to deleted|cannot bind" +) +add_fail_test(expected_void_ref_e_no_value_or_fail + expected_void_ref_e_no_value_or_fail.cpp + "no member named" +) + # ============================================================================= # Hardened precondition tests (compiled with -DBEMAN_EXPECTED_HARDENED) # ============================================================================= 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 index b4573f1..1a708bf 100644 --- 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 @@ -4,8 +4,8 @@ #include #include void test() { - int x = 0; - beman::expected::expected e(x); + 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_and_then_wrong_error_type_fail.cpp b/tests/beman/expected/expected_ref_both_and_then_wrong_error_type_fail.cpp index 23fd4c3..672b8df 100644 --- 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 @@ -3,8 +3,8 @@ // EXPECT: "F must return expected with the same error_type" #include void test() { - int x = 0; - beman::expected::expected e(x); + 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_e_array_fail.cpp b/tests/beman/expected/expected_ref_both_e_array_fail.cpp index 451cb2a..60275ef 100644 --- a/tests/beman/expected/expected_ref_both_e_array_fail.cpp +++ b/tests/beman/expected/expected_ref_both_e_array_fail.cpp @@ -3,7 +3,7 @@ // EXPECT: "E must not be an array type" #include void test() { - using arr = int[3]; - int x = 0; + 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 index 2344a9c..9a5f69f 100644 --- a/tests/beman/expected/expected_ref_both_inplace_value_fail.cpp +++ b/tests/beman/expected/expected_ref_both_inplace_value_fail.cpp @@ -4,6 +4,6 @@ #include #include void test() { - int err = 0; + int err = 0; beman::expected::expected e(std::in_place, 42); } diff --git a/tests/beman/expected/expected_ref_both_t_array_fail.cpp b/tests/beman/expected/expected_ref_both_t_array_fail.cpp index b7e9108..2a2a303 100644 --- a/tests/beman/expected/expected_ref_both_t_array_fail.cpp +++ b/tests/beman/expected/expected_ref_both_t_array_fail.cpp @@ -3,7 +3,7 @@ // EXPECT: "T must not be an array type" #include void test() { - using arr = int[3]; - int err = 0; + 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 index a5173c0..1de2d0f 100644 --- a/tests/beman/expected/expected_ref_both_t_inplace_fail.cpp +++ b/tests/beman/expected/expected_ref_both_t_inplace_fail.cpp @@ -4,7 +4,7 @@ #include #include void test() { - std::in_place_t ip{}; - int err = 0; + 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 index 7906dbf..ee424ab 100644 --- a/tests/beman/expected/expected_ref_both_t_unexpect_fail.cpp +++ b/tests/beman/expected/expected_ref_both_t_unexpect_fail.cpp @@ -3,7 +3,7 @@ // EXPECT: "T must not be unexpect_t" #include void test() { - beman::expected::unexpect_t u{}; - int err = 0; + 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 index 55d9931..6c2655a 100644 --- a/tests/beman/expected/expected_ref_both_t_unexpected_fail.cpp +++ b/tests/beman/expected/expected_ref_both_t_unexpected_fail.cpp @@ -3,7 +3,7 @@ // EXPECT: "T must not be a specialization of unexpected" #include void test() { - beman::expected::unexpected u(1); - int err = 0; + beman::expected::unexpected u(1); + int err = 0; beman::expected::expected&, int&> e(u); } 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 index f489ad6..d283262 100644 --- 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 @@ -3,8 +3,8 @@ // EXPECT: "G must be an object type" #include void test() { - int err = 0; - beman::expected::expected e(beman::expected::unexpect, err); + 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_e_and_then_wrong_error_type_fail.cpp b/tests/beman/expected/expected_ref_e_and_then_wrong_error_type_fail.cpp index 4ef4933..6e9849f 100644 --- 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 @@ -3,8 +3,8 @@ // EXPECT: "F must return expected with the same error_type" #include void test() { - int err = 0; - beman::expected::expected e(42); + 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_or_else_wrong_value_type_fail.cpp b/tests/beman/expected/expected_ref_e_or_else_wrong_value_type_fail.cpp index 2d333a0..91117a8 100644 --- 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 @@ -3,8 +3,8 @@ // EXPECT: "F must return expected with the same value_type" #include void test() { - int err = 0; - beman::expected::expected e(beman::expected::unexpect, err); + 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 index 8371600..67546bd 100644 --- a/tests/beman/expected/expected_ref_e_t_array_fail.cpp +++ b/tests/beman/expected/expected_ref_e_t_array_fail.cpp @@ -2,6 +2,4 @@ // 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{}; -} +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 index 093402f..b352c3a 100644 --- a/tests/beman/expected/expected_ref_e_t_inplace_fail.cpp +++ b/tests/beman/expected/expected_ref_e_t_inplace_fail.cpp @@ -3,6 +3,4 @@ // EXPECT: "T must not be in_place_t" #include #include -void test() { - beman::expected::expected e{}; -} +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 index 3520857..335f509 100644 --- a/tests/beman/expected/expected_ref_e_t_unexpect_fail.cpp +++ b/tests/beman/expected/expected_ref_e_t_unexpect_fail.cpp @@ -2,6 +2,4 @@ // NEGATIVE: T must not be unexpect_t in expected // EXPECT: "T must not be unexpect_t" #include -void test() { - beman::expected::expected e{}; -} +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 index f27e669..21f6bd8 100644 --- a/tests/beman/expected/expected_ref_e_t_unexpected_fail.cpp +++ b/tests/beman/expected/expected_ref_e_t_unexpected_fail.cpp @@ -2,6 +2,4 @@ // 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{}; -} +void test() { beman::expected::expected, int&> e{}; } 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 index 6a601b4..deba573 100644 --- 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 @@ -3,8 +3,8 @@ // EXPECT: "G must be an object type" #include void test() { - int err = 0; - beman::expected::expected e(beman::expected::unexpect, err); + 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_void_ref_e.test.cpp b/tests/beman/expected/expected_void_ref_e.test.cpp new file mode 100644 index 0000000..85b63ea --- /dev/null +++ b/tests/beman/expected/expected_void_ref_e.test.cpp @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include +#include + +#include + +#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); +} + +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>); +} 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..2ea31a3 --- /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_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..246cba6 --- /dev/null +++ b/tests/beman/expected/expected_void_ref_e_temporary_fail.cpp @@ -0,0 +1,6 @@ +// 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); +} From 4035cebf4e244781915b6fae2549b399633e19a0 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Wed, 3 Jun 2026 13:11:37 -0400 Subject: [PATCH 13/42] docs: mark Step 10 complete, write final handoff --- docs/plan/handoff.md | 112 ++++++++++++++++++++----------------------- docs/plan/index.md | 2 +- 2 files changed, 53 insertions(+), 61 deletions(-) diff --git a/docs/plan/handoff.md b/docs/plan/handoff.md index eaff0f4..3687cb3 100644 --- a/docs/plan/handoff.md +++ b/docs/plan/handoff.md @@ -13,43 +13,44 @@ the integration branch for this work. ## Current State -Steps 1–9 are complete. Steps 1–8 are merged into `expected-over-references`. -Step 9 is on branch `step9-expected-ref-both`, ready to merge. +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), and `expected` (P2988 both-reference specialization). -401 tests pass. +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` (with monadic ops for all) -- `tests/beman/expected/` — comprehensive test suite (401 tests) - -### Step 9 Design Notes - -- `expected` stores both sides as pointers: `T* val_`, `E* unex_` - in a union with `bool has_val_` -- **Fully trivial**: both union members are pointers (same size, trivially - copyable), so copy, move, destructor are all `= default` -- **No default constructor**: deleted (T& cannot be default-initialized) -- **Shallow const on both sides**: `operator*()`, `error()` return the - underlying reference regardless of const on the `expected` object -- **Value rebind**: `operator=(U&&)` rebinds the T* pointer; no destructor call -- **Error rebind**: done via copy/move assignment of another `expected` - (assigning from `unexpected` was not added — `unexpected` stores by value - so its `error()` returns `const G&` which can't bind to non-const `E&`) -- The `expected_ref_e_ref_fail.cpp` negative compile test was updated: it now - tests `expected` (rvalue reference as E, still invalid in - `expected`) instead of the previously-tested `expected` - which is now valid via the `expected` specialization -- `transform_error(F)` returns `expected` — the step-7 specialization - which accepts T& in its constructor -- `transform(F)` returns `expected` — step-8 specialization; - void-return case returns `expected` (step 10's specialization, - not yet implemented but won't fail until actually instantiated with void F) + `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 @@ -64,8 +65,8 @@ specialization), and `expected` (P2988 both-reference specialization). - 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 Catch2, then std @@ -74,39 +75,30 @@ specialization), and `expected` (P2988 both-reference specialization). 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 10: `expected` void+reference-error specialization — see -`docs/plan/step10-expected-void-ref-e.md`. -Create branch `step10-expected-void-ref-e` from `expected-over-references` -(after merging `step9-expected-ref-both` into `expected-over-references`). +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 338a167..80dc8eb 100644 --- a/docs/plan/index.md +++ b/docs/plan/index.md @@ -89,4 +89,4 @@ Steps 7-10 are the reference specializations (the novel work in this proposal). - [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 -- [ ] Step 10: `expected` — no value storage, error pointer, rebind error, observers, monadic ops +- [x] Step 10: `expected` — no value storage, error pointer, rebind error, observers, monadic ops From 34ed4bcb564ac5ca880b1731b4d4ddf8e52eccfc Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Wed, 3 Jun 2026 13:44:38 -0400 Subject: [PATCH 14/42] WIP: add = delete("message") diagnostics to all deleted declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Partial implementation — deleted overloads added to expected, expected, expected, expected. CMakeLists regexes not yet updated to match the new messages. 8 fail tests need regex updates. Messages added: - "no default constructor; T& cannot be null" - "no in_place value constructor; use expected(lvalue_ref) to bind T&" - "binding a temporary to T& creates a dangling reference" - "argument type cannot bind to non-const E&; provide a mutable lvalue" - "no constructor from unexpected; use (unexpect, lvalue_ref)" - "no assignment from unexpected; copy-assign from another expected" - "no value_or for void specialization" --- include/beman/expected/expected.hpp | 95 +++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 12 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 4949e8c..692401d 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -2028,7 +2028,7 @@ class expected { // Constructors // ------------------------------------------------------------------------- - expected() = delete; + expected() = delete("expected: no default constructor; T& cannot be null"); // Copy constructor (trivial path) constexpr expected(const expected&) @@ -2060,6 +2060,11 @@ class expected { std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); } + // Deleted: no in-place value constructor — T& cannot be constructed in-place + template + constexpr expected(std::in_place_t, Args&&...) = + delete("expected: no in_place value constructor; use expected(lvalue_ref) to bind T&"); + // Value constructor — takes U that can bind to T& template requires(!std::is_same_v, std::in_place_t> && @@ -2071,10 +2076,11 @@ class expected { val_ = std::addressof(r); } - // Deleted constructor — prevent binding temporaries + // Deleted: binding a temporary to T& creates a dangling reference template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(U&&) = delete; + constexpr expected(U&&) = + delete("expected: binding a temporary to T& creates a dangling reference"); // Converting constructor from expected (copy) template @@ -2782,10 +2788,25 @@ class expected { unex_ptr_ = std::addressof(r); } - // Deleted: prevent binding temporaries to E& + // Deleted: argument cannot bind to E& (covers rvalue, const lvalue, and temp-creating cases) template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = delete; + constexpr expected(unexpect_t, G&&) = + delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + + template + requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr expected(unexpect_t, G&&) = + delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + + // Deleted: no constructor from unexpected (would bind E& to temporary storage in unexpected) + template + constexpr expected(const unexpected&) = + delete("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); + + template + constexpr expected(unexpected&&) = + delete("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); // Converting constructor from expected (copy) — mirrors expected's from expected template @@ -2904,6 +2925,15 @@ class expected { return *this; } + // Deleted: no assignment from unexpected (would rebind E& to temporary storage) + template + constexpr expected& operator=(const unexpected&) = + delete("expected: no assignment from unexpected; copy-assign from another expected"); + + template + constexpr expected& operator=(unexpected&&) = + delete("expected: no assignment from unexpected; copy-assign from another expected"); + // emplace — construct T in-place (nothrow required for exception safety when T is destroyed) template requires std::is_nothrow_constructible_v @@ -3385,12 +3415,17 @@ class expected { // Constructors // ------------------------------------------------------------------------- - expected() = delete; + expected() = delete("expected: no default constructor; T& cannot be null"); // Copy/move constructors — trivial (union holds only pointers + bool has_val_) constexpr expected(const expected&) = default; constexpr expected(expected&&) = default; + // Deleted: no in-place value constructor — T& cannot be constructed in-place + template + constexpr expected(std::in_place_t, Args&&...) = + delete("expected: no in_place value constructor; use expected(lvalue_ref) to bind T&"); + // Value constructor — binds T& from lvalue (dangling prevention via deleted rvalue overload) template requires(!std::is_same_v, expected> && @@ -3401,10 +3436,11 @@ class expected { val_ = std::addressof(r); } - // Deleted: prevent binding rvalue (temporary) to T& + // Deleted: binding a temporary to T& creates a dangling reference template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(U&&) = delete; + constexpr expected(U&&) = + delete("expected: binding a temporary to T& creates a dangling reference"); // Error constructor — binds E& (no temporary allowed) template @@ -3414,10 +3450,25 @@ class expected { unex_ = std::addressof(r); } - // Deleted: prevent binding temporaries to E& + // Deleted: argument cannot bind to E& (covers rvalue, const lvalue, and temp-creating cases) template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = delete; + constexpr expected(unexpect_t, G&&) = + delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + + template + requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr expected(unexpect_t, G&&) = + delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + + // Deleted: no constructor from unexpected (would bind E& to temporary storage in unexpected) + template + constexpr expected(const unexpected&) = + delete("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); + + template + constexpr expected(unexpected&&) = + delete("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); // Converting constructor from expected (copy) template @@ -3477,6 +3528,15 @@ class expected { return *this; } + // Deleted: no assignment from unexpected + template + constexpr expected& operator=(const unexpected&) = + delete("expected: no assignment from unexpected; copy-assign from another expected"); + + template + constexpr expected& operator=(unexpected&&) = + delete("expected: no assignment from unexpected; copy-assign from another expected"); + // emplace — rebind T& (pointer transition is trivial) template requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) @@ -3920,10 +3980,16 @@ class expected { unex_ptr_ = std::addressof(r); } - // Deleted: prevent binding temporaries to E& + // Deleted: argument cannot bind to E& (covers rvalue, const lvalue, and temp-creating cases) template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = delete; + constexpr expected(unexpect_t, G&&) = + delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + + template + requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr expected(unexpect_t, G&&) = + delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); // Converting constructor from expected template @@ -4021,6 +4087,11 @@ class expected { } // ------------------------------------------------------------------------- + // Deleted: value_or is not available for void expected + template + constexpr void value_or(U&&) const = + delete("expected: no value_or for void specialization"); + // Monadic operations — void value + E& error // ------------------------------------------------------------------------- From e61d877ad9b9b5d9183de880a3b969b1728f0948 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 5 Jun 2026 17:58:56 -0400 Subject: [PATCH 15/42] fix: update fail test regexes to match = delete("message") diagnostics Update PASS_REGULAR_EXPRESSION patterns in CMakeLists.txt to use the specific diagnostic strings from = delete("message") declarations instead of generic patterns like "delete" or "no matching function". Run clang-format to fix formatting. All 470 tests pass (416 positive + 54 negative). --- include/beman/expected/expected.hpp | 51 +++++++------- tests/beman/expected/CMakeLists.txt | 36 +++++----- .../expected/expected_void_ref_e.test.cpp | 69 +++++++++---------- .../expected_void_ref_e_const_lvalue_fail.cpp | 2 +- .../expected_void_ref_e_temporary_fail.cpp | 4 +- 5 files changed, 77 insertions(+), 85 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 692401d..24f8284 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -2028,7 +2028,7 @@ class expected { // Constructors // ------------------------------------------------------------------------- - expected() = delete("expected: no default constructor; T& cannot be null"); + expected() = delete ("expected: no default constructor; T& cannot be null"); // Copy constructor (trivial path) constexpr expected(const expected&) @@ -2063,7 +2063,7 @@ class expected { // Deleted: no in-place value constructor — T& cannot be constructed in-place template constexpr expected(std::in_place_t, Args&&...) = - delete("expected: no in_place value constructor; use expected(lvalue_ref) to bind T&"); + delete ("expected: no in_place value constructor; use expected(lvalue_ref) to bind T&"); // Value constructor — takes U that can bind to T& template @@ -2079,8 +2079,7 @@ class expected { // Deleted: binding a temporary to T& creates a dangling reference template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(U&&) = - delete("expected: binding a temporary to T& creates a dangling reference"); + constexpr expected(U&&) = delete ("expected: binding a temporary to T& creates a dangling reference"); // Converting constructor from expected (copy) template @@ -2792,21 +2791,21 @@ class expected { template requires(detail::reference_constructs_from_temporary_v) constexpr expected(unexpect_t, G&&) = - delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); template - requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) constexpr expected(unexpect_t, G&&) = - delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); // Deleted: no constructor from unexpected (would bind E& to temporary storage in unexpected) template constexpr expected(const unexpected&) = - delete("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); + delete ("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); template constexpr expected(unexpected&&) = - delete("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); + delete ("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); // Converting constructor from expected (copy) — mirrors expected's from expected template @@ -2928,11 +2927,11 @@ class expected { // Deleted: no assignment from unexpected (would rebind E& to temporary storage) template constexpr expected& operator=(const unexpected&) = - delete("expected: no assignment from unexpected; copy-assign from another expected"); + delete ("expected: no assignment from unexpected; copy-assign from another expected"); template constexpr expected& operator=(unexpected&&) = - delete("expected: no assignment from unexpected; copy-assign from another expected"); + delete ("expected: no assignment from unexpected; copy-assign from another expected"); // emplace — construct T in-place (nothrow required for exception safety when T is destroyed) template @@ -3415,7 +3414,7 @@ class expected { // Constructors // ------------------------------------------------------------------------- - expected() = delete("expected: no default constructor; T& cannot be null"); + expected() = delete ("expected: no default constructor; T& cannot be null"); // Copy/move constructors — trivial (union holds only pointers + bool has_val_) constexpr expected(const expected&) = default; @@ -3424,7 +3423,7 @@ class expected { // Deleted: no in-place value constructor — T& cannot be constructed in-place template constexpr expected(std::in_place_t, Args&&...) = - delete("expected: no in_place value constructor; use expected(lvalue_ref) to bind T&"); + delete ("expected: no in_place value constructor; use expected(lvalue_ref) to bind T&"); // Value constructor — binds T& from lvalue (dangling prevention via deleted rvalue overload) template @@ -3439,8 +3438,7 @@ class expected { // Deleted: binding a temporary to T& creates a dangling reference template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(U&&) = - delete("expected: binding a temporary to T& creates a dangling reference"); + constexpr expected(U&&) = delete ("expected: binding a temporary to T& creates a dangling reference"); // Error constructor — binds E& (no temporary allowed) template @@ -3454,21 +3452,21 @@ class expected { template requires(detail::reference_constructs_from_temporary_v) constexpr expected(unexpect_t, G&&) = - delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); template - requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) constexpr expected(unexpect_t, G&&) = - delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); // Deleted: no constructor from unexpected (would bind E& to temporary storage in unexpected) template constexpr expected(const unexpected&) = - delete("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); + delete ("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); template constexpr expected(unexpected&&) = - delete("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); + delete ("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); // Converting constructor from expected (copy) template @@ -3531,11 +3529,11 @@ class expected { // Deleted: no assignment from unexpected template constexpr expected& operator=(const unexpected&) = - delete("expected: no assignment from unexpected; copy-assign from another expected"); + delete ("expected: no assignment from unexpected; copy-assign from another expected"); template constexpr expected& operator=(unexpected&&) = - delete("expected: no assignment from unexpected; copy-assign from another expected"); + delete ("expected: no assignment from unexpected; copy-assign from another expected"); // emplace — rebind T& (pointer transition is trivial) template @@ -3984,12 +3982,12 @@ class expected { template requires(detail::reference_constructs_from_temporary_v) constexpr expected(unexpect_t, G&&) = - delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); template - requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) constexpr expected(unexpect_t, G&&) = - delete("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); // Converting constructor from expected template @@ -4089,8 +4087,7 @@ class expected { // ------------------------------------------------------------------------- // Deleted: value_or is not available for void expected template - constexpr void value_or(U&&) const = - delete("expected: no value_or for void specialization"); + constexpr void value_or(U&&) const = delete ("expected: no value_or for void specialization"); // Monadic operations — void value + E& error // ------------------------------------------------------------------------- diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 645a1c2..ee7c7a8 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -66,7 +66,7 @@ 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 - "delete" + "no default constructor" ) # Note: expected_e_ref_fail removed — expected is now valid via expected add_fail_test(expected_t_array_fail expected_t_array_fail.cpp @@ -119,32 +119,32 @@ add_fail_test(expected_unexpected_value_type_fail expected_unexpected_value_type # Step 7 — expected reference specialization: constructor constraints add_fail_test(expected_ref_temporary_fail expected_ref_temporary_fail.cpp - "no matching function|delete" + "binding a temporary to T& creates a dangling reference|no matching function" ) add_fail_test(expected_ref_no_default_fail expected_ref_no_default_fail.cpp - "delete" + "no default constructor" ) add_fail_test(expected_ref_inplace_value_fail expected_ref_inplace_value_fail.cpp - "no matching function" + "no in_place value constructor" ) # Step 8 — expected reference error: dangling prevention add_fail_test(expected_ref_e_temporary_error_fail expected_ref_e_temporary_error_fail.cpp - "delete|no matching function" + "cannot bind to non-const E&" ) add_fail_test(expected_ref_e_const_lvalue_fail expected_ref_e_const_lvalue_fail.cpp - "no matching function|cannot bind" + "cannot bind to non-const E&" ) # 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 - "conversion from|no matching function" + "no constructor from unexpected" ) add_fail_test(expected_ref_e_assign_unexpected_fail expected_ref_e_assign_unexpected_fail.cpp - "no match for|no matching function" + "no assignment from unexpected" ) # Step 7/8 parity — expected or_else value_type mandate (added in Step 8 review) @@ -154,24 +154,24 @@ add_fail_test(expected_ref_or_else_wrong_value_type_fail expected_ref_or_else_wr # 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 - "delete|no matching function" + "binding a temporary to T& creates a dangling reference|no matching function" ) add_fail_test(expected_ref_both_temporary_err_fail expected_ref_both_temporary_err_fail.cpp - "delete|no matching function" + "cannot bind to non-const E&" ) add_fail_test(expected_ref_both_no_default_fail expected_ref_both_no_default_fail.cpp - "delete|no matching function" + "no default constructor" ) # 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 - "conversion from|no matching function" + "no constructor from unexpected" ) add_fail_test(expected_ref_both_assign_unexpected_fail expected_ref_both_assign_unexpected_fail.cpp - "no match for|no matching function" + "no assignment from unexpected" ) # Step 9 — expected: or_else value_type mandate @@ -182,7 +182,7 @@ add_fail_test(expected_ref_both_or_else_wrong_value_type_fail # Step 7 — expected Mandates: E constraints add_fail_test(expected_ref_e_ref_fail expected_ref_e_ref_fail.cpp - "ambiguous|E must not be a reference" + "E must not be a reference" ) add_fail_test(expected_ref_e_void_fail expected_ref_e_void_fail.cpp "E must not be void" @@ -263,7 +263,7 @@ add_fail_test(expected_ref_both_t_unexpected_fail ) add_fail_test(expected_ref_both_inplace_value_fail expected_ref_both_inplace_value_fail.cpp - "no matching function" + "no in_place value constructor" ) # Step 9 — expected monadic mandates @@ -279,15 +279,15 @@ add_fail_test(expected_ref_both_transform_error_ref_result_fail # 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 - "no matching function|call to deleted" + "cannot bind to non-const E&" ) add_fail_test(expected_void_ref_e_const_lvalue_fail expected_void_ref_e_const_lvalue_fail.cpp - "no matching function|call to deleted|cannot bind" + "cannot bind to non-const E&" ) add_fail_test(expected_void_ref_e_no_value_or_fail expected_void_ref_e_no_value_or_fail.cpp - "no member named" + "no value_or for void" ) # ============================================================================= diff --git a/tests/beman/expected/expected_void_ref_e.test.cpp b/tests/beman/expected/expected_void_ref_e.test.cpp index 85b63ea..1e72bb6 100644 --- a/tests/beman/expected/expected_void_ref_e.test.cpp +++ b/tests/beman/expected/expected_void_ref_e.test.cpp @@ -39,7 +39,7 @@ TEST_CASE("expected: default construct has value", "[expected_void_ref_ } TEST_CASE("expected: construct from unexpect+ref binds E&", "[expected_void_ref_e]") { - int err = 42; + int err = 42; expected e(unexpect, err); REQUIRE(!e.has_value()); CHECK(&e.error() == &err); @@ -59,7 +59,7 @@ TEST_CASE("expected: copy construct from value state", "[expected_void_ } TEST_CASE("expected: copy construct from error state", "[expected_void_ref_e]") { - int err = 5; + int err = 5; expected a(unexpect, err); expected b = a; REQUIRE(!b.has_value()); @@ -67,7 +67,7 @@ TEST_CASE("expected: copy construct from error state", "[expected_void_ } TEST_CASE("expected: move construct copies pointer", "[expected_void_ref_e]") { - int err = 3; + int err = 3; expected a(unexpect, err); expected b = std::move(a); REQUIRE(!b.has_value()); @@ -75,7 +75,7 @@ TEST_CASE("expected: move construct copies pointer", "[expected_void_re } TEST_CASE("expected: convert from expected", "[expected_void_ref_e]") { - int err = 7; + int err = 7; expected src(unexpect, err); expected dst(src); REQUIRE(!dst.has_value()); @@ -86,29 +86,27 @@ TEST_CASE("expected: convert from expected", "[expected_void_ // 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; +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 + 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; +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(e1 == 100); // e1 unchanged CHECK(a.error() == 200); } TEST_CASE("expected: assign to value state rebinds error", "[expected_void_ref_e]") { - int err = 99; + int err = 99; expected e; e = expected(unexpect, err); REQUIRE(!e.has_value()); @@ -116,7 +114,7 @@ TEST_CASE("expected: assign to value state rebinds error", "[expected_v } TEST_CASE("expected: assign error state to value state", "[expected_void_ref_e]") { - int err = 99; + int err = 99; expected e(unexpect, err); e = expected(); CHECK(e.has_value()); @@ -126,9 +124,8 @@ TEST_CASE("expected: assign error state to value state", "[expected_voi // Shallow const on error // --------------------------------------------------------------------------- -TEST_CASE("expected: shallow const allows mutation of error referent", - "[expected_void_ref_e]") { - int err = 10; +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); @@ -139,7 +136,7 @@ TEST_CASE("expected: shallow const allows mutation of error referent", // --------------------------------------------------------------------------- TEST_CASE("expected: emplace from error state sets has_value", "[expected_void_ref_e]") { - int err = 5; + int err = 5; expected e(unexpect, err); e.emplace(); CHECK(e.has_value()); @@ -177,8 +174,7 @@ TEST_CASE("expected: value() on success is no-op", "[expected_void_ref_ e.value(); } -TEST_CASE("expected: value() throws bad_expected_access on error", - "[expected_void_ref_e]") { +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); @@ -280,7 +276,7 @@ TEST_CASE("expected: equality with unexpected", "[expected_void_ref_e]" 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 { + auto r = e.and_then([&]() -> expected { ++calls; return 42; }); @@ -316,7 +312,7 @@ 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 { + auto r = e.or_else([&](int& v) -> expected { seen = &v; return expected(unexpect, v); }); @@ -353,7 +349,7 @@ TEST_CASE("expected: transform with void-returning F", "[expected_void_ } TEST_CASE("expected: transform short-circuits on error", "[expected_void_ref_e]") { - int err = 9; + int err = 9; expected e(unexpect, err); bool called = false; auto r = e.transform([&]() -> int { @@ -365,21 +361,22 @@ TEST_CASE("expected: transform short-circuits on error", "[expected_voi CHECK(&r.error() == &err); } -TEST_CASE("expected: transform_error transforms E& to new type", - "[expected_void_ref_e]") { +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); }); + 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]") { +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 ""; }); + auto r = e.transform_error([&](int&) -> std::string { + called = true; + return ""; + }); CHECK(!called); CHECK(r.has_value()); } @@ -389,18 +386,18 @@ TEST_CASE("expected: transform_error with value short-circuits", // --------------------------------------------------------------------------- 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; }); + 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); }); + 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()); } 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 index 2ea31a3..5e4b652 100644 --- a/tests/beman/expected/expected_void_ref_e_const_lvalue_fail.cpp +++ b/tests/beman/expected/expected_void_ref_e_const_lvalue_fail.cpp @@ -2,6 +2,6 @@ // NEGATIVE: cannot bind const lvalue to non-const E& #include void test() { - const int err = 5; + const int err = 5; beman::expected::expected e(beman::expected::unexpect, err); } diff --git a/tests/beman/expected/expected_void_ref_e_temporary_fail.cpp b/tests/beman/expected/expected_void_ref_e_temporary_fail.cpp index 246cba6..3e2619b 100644 --- a/tests/beman/expected/expected_void_ref_e_temporary_fail.cpp +++ b/tests/beman/expected/expected_void_ref_e_temporary_fail.cpp @@ -1,6 +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); -} +void test() { beman::expected::expected e(beman::expected::unexpect, 42); } From 7201658d27956c008c2fa7d6257f3d0b7bf98294 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 5 Jun 2026 18:06:47 -0400 Subject: [PATCH 16/42] chore: remove initial scaffolding files (todo.hpp, todo.test.cpp, todo.cpp) These were empty placeholders from project bootstrap that serve no purpose now that the implementation is complete. --- examples/todo.cpp | 8 -------- include/beman/expected/todo.hpp | 23 ----------------------- tests/beman/expected/CMakeLists.txt | 1 - tests/beman/expected/todo.test.cpp | 10 ---------- 4 files changed, 42 deletions(-) delete mode 100644 examples/todo.cpp delete mode 100644 include/beman/expected/todo.hpp delete mode 100644 tests/beman/expected/todo.test.cpp 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/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 diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index ee7c7a8..da8263e 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -19,7 +19,6 @@ target_sources( expected_ref_e.test.cpp expected_ref_both.test.cpp expected_void_ref_e.test.cpp - todo.test.cpp ) target_link_libraries( beman.expected.tests.expected 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); -} From d9bc1983fb53ea727c22e8751d1f20d609893581 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 5 Jun 2026 18:12:35 -0400 Subject: [PATCH 17/42] chore: remove config.hpp and config_generated.hpp.in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BEMAN_EXPECTED_USE_MODULES only controlled the build system, not code generation — no ABI difference between module and non-module builds. The installed config header served no purpose for consumers. --- CMakeLists.txt | 17 ++--------------- include/beman/expected/CMakeLists.txt | 7 +------ include/beman/expected/config.hpp | 12 ------------ include/beman/expected/config_generated.hpp.in | 8 -------- 4 files changed, 3 insertions(+), 41 deletions(-) delete mode 100644 include/beman/expected/config.hpp delete mode 100644 include/beman/expected/config_generated.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d9b52e..aba09a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,12 +32,6 @@ if(BEMAN_EXPECTED_USE_MODULES) set(CMAKE_CXX_SCAN_FOR_MODULES ON) endif() -configure_file( - "${PROJECT_SOURCE_DIR}/include/beman/expected/config_generated.hpp.in" - "${PROJECT_BINARY_DIR}/include/beman/expected/config_generated.hpp" - @ONLY -) - # for find of beman_install_library and configure_build_telemetry include(infra/cmake/beman-install-library.cmake) include(infra/cmake/BuildTelemetryConfig.cmake) @@ -54,21 +48,14 @@ if(BEMAN_EXPECTED_USE_MODULES) beman.expected PUBLIC FILE_SET CXX_MODULES - FILE_SET HEADERS - BASE_DIRS - "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_BINARY_DIR}/include" + FILE_SET HEADERS BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" ) set_target_properties(beman.expected PROPERTIES CXX_MODULE_STD ON) target_compile_features(beman.expected PUBLIC cxx_std_23) else() target_sources( beman.expected - PUBLIC - FILE_SET HEADERS - BASE_DIRS - "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_BINARY_DIR}/include" + PUBLIC FILE_SET HEADERS BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" ) set_target_properties( beman.expected diff --git a/include/beman/expected/CMakeLists.txt b/include/beman/expected/CMakeLists.txt index 86ab1bc..92cd8c6 100644 --- a/include/beman/expected/CMakeLists.txt +++ b/include/beman/expected/CMakeLists.txt @@ -7,12 +7,7 @@ if(BEMAN_EXPECTED_USE_MODULES) PUBLIC FILE_SET CXX_MODULES FILES expected.cppm FILE_SET HEADERS - FILES - config.hpp - expected.hpp - unexpected.hpp - bad_expected_access.hpp - "${PROJECT_BINARY_DIR}/include/beman/expected/config_generated.hpp" + FILES expected.hpp unexpected.hpp bad_expected_access.hpp ) else() target_sources( diff --git a/include/beman/expected/config.hpp b/include/beman/expected/config.hpp deleted file mode 100644 index bfaf5ac..0000000 --- a/include/beman/expected/config.hpp +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#ifndef BEMAN_EXPECTED_CONFIG_HPP -#define BEMAN_EXPECTED_CONFIG_HPP - -#if !defined(__has_include) || __has_include() - #include -#else - #define BEMAN_EXPECTED_USE_MODULES() 0 -#endif - -#endif diff --git a/include/beman/expected/config_generated.hpp.in b/include/beman/expected/config_generated.hpp.in deleted file mode 100644 index 6d71197..0000000 --- a/include/beman/expected/config_generated.hpp.in +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#ifndef BEMAN_EXPECTED_CONFIG_GENERATED_HPP -#define BEMAN_EXPECTED_CONFIG_GENERATED_HPP - -#cmakedefine01 BEMAN_EXPECTED_USE_MODULES() - -#endif From 4719d8ffb7b4100fbbae66acb14e81d76f93cbdd Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 5 Jun 2026 18:30:14 -0400 Subject: [PATCH 18/42] docs: add painfully honest review guides for LLM and human reviewers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LLM guide: specification sources, clause-by-clause protocol, known issues to investigate, structured output format. Human guide: 10 design decisions as discussion points — rebind semantics, code duplication trade-offs, dangling prevention subtleties, test coverage gaps, and the "should this be how it works for 30 years" framing. --- docs/human-design-review-guide.md | 236 ++++++++++++++++++++++++++++++ docs/llm-code-review-guide.md | 187 +++++++++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100644 docs/human-design-review-guide.md create mode 100644 docs/llm-code-review-guide.md diff --git a/docs/human-design-review-guide.md b/docs/human-design-review-guide.md new file mode 100644 index 0000000..f47d23d --- /dev/null +++ b/docs/human-design-review-guide.md @@ -0,0 +1,236 @@ +# 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: No `unexpected` Construction or Assignment for `E&` Specializations + +### What was done + +For `expected`, `expected`, and `expected`, constructing from `unexpected` is `= delete`. So is assignment from `unexpected`. + +```cpp +int err = 42; +expected e(unexpect, err); // OK: unexpect_t ctor takes lvalue +expected e2(unexpected(42)); // ERROR: deleted +e = unexpected(42); // ERROR: deleted +``` + +### Why + +An `unexpected` is a temporary that owns its `G`. Binding `E&` to the `G` inside it creates a dangling reference the moment the `unexpected` is destroyed (end of the full-expression). There is no safe way to support this. + +### What to discuss + +- **Is `unexpected` the answer?** If someone has `unexpected(ref)`, could that be used to construct `expected`? Currently this path doesn't exist. Should it? +- **Ergonomic cost.** The "obvious" way to create an error-state expected is `expected(unexpected(err))`. That doesn't work. Users must write `expected(unexpect, err)` instead. This will be a FAQ. +- **The `= delete("message")` diagnostics help but don't solve the discoverability problem.** A user gets `expected: no constructor from unexpected; use (unexpect, lvalue_ref)` — but they have to trigger the error to see it. Can we do better in documentation? + +--- + +## 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. + +--- + +## How to Structure Your Review + +For each decision above, state: + +1. **Agree / Disagree / Need More Information** +2. **If disagree:** What alternative would you propose, and what is its cost? +3. **If agree:** Is the implementation faithful to the stated design intent? + +Do not review this code as if it were a typical pull request. Review it as proposed standard library wording rendered into C++. The question is not "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." diff --git a/docs/llm-code-review-guide.md b/docs/llm-code-review-guide.md new file mode 100644 index 0000000..a0f16b2 --- /dev/null +++ b/docs/llm-code-review-guide.md @@ -0,0 +1,187 @@ +# 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 | **deleted** | **deleted** | **deleted** | +| 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. + +#### 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 + +--- + +## 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. From 4f23a4ed8bbe729f004d59023c5ec7abe9e953a9 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 5 Jun 2026 18:42:57 -0400 Subject: [PATCH 19/42] fix: add process_coverage target and update gcovr.cfg.in filter Borrowed from example/main. The Makefile coverage target calls `--target process_coverage` which didn't exist. Added the custom target with configure_file for gcovr.cfg, and fixed the filter pattern to match beman/expected sources. --- CMakeLists.txt | 11 +++++++++++ cmake/gcovr.cfg.in | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aba09a5..fd1ab77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,3 +87,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/cmake/gcovr.cfg.in b/cmake/gcovr.cfg.in index b4e248b..fedcd8c 100644 --- a/cmake/gcovr.cfg.in +++ b/cmake/gcovr.cfg.in @@ -7,7 +7,7 @@ gcov-parallel = yes html-theme = github.dark-blue html-self-contained = yes print-summary = yes -filter = .*/smd/conceptmap/.* -exclude = .*\.t\.cpp +filter = .*/beman/expected/.* +exclude = .*\.test\.cpp coveralls = coverage.json coveralls-pretty = yes From d64e4a6beca5c1574af2fa39e5e17f0acfa852f6 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 5 Jun 2026 18:57:45 -0400 Subject: [PATCH 20/42] test: add coverage tests for rvalue/const-rvalue monadic, throw, and equality paths 94 new test cases targeting lines uncovered by gcov: - Monadic rvalue/const-rvalue overloads on both value and error paths for all 6 specializations (and_then, or_else, transform, transform_error) - value() throw paths for rvalue and const-rvalue overloads - error_or/value_or rvalue overloads - Constructor coverage (unexpect_t init-list, emplace on error state) - Cross-type equality operators - bad_expected_access rvalue error accessors Also narrow gcovr filter to include/ path only so test .cpp files don't inflate the line count. 563 tests pass (509 positive + 54 negative). --- cmake/gcovr.cfg.in | 3 +- tests/beman/expected/CMakeLists.txt | 1 + .../beman/expected/expected_coverage.test.cpp | 812 ++++++++++++++++++ 3 files changed, 814 insertions(+), 2 deletions(-) create mode 100644 tests/beman/expected/expected_coverage.test.cpp diff --git a/cmake/gcovr.cfg.in b/cmake/gcovr.cfg.in index fedcd8c..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 = .*/beman/expected/.* -exclude = .*\.test\.cpp +filter = .*/include/beman/expected/.* coveralls = coverage.json coveralls-pretty = yes diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index da8263e..971f2be 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources( expected_ref_e.test.cpp expected_ref_both.test.cpp expected_void_ref_e.test.cpp + expected_coverage.test.cpp ) target_link_libraries( beman.expected.tests.expected diff --git a/tests/beman/expected/expected_coverage.test.cpp b/tests/beman/expected/expected_coverage.test.cpp new file mode 100644 index 0000000..71986c7 --- /dev/null +++ b/tests/beman/expected/expected_coverage.test.cpp @@ -0,0 +1,812 @@ +// beman/expected/expected_coverage.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// Tests targeting lines uncovered by gcov: rvalue/const-rvalue monadic +// overloads, value()/error() throw paths, rvalue value_or/error_or, +// constructor/assignment error-path branches, and cross-type equality. + +#include +#include + +#include + +#include +#include +#include +#include + +namespace expt = beman::expected; +using expt::expected; +using expt::unexpect; +using expt::unexpected; + +// ============================================================================= +// expected — primary template coverage gaps +// ============================================================================= + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state (short-circuit) --- +TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { + 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("coverage: and_then const rvalue on error short-circuits", "[coverage]") { + 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { + 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("coverage: or_else const rvalue on value short-circuits", "[coverage]") { + 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("coverage: transform rvalue on error short-circuits", "[coverage]") { + 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("coverage: transform const rvalue on error short-circuits", "[coverage]") { + 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { + expected e("val"); + auto r = std::move(e).transform_error([](int v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { + 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("coverage: transform const rvalue on value calls F", "[coverage]") { + 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { + 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); +} + +// --- value() throw from rvalue and const rvalue --- +TEST_CASE("coverage: value() rvalue throws with moved error", "[coverage]") { + expected e(unexpect, "rval-err"); + CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); +} + +TEST_CASE("coverage: value() const rvalue throws", "[coverage]") { + const expected e(unexpect, "crval-err"); + CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); +} + +// --- error_or rvalue overloads --- +TEST_CASE("coverage: error_or rvalue uses default when has value", "[coverage]") { + expected e(42); + std::string s = std::move(e).error_or("fallback"); + CHECK(s == "fallback"); +} + +// --- unexpect_t constructor with initializer_list --- +TEST_CASE("coverage: unexpect_t constructor with initializer_list", "[coverage]") { + 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("coverage: in_place_t constructor with initializer_list", "[coverage]") { + 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("coverage: emplace on error state transitions to value", "[coverage]") { + expected e(unexpect, "err"); + e.emplace(42); + REQUIRE(e.has_value()); + CHECK(*e == 42); +} + +// --- Cross-type equality (expected vs expected) --- +TEST_CASE("coverage: cross-type equality different error types", "[coverage]") { + expected a(unexpect, 1); + expected b(unexpect, 1L); + CHECK(a == b); +} + +TEST_CASE("coverage: cross-type equality error vs value", "[coverage]") { + expected a(42); + expected b(unexpect, 0L); + CHECK_FALSE(a == b); +} + +// ============================================================================= +// expected — void specialization coverage gaps +// ============================================================================= + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- +TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { + 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("coverage: and_then const rvalue on error short-circuits", "[coverage]") { + 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("coverage: and_then rvalue on value calls F", "[coverage]") { + 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("coverage: and_then const rvalue on value calls F", "[coverage]") { + 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { + 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("coverage: or_else const rvalue on value short-circuits", "[coverage]") { + const expected e; + auto r = std::move(e).or_else([](int) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +// --- Monadic: transform rvalue/const-rvalue on ERROR state --- +TEST_CASE("coverage: transform rvalue on error short-circuits", "[coverage]") { + expected e(unexpect, "err"); + auto r = std::move(e).transform([]() { return 1; }); + REQUIRE(!r.has_value()); + CHECK(r.error() == "err"); +} + +TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { + 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("coverage: transform rvalue on value calls F", "[coverage]") { + expected e; + auto r = std::move(e).transform([]() { return 42; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("coverage: transform const rvalue on value calls F", "[coverage]") { + 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { + expected e; + auto r = std::move(e).transform_error([](int v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("coverage: transform_error rvalue on error calls F", "[coverage]") { + 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { + 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); +} + +// --- value() throw paths for void --- +TEST_CASE("coverage: value() rvalue throws", "[coverage]") { + expected e(unexpect, 42); + CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); +} + +TEST_CASE("coverage: value() const rvalue throws", "[coverage]") { + const expected e(unexpect, 42); + CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); +} + +// --- void unexpect_t with init-list --- +TEST_CASE("coverage: unexpect_t constructor with initializer_list", "[coverage]") { + 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("coverage: move-assign error to value state", "[coverage]") { + expected a; + expected b(unexpect, "e"); + a = std::move(b); + REQUIRE(!a.has_value()); + CHECK(a.error() == "e"); +} + +TEST_CASE("coverage: move-assign value to error state", "[coverage]") { + expected a(unexpect, "e"); + expected b; + a = std::move(b); + REQUIRE(a.has_value()); +} + +// --- void cross-type equality --- +TEST_CASE("coverage: cross-type equality with expected", "[coverage]") { + expected a(unexpect, 1); + expected b(unexpect, 1L); + CHECK(a == b); + expected c; + CHECK_FALSE(a == c); +} + +// ============================================================================= +// expected — reference specialization coverage gaps +// ============================================================================= + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- +TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { + 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("coverage: and_then const rvalue on error short-circuits", "[coverage]") { + 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("coverage: and_then const rvalue on value calls F", "[coverage]") { + 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { + 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("coverage: or_else const rvalue on value short-circuits", "[coverage]") { + 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("coverage: or_else rvalue on error calls F", "[coverage]") { + 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("coverage: transform rvalue on error short-circuits", "[coverage]") { + 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("coverage: transform const rvalue on error short-circuits", "[coverage]") { + 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("coverage: transform const rvalue on value calls F", "[coverage]") { + 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { + 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("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { + 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { + 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); +} + +// --- value() throw paths --- +TEST_CASE("coverage: value() throws on error state", "[coverage]") { + expected e(unexpect, 42); + CHECK_THROWS_AS(e.value(), expt::bad_expected_access); +} + +// --- Cross-type equality --- +TEST_CASE("coverage: equality error vs value", "[coverage]") { + int x = 1; + expected a(x); + expected b(unexpect, 0); + CHECK_FALSE(a == b); +} + +// ============================================================================= +// expected — error-reference specialization coverage gaps +// ============================================================================= + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- +TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { + 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("coverage: and_then const rvalue on error short-circuits", "[coverage]") { + 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { + 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("coverage: or_else const rvalue on value short-circuits", "[coverage]") { + 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("coverage: transform rvalue on error short-circuits", "[coverage]") { + int err = 3; + expected e(unexpect, err); + auto r = std::move(e).transform([](int v) { return v * 2; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { + 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { + expected e(42); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { + const expected e(42); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("coverage: transform_error rvalue on error calls F", "[coverage]") { + 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { + 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("coverage: value assignment on value state", "[coverage]") { + expected e(10); + e = 20; + REQUIRE(e.has_value()); + CHECK(*e == 20); +} + +// --- Cross-type equality --- +TEST_CASE("coverage: equality error vs value", "[coverage]") { + int err = 0; + expected a(1); + expected b(unexpect, err); + CHECK_FALSE(a == b); +} + +// ============================================================================= +// expected — both-reference specialization coverage gaps +// ============================================================================= + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- +TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { + 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("coverage: and_then const rvalue on error short-circuits", "[coverage]") { + 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { + 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("coverage: or_else const rvalue on value short-circuits", "[coverage]") { + 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("coverage: transform rvalue on error short-circuits", "[coverage]") { + int err = 3; + expected e(unexpect, err); + auto r = std::move(e).transform([](int& v) { return v * 2; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { + 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { + 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("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { + 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("coverage: transform_error rvalue on error calls F", "[coverage]") { + 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { + 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("coverage: equality error vs value", "[coverage]") { + int x = 1, err = 0; + expected a(x); + expected b(unexpect, err); + CHECK_FALSE(a == b); +} + +// ============================================================================= +// expected — void+error-reference specialization coverage gaps +// ============================================================================= + +// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- +TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { + int err = 5; + expected e(unexpect, err); + auto r = std::move(e).and_then([]() -> expected { return {}; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[coverage]") { + 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("coverage: and_then rvalue on value calls F", "[coverage]") { + 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("coverage: and_then const rvalue on value calls F", "[coverage]") { + 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { + expected e; + auto r = std::move(e).or_else([](int&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[coverage]") { + const expected e; + auto r = std::move(e).or_else([](int&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +// --- Monadic: transform rvalue/const-rvalue --- +TEST_CASE("coverage: transform rvalue on error short-circuits", "[coverage]") { + int err = 3; + expected e(unexpect, err); + auto r = std::move(e).transform([]() { return 1; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { + int err = 3; + const expected e(unexpect, err); + auto r = std::move(e).transform([]() { return 1; }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("coverage: transform rvalue on value calls F", "[coverage]") { + expected e; + auto r = std::move(e).transform([]() { return 42; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("coverage: transform const rvalue on value calls F", "[coverage]") { + 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { + expected e; + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { + const expected e; + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("coverage: transform_error rvalue on error calls F", "[coverage]") { + 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { + 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("coverage: value() throws on error state", "[coverage]") { + int err = 42; + expected e(unexpect, err); + CHECK_THROWS_AS(e.value(), expt::bad_expected_access); +} + +TEST_CASE("coverage: value() rvalue throws on error state", "[coverage]") { + int err = 42; + expected e(unexpect, err); + CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); +} + +// --- Cross-type equality --- +TEST_CASE("coverage: equality error vs value", "[coverage]") { + int err = 0; + expected a; + expected b(unexpect, err); + CHECK_FALSE(a == b); +} + +// ============================================================================= +// bad_expected_access coverage +// ============================================================================= + +TEST_CASE("coverage: bad_expected_access move constructor", "[coverage]") { + expt::bad_expected_access orig("test error"); + expt::bad_expected_access moved(std::move(orig)); + CHECK(moved.error() == "test error"); +} + +TEST_CASE("coverage: bad_expected_access rvalue error accessor", "[coverage]") { + expt::bad_expected_access e("val"); + std::string s = std::move(e).error(); + CHECK(s == "val"); +} + +TEST_CASE("coverage: bad_expected_access const rvalue error accessor", "[coverage]") { + const expt::bad_expected_access e("val"); + std::string s = std::move(e).error(); + CHECK(s == "val"); +} From c9e8715c509ca925640d7f5b22bde181cce134ea Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 5 Jun 2026 19:21:48 -0400 Subject: [PATCH 21/42] test: add shared test types and comprehensive coverage tests New testing::types.hpp in beman::expected::testing namespace with purpose-built types: traced (non-trivial dtor/ctor), move_only, init_list_type, narrowed/widened (converting ctors), from_expected (derived from expected), from_unexpected (derived from unexpected), eq_a/eq_b (cross-type equality). Coverage tests exercise: non-trivial destructor/ctor/assignment paths for all specializations, init-list in_place_t/unexpect_t constructors, converting constructors, all 4 monadic ref-qualified overloads on both value and error paths with non-trivial types, nested expected and expected constructor disambiguation, lvalue monadic overloads for reference specializations, cross-type equality. 708 tests pass (654 positive + 54 negative). --- .../beman/expected/expected_coverage.test.cpp | 1264 +++++++++++++++-- tests/beman/expected/testing/types.hpp | 131 ++ 2 files changed, 1312 insertions(+), 83 deletions(-) create mode 100644 tests/beman/expected/testing/types.hpp diff --git a/tests/beman/expected/expected_coverage.test.cpp b/tests/beman/expected/expected_coverage.test.cpp index 71986c7..c038cb3 100644 --- a/tests/beman/expected/expected_coverage.test.cpp +++ b/tests/beman/expected/expected_coverage.test.cpp @@ -9,6 +9,8 @@ #include +#include "testing/types.hpp" + #include #include #include @@ -18,6 +20,7 @@ namespace expt = beman::expected; using expt::expected; using expt::unexpect; using expt::unexpected; +using namespace beman::expected::testing; // ============================================================================= // expected — primary template coverage gaps @@ -27,7 +30,7 @@ using expt::unexpected; TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { expected e(unexpect, "err"); bool called = false; - auto r = std::move(e).and_then([&](int) -> expected { + auto r = std::move(e).and_then([&](int) -> expected { called = true; return 0; }); @@ -39,7 +42,7 @@ TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]" TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[coverage]") { const expected e(unexpect, "err"); bool called = false; - auto r = std::move(e).and_then([&](int) -> expected { + auto r = std::move(e).and_then([&](int) -> expected { called = true; return 0; }); @@ -52,7 +55,7 @@ TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[cove TEST_CASE("coverage: or_else rvalue on value short-circuits", "[coverage]") { expected e("val"); bool called = false; - auto r = std::move(e).or_else([&](int) -> expected { + auto r = std::move(e).or_else([&](int) -> expected { called = true; return "x"; }); @@ -63,7 +66,7 @@ TEST_CASE("coverage: or_else rvalue on value short-circuits", "[coverage]") TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[coverage]") { const expected e(42); bool called = false; - auto r = std::move(e).or_else([&](int) -> expected { + auto r = std::move(e).or_else([&](int) -> expected { called = true; return 0; }); @@ -178,7 +181,7 @@ TEST_CASE("coverage: cross-type equality error vs value", "[coverage]") { TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { expected e(unexpect, "err"); bool called = false; - auto r = std::move(e).and_then([&]() -> expected { + auto r = std::move(e).and_then([&]() -> expected { called = true; return {}; }); @@ -189,7 +192,7 @@ TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverag TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[coverage]") { const expected e(unexpect, "err"); bool called = false; - auto r = std::move(e).and_then([&]() -> expected { + auto r = std::move(e).and_then([&]() -> expected { called = true; return {}; }); @@ -201,7 +204,7 @@ TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[c TEST_CASE("coverage: and_then rvalue on value calls F", "[coverage]") { expected e; bool called = false; - auto r = std::move(e).and_then([&]() -> expected { + auto r = std::move(e).and_then([&]() -> expected { called = true; return 42; }); @@ -212,7 +215,7 @@ TEST_CASE("coverage: and_then rvalue on value calls F", "[coverage]") { TEST_CASE("coverage: and_then const rvalue on value calls F", "[coverage]") { const expected e; - auto r = std::move(e).and_then([]() -> expected { return 99; }); + auto r = std::move(e).and_then([]() -> expected { return 99; }); REQUIRE(r.has_value()); CHECK(*r == 99); } @@ -221,7 +224,7 @@ TEST_CASE("coverage: and_then const rvalue on value calls F", "[coverage TEST_CASE("coverage: or_else rvalue on value short-circuits", "[coverage]") { expected e; bool called = false; - auto r = std::move(e).or_else([&](int) -> expected { + auto r = std::move(e).or_else([&](int) -> expected { called = true; return {}; }); @@ -231,7 +234,7 @@ TEST_CASE("coverage: or_else rvalue on value short-circuits", "[coverage TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[coverage]") { const expected e; - auto r = std::move(e).or_else([](int) -> expected { return {}; }); + auto r = std::move(e).or_else([](int) -> expected { return {}; }); REQUIRE(r.has_value()); } @@ -335,7 +338,7 @@ TEST_CASE("coverage: cross-type equality with expected", "[cove // --- Monadic: and_then rvalue/const-rvalue on ERROR state --- TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { expected e(unexpect, "err"); - auto r = std::move(e).and_then([](int& v) -> expected { return v; }); + auto r = std::move(e).and_then([](int& v) -> expected { return v; }); REQUIRE(!r.has_value()); CHECK(r.error() == "err"); } @@ -348,18 +351,18 @@ TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[cov // --- Monadic: and_then const-rvalue on VALUE state --- TEST_CASE("coverage: and_then const rvalue on value calls F", "[coverage]") { - int x = 5; + int x = 5; const expected e(x); - auto r = std::move(e).and_then([](int& v) -> expected { return v * 2; }); + 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { - int x = 42; + int x = 42; expected e(x); - auto r = std::move(e).or_else([](int) -> expected { + auto r = std::move(e).or_else([](int) -> expected { static int dummy = 0; return expected(dummy); }); @@ -368,9 +371,9 @@ TEST_CASE("coverage: or_else rvalue on value short-circuits", "[coverage]" } TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[coverage]") { - int x = 42; + int x = 42; const expected e(x); - auto r = std::move(e).or_else([](int) -> expected { + auto r = std::move(e).or_else([](int) -> expected { static int dummy = 0; return expected(dummy); }); @@ -381,7 +384,7 @@ TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[cove // --- Monadic: or_else rvalue/const-rvalue on ERROR state --- TEST_CASE("coverage: or_else rvalue on error calls F", "[coverage]") { expected e(unexpect, 3); - auto r = std::move(e).or_else([](int v) -> expected { + auto r = std::move(e).or_else([](int v) -> expected { static int result = 0; result = v * 10; return expected(result); @@ -406,32 +409,32 @@ TEST_CASE("coverage: transform const rvalue on error short-circuits", "[co // --- Monadic: transform const-rvalue on VALUE state --- TEST_CASE("coverage: transform const rvalue on value calls F", "[coverage]") { - int x = 4; + int x = 4; const expected e(x); - auto r = std::move(e).transform([](int& v) { return v + 1; }); + 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { - int x = 42; + int x = 42; expected e(x); - auto r = std::move(e).transform_error([](int v) { return v * 2; }); + auto r = std::move(e).transform_error([](int v) { return v * 2; }); REQUIRE(r.has_value()); CHECK(*r == 42); } TEST_CASE("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { - int x = 42; + int x = 42; const expected e(x); - auto r = std::move(e).transform_error([](int v) { return v * 2; }); + auto r = std::move(e).transform_error([](int v) { return v * 2; }); REQUIRE(r.has_value()); } TEST_CASE("coverage: transform_error const rvalue on error calls F", "[coverage]") { const expected e(unexpect, 7); - auto r = std::move(e).transform_error([](int v) { return v + 1; }); + auto r = std::move(e).transform_error([](int v) { return v + 1; }); REQUIRE(!r.has_value()); CHECK(r.error() == 8); } @@ -444,7 +447,7 @@ TEST_CASE("coverage: value() throws on error state", "[coverage]") { // --- Cross-type equality --- TEST_CASE("coverage: equality error vs value", "[coverage]") { - int x = 1; + int x = 1; expected a(x); expected b(unexpect, 0); CHECK_FALSE(a == b); @@ -456,9 +459,9 @@ TEST_CASE("coverage: equality error vs value", "[coverage]") { // --- Monadic: and_then rvalue/const-rvalue on ERROR state --- TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { - int err = 5; + int err = 5; expected e(unexpect, err); - auto r = std::move(e).and_then([](int) -> expected { + auto r = std::move(e).and_then([](int) -> expected { static int dummy = 0; return expected(unexpect, dummy); }); @@ -467,9 +470,9 @@ TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage] } TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[coverage]") { - int err = 5; + int err = 5; const expected e(unexpect, err); - auto r = std::move(e).and_then([](int) -> expected { + auto r = std::move(e).and_then([](int) -> expected { static int dummy = 0; return expected(unexpect, dummy); }); @@ -479,7 +482,7 @@ TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[cov // --- Monadic: or_else rvalue/const-rvalue on VALUE state --- TEST_CASE("coverage: or_else rvalue on value short-circuits", "[coverage]") { expected e(42); - auto r = std::move(e).or_else([](int&) -> expected { + auto r = std::move(e).or_else([](int&) -> expected { static int dummy = 0; return expected(unexpect, dummy); }); @@ -489,7 +492,7 @@ TEST_CASE("coverage: or_else rvalue on value short-circuits", "[coverage]" TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[coverage]") { const expected e(42); - auto r = std::move(e).or_else([](int&) -> expected { + auto r = std::move(e).or_else([](int&) -> expected { static int dummy = 0; return expected(unexpect, dummy); }); @@ -498,45 +501,45 @@ TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[cove // --- Monadic: transform rvalue/const-rvalue on ERROR state --- TEST_CASE("coverage: transform rvalue on error short-circuits", "[coverage]") { - int err = 3; + int err = 3; expected e(unexpect, err); - auto r = std::move(e).transform([](int v) { return v * 2; }); + auto r = std::move(e).transform([](int v) { return v * 2; }); REQUIRE(!r.has_value()); } TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { - int err = 3; + int err = 3; const expected e(unexpect, err); - auto r = std::move(e).transform([](int v) { return v * 2; }); + auto r = std::move(e).transform([](int v) { return v * 2; }); REQUIRE(!r.has_value()); } // --- Monadic: transform_error rvalue/const-rvalue --- TEST_CASE("coverage: transform_error rvalue on value short-circuits", "[coverage]") { expected e(42); - auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); REQUIRE(r.has_value()); CHECK(*r == 42); } TEST_CASE("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { const expected e(42); - auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); REQUIRE(r.has_value()); } TEST_CASE("coverage: transform_error rvalue on error calls F", "[coverage]") { - int err = 5; + int err = 5; expected e(unexpect, err); - auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + auto r = std::move(e).transform_error([](int& v) { return v + 1; }); REQUIRE(!r.has_value()); CHECK(r.error() == 6); } TEST_CASE("coverage: transform_error const rvalue on error calls F", "[coverage]") { - int err = 5; + int err = 5; const expected e(unexpect, err); - auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + auto r = std::move(e).transform_error([](int& v) { return v + 1; }); REQUIRE(!r.has_value()); CHECK(r.error() == 6); } @@ -551,7 +554,7 @@ TEST_CASE("coverage: value assignment on value state", "[coverage]") { // --- Cross-type equality --- TEST_CASE("coverage: equality error vs value", "[coverage]") { - int err = 0; + int err = 0; expected a(1); expected b(unexpect, err); CHECK_FALSE(a == b); @@ -563,9 +566,9 @@ TEST_CASE("coverage: equality error vs value", "[coverage]") { // --- Monadic: and_then rvalue/const-rvalue on ERROR state --- TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { - int err = 5; + int err = 5; expected e(unexpect, err); - auto r = std::move(e).and_then([](int&) -> expected { + auto r = std::move(e).and_then([](int&) -> expected { static int dummy = 0; return expected(unexpect, dummy); }); @@ -573,9 +576,9 @@ TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage } TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[coverage]") { - int err = 5; + int err = 5; const expected e(unexpect, err); - auto r = std::move(e).and_then([](int&) -> expected { + auto r = std::move(e).and_then([](int&) -> expected { static int dummy = 0; return expected(unexpect, dummy); }); @@ -584,9 +587,9 @@ TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[co // --- Monadic: or_else rvalue/const-rvalue on VALUE state --- TEST_CASE("coverage: or_else rvalue on value short-circuits", "[coverage]") { - int x = 42; + int x = 42; expected e(x); - auto r = std::move(e).or_else([](int&) -> expected { + auto r = std::move(e).or_else([](int&) -> expected { static int dummy = 0; return expected(dummy); }); @@ -595,9 +598,9 @@ TEST_CASE("coverage: or_else rvalue on value short-circuits", "[coverage] } TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[coverage]") { - int x = 42; + int x = 42; const expected e(x); - auto r = std::move(e).or_else([](int&) -> expected { + auto r = std::move(e).or_else([](int&) -> expected { static int dummy = 0; return expected(dummy); }); @@ -606,54 +609,54 @@ TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[cov // --- Monadic: transform rvalue/const-rvalue on ERROR state --- TEST_CASE("coverage: transform rvalue on error short-circuits", "[coverage]") { - int err = 3; + int err = 3; expected e(unexpect, err); - auto r = std::move(e).transform([](int& v) { return v * 2; }); + auto r = std::move(e).transform([](int& v) { return v * 2; }); REQUIRE(!r.has_value()); } TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { - int err = 3; + int err = 3; const expected e(unexpect, err); - auto r = std::move(e).transform([](int& v) { return v * 2; }); + auto r = std::move(e).transform([](int& v) { return v * 2; }); REQUIRE(!r.has_value()); } // --- Monadic: transform_error rvalue/const-rvalue --- TEST_CASE("coverage: transform_error rvalue on value short-circuits", "[coverage]") { - int x = 42; + int x = 42; expected e(x); - auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); REQUIRE(r.has_value()); CHECK(*r == 42); } TEST_CASE("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { - int x = 42; + int x = 42; const expected e(x); - auto r = std::move(e).transform_error([](int& v) { return v * 2; }); + auto r = std::move(e).transform_error([](int& v) { return v * 2; }); REQUIRE(r.has_value()); } TEST_CASE("coverage: transform_error rvalue on error calls F", "[coverage]") { - int err = 5; + int err = 5; expected e(unexpect, err); - auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + auto r = std::move(e).transform_error([](int& v) { return v + 1; }); REQUIRE(!r.has_value()); CHECK(r.error() == 6); } TEST_CASE("coverage: transform_error const rvalue on error calls F", "[coverage]") { - int err = 5; + int err = 5; const expected e(unexpect, err); - auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + 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("coverage: equality error vs value", "[coverage]") { - int x = 1, err = 0; + int x = 1, err = 0; expected a(x); expected b(unexpect, err); CHECK_FALSE(a == b); @@ -665,16 +668,16 @@ TEST_CASE("coverage: equality error vs value", "[coverage]") { // --- Monadic: and_then rvalue/const-rvalue on ERROR state --- TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { - int err = 5; + int err = 5; expected e(unexpect, err); - auto r = std::move(e).and_then([]() -> expected { return {}; }); + auto r = std::move(e).and_then([]() -> expected { return {}; }); REQUIRE(!r.has_value()); } TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[coverage]") { - int err = 5; + int err = 5; const expected e(unexpect, err); - auto r = std::move(e).and_then([]() -> expected { return {}; }); + auto r = std::move(e).and_then([]() -> expected { return {}; }); REQUIRE(!r.has_value()); } @@ -682,7 +685,7 @@ TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[ TEST_CASE("coverage: and_then rvalue on value calls F", "[coverage]") { expected e; bool called = false; - auto r = std::move(e).and_then([&]() -> expected { + auto r = std::move(e).and_then([&]() -> expected { called = true; return 42; }); @@ -692,7 +695,7 @@ TEST_CASE("coverage: and_then rvalue on value calls F", "[coverage]") { TEST_CASE("coverage: and_then const rvalue on value calls F", "[coverage]") { const expected e; - auto r = std::move(e).and_then([]() -> expected { return 99; }); + auto r = std::move(e).and_then([]() -> expected { return 99; }); REQUIRE(r.has_value()); CHECK(*r == 99); } @@ -700,28 +703,28 @@ TEST_CASE("coverage: and_then const rvalue on value calls F", "[coverag // --- Monadic: or_else rvalue/const-rvalue on VALUE state --- TEST_CASE("coverage: or_else rvalue on value short-circuits", "[coverage]") { expected e; - auto r = std::move(e).or_else([](int&) -> expected { return {}; }); + auto r = std::move(e).or_else([](int&) -> expected { return {}; }); REQUIRE(r.has_value()); } TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[coverage]") { const expected e; - auto r = std::move(e).or_else([](int&) -> expected { return {}; }); + auto r = std::move(e).or_else([](int&) -> expected { return {}; }); REQUIRE(r.has_value()); } // --- Monadic: transform rvalue/const-rvalue --- TEST_CASE("coverage: transform rvalue on error short-circuits", "[coverage]") { - int err = 3; + int err = 3; expected e(unexpect, err); - auto r = std::move(e).transform([]() { return 1; }); + auto r = std::move(e).transform([]() { return 1; }); REQUIRE(!r.has_value()); } TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { - int err = 3; + int err = 3; const expected e(unexpect, err); - auto r = std::move(e).transform([]() { return 1; }); + auto r = std::move(e).transform([]() { return 1; }); REQUIRE(!r.has_value()); } @@ -753,37 +756,37 @@ TEST_CASE("coverage: transform_error const rvalue on value short-circui } TEST_CASE("coverage: transform_error rvalue on error calls F", "[coverage]") { - int err = 5; + int err = 5; expected e(unexpect, err); - auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + auto r = std::move(e).transform_error([](int& v) { return v + 1; }); REQUIRE(!r.has_value()); CHECK(r.error() == 6); } TEST_CASE("coverage: transform_error const rvalue on error calls F", "[coverage]") { - int err = 5; + int err = 5; const expected e(unexpect, err); - auto r = std::move(e).transform_error([](int& v) { return v + 1; }); + 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("coverage: value() throws on error state", "[coverage]") { - int err = 42; + int err = 42; expected e(unexpect, err); CHECK_THROWS_AS(e.value(), expt::bad_expected_access); } TEST_CASE("coverage: value() rvalue throws on error state", "[coverage]") { - int err = 42; + int err = 42; expected e(unexpect, err); CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); } // --- Cross-type equality --- TEST_CASE("coverage: equality error vs value", "[coverage]") { - int err = 0; + int err = 0; expected a; expected b(unexpect, err); CHECK_FALSE(a == b); @@ -810,3 +813,1098 @@ TEST_CASE("coverage: bad_expected_access const rvalue error accessor", "[coverag std::string s = std::move(e).error(); CHECK(s == "val"); } + +// ============================================================================= +// Tests using beman::expected::testing types for non-trivial path coverage +// ============================================================================= + +// --------------------------------------------------------------------------- +// expected: non-trivial destructor, copy/move ctor, assignment +// --------------------------------------------------------------------------- + +TEST_CASE("traced: default construct (value state)", "[coverage][traced]") { + expected e(std::in_place, 42); + REQUIRE(e.has_value()); + CHECK(e->val == 42); +} + +TEST_CASE("traced: error construct", "[coverage][traced]") { + expected e(unexpect, 7); + REQUIRE(!e.has_value()); + CHECK(e.error().val == 7); +} + +TEST_CASE("traced: copy construct value state", "[coverage][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", "[coverage][traced]") { + expected a(unexpect, 20); + expected b(a); + REQUIRE(!b.has_value()); + CHECK(b.error().val == 20); +} + +TEST_CASE("traced: move construct value state", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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)", "[coverage][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)", "[coverage][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", "[coverage][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)", "[coverage][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)", "[coverage][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&", "[coverage][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&&", "[coverage][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", "[coverage][traced]") { + expected e(unexpect, 1); + e.emplace(42); + REQUIRE(e.has_value()); + CHECK(e->val == 42); +} + +TEST_CASE("traced: destructor runs for value state", "[coverage][traced]") { + { + expected e(std::in_place, 1); + } +} + +TEST_CASE("traced: destructor runs for error state", "[coverage][traced]") { + { + expected e(unexpect, 1); + } +} + +// --- traced monadic: all 4 overloads × value + error paths --- + +TEST_CASE("traced: and_then lvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { + expected e(unexpect, 3); + auto r = e.transform([](traced&) { return traced(0); }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("traced: transform rvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { + expected e(std::in_place, 1); + auto r = e.transform([](traced&) {}); + REQUIRE(r.has_value()); +} + +TEST_CASE("traced: transform_error lvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { + expected e(unexpect, 42); + CHECK_THROWS_AS(e.value(), expt::bad_expected_access); +} + +TEST_CASE("traced: value() const rvalue throws", "[coverage][traced]") { + const expected e(unexpect, 42); + CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); +} + +// --- value_or / error_or rvalue with non-trivial types --- +TEST_CASE("traced: value_or rvalue returns value", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][init_list]") { + expected e(unexpect, 0); + e.emplace({7, 8}); + REQUIRE(e.has_value()); + CHECK(e->sum == 15); +} + +// --------------------------------------------------------------------------- +// expected: void specialization with non-trivial E +// --------------------------------------------------------------------------- + +TEST_CASE("void: unexpect_t constructor", "[coverage][traced]") { + expected e(unexpect, 42); + REQUIRE(!e.has_value()); + CHECK(e.error().val == 42); +} + +TEST_CASE("void: move construct error state", "[coverage][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", "[coverage][traced]") { + expected a(unexpect, 1); + expected b(unexpect, 2); + b = std::move(a); + CHECK(b.error().val == 1); +} + +TEST_CASE("void: value() rvalue throws", "[coverage][traced]") { + expected e(unexpect, 42); + CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); +} + +TEST_CASE("void: and_then lvalue error path", "[coverage][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", "[coverage][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", "[coverage][traced]") { + expected e; + auto r = e.or_else([](traced&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: or_else rvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { + expected e(unexpect, 5); + auto r = e.transform([]() { return traced(0); }); + REQUIRE(!r.has_value()); +} + +TEST_CASE("void: transform rvalue value path", "[coverage][traced]") { + expected e; + auto r = std::move(e).transform([]() { return traced(2); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: transform rvalue error path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { + expected e; + auto r = e.transform([]() {}); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: transform_error lvalue value path", "[coverage][traced]") { + expected e; + auto r = e.transform_error([](traced&) { return traced(0); }); + REQUIRE(r.has_value()); +} + +TEST_CASE("void: transform_error lvalue error path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][init_list]") { + expected e(unexpect, {1, 2, 3}); + REQUIRE(!e.has_value()); + CHECK(e.error().sum == 6); +} + +// --------------------------------------------------------------------------- +// Converting constructors: expected from expected +// --------------------------------------------------------------------------- + +TEST_CASE("converting: copy construct value state", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][converting]") { + expected a(unexpect, narrowed(7)); + expected b(std::move(a)); + REQUIRE(!b.has_value()); + CHECK(b.error().val == 7); +} + +// --------------------------------------------------------------------------- +// expected with non-trivial E: lvalue monadic overloads +// --------------------------------------------------------------------------- + +TEST_CASE("ref: and_then lvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { + int x = 1; + expected e(x); + auto r = e.transform([](int&) {}); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error lvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { + expected a(unexpect, 1); + expected b(unexpect, 1); + CHECK(a == b); +} + +TEST_CASE("ref: equality error vs value", "[coverage][traced]") { + int x = 1; + expected a(x); + expected b(unexpect, 0); + CHECK_FALSE(a == b); +} + +// --------------------------------------------------------------------------- +// expected: lvalue monadic paths with non-trivial T +// --------------------------------------------------------------------------- + +TEST_CASE("ref: and_then lvalue error path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { + int err = 0; + expected a(std::in_place, 1); + expected b(unexpect, err); + CHECK_FALSE(a == b); +} + +// --------------------------------------------------------------------------- +// expected: lvalue monadic paths +// --------------------------------------------------------------------------- + +TEST_CASE("ref: and_then lvalue value and error paths", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { + int e1 = 1, e2 = 1; + expected a(unexpect, e1); + expected b(unexpect, e2); + CHECK(a == b); +} + +// --------------------------------------------------------------------------- +// expected: lvalue monadic paths +// --------------------------------------------------------------------------- + +TEST_CASE("ref: and_then lvalue error path", "[coverage][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", "[coverage][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", "[coverage][traced]") { + expected e; + auto r = e.or_else([](int&) -> expected { return {}; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: or_else rvalue value path", "[coverage][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", "[coverage][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", "[coverage][traced]") { + expected e; + auto r = e.transform([]() { return 42; }); + REQUIRE(r.has_value()); + CHECK(*r == 42); +} + +TEST_CASE("ref: transform lvalue error path", "[coverage][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", "[coverage][traced]") { + expected e; + auto r = std::move(e).transform([]() { return 42; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform const rvalue value path", "[coverage][traced]") { + const expected e; + auto r = std::move(e).transform([]() { return 42; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform to void lvalue", "[coverage][traced]") { + expected e; + auto r = e.transform([]() {}); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error lvalue value path", "[coverage][traced]") { + expected e; + auto r = e.transform_error([](int&) { return 0; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: transform_error rvalue value path", "[coverage][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", "[coverage][traced]") { + const expected e; + auto r = std::move(e).transform_error([](int&) { return 0; }); + REQUIRE(r.has_value()); +} + +TEST_CASE("ref: value() lvalue throws", "[coverage][traced]") { + int err = 42; + expected e(unexpect, err); + CHECK_THROWS_AS(e.value(), expt::bad_expected_access); +} + +// --------------------------------------------------------------------------- +// Cross-type equality with eq_a / eq_b +// --------------------------------------------------------------------------- + +TEST_CASE("cross-eq: expected == expected", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][nested]") { + expected e(std::in_place, 42); + REQUIRE(e.has_value()); + CHECK(e->has_value()); + CHECK(**e == 42); +} + +TEST_CASE("nested: expected error state", "[coverage][nested]") { + expected e(unexpect, "err"); + REQUIRE(!e.has_value()); + CHECK(e.error() == "err"); +} + +TEST_CASE("nested: expected copy and move", "[coverage][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/testing/types.hpp b/tests/beman/expected/testing/types.hpp new file mode 100644 index 0000000..904160e --- /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 874ee0292f8136f210ccdd0da7c9cfd11e239b2a Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 5 Jun 2026 21:07:49 -0400 Subject: [PATCH 22/42] refactor: distribute coverage tests to their proper per-specialization test files Delete expected_coverage.test.cpp (239 tests, ~1900 lines) and move each test to the file matching its specialization: expected.test.cpp, expected_monadic.test.cpp, expected_void.test.cpp, expected_void_monadic.test.cpp, expected_ref.test.cpp, expected_ref_e.test.cpp, expected_ref_both.test.cpp, expected_void_ref_e.test.cpp, and bad_expected_access.test.cpp. The coverage tests were written to hit specific gcov-uncovered paths (rvalue/const-rvalue monadic overloads, throw paths, non-trivial type construction/assignment). Keeping them separate from the feature tests they exercise was confusing and made each file appear to have less coverage than it does. Tests now live next to the behavior they verify. --- tests/beman/expected/CMakeLists.txt | 1 - .../expected/bad_expected_access.test.cpp | 18 + tests/beman/expected/expected.test.cpp | 407 ++++ .../beman/expected/expected_coverage.test.cpp | 1910 ----------------- .../beman/expected/expected_monadic.test.cpp | 252 +++ tests/beman/expected/expected_ref.test.cpp | 313 +++ .../beman/expected/expected_ref_both.test.cpp | 204 ++ tests/beman/expected/expected_ref_e.test.cpp | 195 ++ tests/beman/expected/expected_void.test.cpp | 79 + .../expected/expected_void_monadic.test.cpp | 227 ++ .../expected/expected_void_ref_e.test.cpp | 225 ++ 11 files changed, 1920 insertions(+), 1911 deletions(-) delete mode 100644 tests/beman/expected/expected_coverage.test.cpp diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 971f2be..da8263e 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -19,7 +19,6 @@ target_sources( expected_ref_e.test.cpp expected_ref_both.test.cpp expected_void_ref_e.test.cpp - expected_coverage.test.cpp ) target_link_libraries( beman.expected.tests.expected 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..9a76dde 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 @@ -799,3 +801,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_coverage.test.cpp b/tests/beman/expected/expected_coverage.test.cpp deleted file mode 100644 index c038cb3..0000000 --- a/tests/beman/expected/expected_coverage.test.cpp +++ /dev/null @@ -1,1910 +0,0 @@ -// beman/expected/expected_coverage.test.cpp -*-C++-*- -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// Tests targeting lines uncovered by gcov: rvalue/const-rvalue monadic -// overloads, value()/error() throw paths, rvalue value_or/error_or, -// constructor/assignment error-path branches, and cross-type equality. - -#include -#include - -#include - -#include "testing/types.hpp" - -#include -#include -#include -#include - -namespace expt = beman::expected; -using expt::expected; -using expt::unexpect; -using expt::unexpected; -using namespace beman::expected::testing; - -// ============================================================================= -// expected — primary template coverage gaps -// ============================================================================= - -// --- Monadic: and_then rvalue/const-rvalue on ERROR state (short-circuit) --- -TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { - 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("coverage: and_then const rvalue on error short-circuits", "[coverage]") { - 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { - 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("coverage: or_else const rvalue on value short-circuits", "[coverage]") { - 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("coverage: transform rvalue on error short-circuits", "[coverage]") { - 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("coverage: transform const rvalue on error short-circuits", "[coverage]") { - 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { - expected e("val"); - auto r = std::move(e).transform_error([](int v) { return v * 2; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { - 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("coverage: transform const rvalue on value calls F", "[coverage]") { - 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { - 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); -} - -// --- value() throw from rvalue and const rvalue --- -TEST_CASE("coverage: value() rvalue throws with moved error", "[coverage]") { - expected e(unexpect, "rval-err"); - CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); -} - -TEST_CASE("coverage: value() const rvalue throws", "[coverage]") { - const expected e(unexpect, "crval-err"); - CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); -} - -// --- error_or rvalue overloads --- -TEST_CASE("coverage: error_or rvalue uses default when has value", "[coverage]") { - expected e(42); - std::string s = std::move(e).error_or("fallback"); - CHECK(s == "fallback"); -} - -// --- unexpect_t constructor with initializer_list --- -TEST_CASE("coverage: unexpect_t constructor with initializer_list", "[coverage]") { - 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("coverage: in_place_t constructor with initializer_list", "[coverage]") { - 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("coverage: emplace on error state transitions to value", "[coverage]") { - expected e(unexpect, "err"); - e.emplace(42); - REQUIRE(e.has_value()); - CHECK(*e == 42); -} - -// --- Cross-type equality (expected vs expected) --- -TEST_CASE("coverage: cross-type equality different error types", "[coverage]") { - expected a(unexpect, 1); - expected b(unexpect, 1L); - CHECK(a == b); -} - -TEST_CASE("coverage: cross-type equality error vs value", "[coverage]") { - expected a(42); - expected b(unexpect, 0L); - CHECK_FALSE(a == b); -} - -// ============================================================================= -// expected — void specialization coverage gaps -// ============================================================================= - -// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- -TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { - 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("coverage: and_then const rvalue on error short-circuits", "[coverage]") { - 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("coverage: and_then rvalue on value calls F", "[coverage]") { - 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("coverage: and_then const rvalue on value calls F", "[coverage]") { - 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { - 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("coverage: or_else const rvalue on value short-circuits", "[coverage]") { - const expected e; - auto r = std::move(e).or_else([](int) -> expected { return {}; }); - REQUIRE(r.has_value()); -} - -// --- Monadic: transform rvalue/const-rvalue on ERROR state --- -TEST_CASE("coverage: transform rvalue on error short-circuits", "[coverage]") { - expected e(unexpect, "err"); - auto r = std::move(e).transform([]() { return 1; }); - REQUIRE(!r.has_value()); - CHECK(r.error() == "err"); -} - -TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { - 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("coverage: transform rvalue on value calls F", "[coverage]") { - expected e; - auto r = std::move(e).transform([]() { return 42; }); - REQUIRE(r.has_value()); - CHECK(*r == 42); -} - -TEST_CASE("coverage: transform const rvalue on value calls F", "[coverage]") { - 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { - expected e; - auto r = std::move(e).transform_error([](int v) { return v * 2; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("coverage: transform_error rvalue on error calls F", "[coverage]") { - 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { - 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); -} - -// --- value() throw paths for void --- -TEST_CASE("coverage: value() rvalue throws", "[coverage]") { - expected e(unexpect, 42); - CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); -} - -TEST_CASE("coverage: value() const rvalue throws", "[coverage]") { - const expected e(unexpect, 42); - CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); -} - -// --- void unexpect_t with init-list --- -TEST_CASE("coverage: unexpect_t constructor with initializer_list", "[coverage]") { - 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("coverage: move-assign error to value state", "[coverage]") { - expected a; - expected b(unexpect, "e"); - a = std::move(b); - REQUIRE(!a.has_value()); - CHECK(a.error() == "e"); -} - -TEST_CASE("coverage: move-assign value to error state", "[coverage]") { - expected a(unexpect, "e"); - expected b; - a = std::move(b); - REQUIRE(a.has_value()); -} - -// --- void cross-type equality --- -TEST_CASE("coverage: cross-type equality with expected", "[coverage]") { - expected a(unexpect, 1); - expected b(unexpect, 1L); - CHECK(a == b); - expected c; - CHECK_FALSE(a == c); -} - -// ============================================================================= -// expected — reference specialization coverage gaps -// ============================================================================= - -// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- -TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { - 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("coverage: and_then const rvalue on error short-circuits", "[coverage]") { - 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("coverage: and_then const rvalue on value calls F", "[coverage]") { - 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { - 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("coverage: or_else const rvalue on value short-circuits", "[coverage]") { - 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("coverage: or_else rvalue on error calls F", "[coverage]") { - 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("coverage: transform rvalue on error short-circuits", "[coverage]") { - 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("coverage: transform const rvalue on error short-circuits", "[coverage]") { - 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("coverage: transform const rvalue on value calls F", "[coverage]") { - 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { - 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("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { - 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { - 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); -} - -// --- value() throw paths --- -TEST_CASE("coverage: value() throws on error state", "[coverage]") { - expected e(unexpect, 42); - CHECK_THROWS_AS(e.value(), expt::bad_expected_access); -} - -// --- Cross-type equality --- -TEST_CASE("coverage: equality error vs value", "[coverage]") { - int x = 1; - expected a(x); - expected b(unexpect, 0); - CHECK_FALSE(a == b); -} - -// ============================================================================= -// expected — error-reference specialization coverage gaps -// ============================================================================= - -// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- -TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { - 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("coverage: and_then const rvalue on error short-circuits", "[coverage]") { - 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { - 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("coverage: or_else const rvalue on value short-circuits", "[coverage]") { - 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("coverage: transform rvalue on error short-circuits", "[coverage]") { - int err = 3; - expected e(unexpect, err); - auto r = std::move(e).transform([](int v) { return v * 2; }); - REQUIRE(!r.has_value()); -} - -TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { - 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { - expected e(42); - auto r = std::move(e).transform_error([](int& v) { return v * 2; }); - REQUIRE(r.has_value()); - CHECK(*r == 42); -} - -TEST_CASE("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { - const expected e(42); - auto r = std::move(e).transform_error([](int& v) { return v * 2; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("coverage: transform_error rvalue on error calls F", "[coverage]") { - 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { - 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("coverage: value assignment on value state", "[coverage]") { - expected e(10); - e = 20; - REQUIRE(e.has_value()); - CHECK(*e == 20); -} - -// --- Cross-type equality --- -TEST_CASE("coverage: equality error vs value", "[coverage]") { - int err = 0; - expected a(1); - expected b(unexpect, err); - CHECK_FALSE(a == b); -} - -// ============================================================================= -// expected — both-reference specialization coverage gaps -// ============================================================================= - -// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- -TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { - 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("coverage: and_then const rvalue on error short-circuits", "[coverage]") { - 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { - 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("coverage: or_else const rvalue on value short-circuits", "[coverage]") { - 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("coverage: transform rvalue on error short-circuits", "[coverage]") { - int err = 3; - expected e(unexpect, err); - auto r = std::move(e).transform([](int& v) { return v * 2; }); - REQUIRE(!r.has_value()); -} - -TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { - 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { - 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("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { - 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("coverage: transform_error rvalue on error calls F", "[coverage]") { - 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { - 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("coverage: equality error vs value", "[coverage]") { - int x = 1, err = 0; - expected a(x); - expected b(unexpect, err); - CHECK_FALSE(a == b); -} - -// ============================================================================= -// expected — void+error-reference specialization coverage gaps -// ============================================================================= - -// --- Monadic: and_then rvalue/const-rvalue on ERROR state --- -TEST_CASE("coverage: and_then rvalue on error short-circuits", "[coverage]") { - int err = 5; - expected e(unexpect, err); - auto r = std::move(e).and_then([]() -> expected { return {}; }); - REQUIRE(!r.has_value()); -} - -TEST_CASE("coverage: and_then const rvalue on error short-circuits", "[coverage]") { - 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("coverage: and_then rvalue on value calls F", "[coverage]") { - 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("coverage: and_then const rvalue on value calls F", "[coverage]") { - 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("coverage: or_else rvalue on value short-circuits", "[coverage]") { - expected e; - auto r = std::move(e).or_else([](int&) -> expected { return {}; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("coverage: or_else const rvalue on value short-circuits", "[coverage]") { - const expected e; - auto r = std::move(e).or_else([](int&) -> expected { return {}; }); - REQUIRE(r.has_value()); -} - -// --- Monadic: transform rvalue/const-rvalue --- -TEST_CASE("coverage: transform rvalue on error short-circuits", "[coverage]") { - int err = 3; - expected e(unexpect, err); - auto r = std::move(e).transform([]() { return 1; }); - REQUIRE(!r.has_value()); -} - -TEST_CASE("coverage: transform const rvalue on error short-circuits", "[coverage]") { - int err = 3; - const expected e(unexpect, err); - auto r = std::move(e).transform([]() { return 1; }); - REQUIRE(!r.has_value()); -} - -TEST_CASE("coverage: transform rvalue on value calls F", "[coverage]") { - expected e; - auto r = std::move(e).transform([]() { return 42; }); - REQUIRE(r.has_value()); - CHECK(*r == 42); -} - -TEST_CASE("coverage: transform const rvalue on value calls F", "[coverage]") { - 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("coverage: transform_error rvalue on value short-circuits", "[coverage]") { - expected e; - auto r = std::move(e).transform_error([](int& v) { return v * 2; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("coverage: transform_error const rvalue on value short-circuits", "[coverage]") { - const expected e; - auto r = std::move(e).transform_error([](int& v) { return v * 2; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("coverage: transform_error rvalue on error calls F", "[coverage]") { - 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("coverage: transform_error const rvalue on error calls F", "[coverage]") { - 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("coverage: value() throws on error state", "[coverage]") { - int err = 42; - expected e(unexpect, err); - CHECK_THROWS_AS(e.value(), expt::bad_expected_access); -} - -TEST_CASE("coverage: value() rvalue throws on error state", "[coverage]") { - int err = 42; - expected e(unexpect, err); - CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); -} - -// --- Cross-type equality --- -TEST_CASE("coverage: equality error vs value", "[coverage]") { - int err = 0; - expected a; - expected b(unexpect, err); - CHECK_FALSE(a == b); -} - -// ============================================================================= -// bad_expected_access coverage -// ============================================================================= - -TEST_CASE("coverage: bad_expected_access move constructor", "[coverage]") { - expt::bad_expected_access orig("test error"); - expt::bad_expected_access moved(std::move(orig)); - CHECK(moved.error() == "test error"); -} - -TEST_CASE("coverage: bad_expected_access rvalue error accessor", "[coverage]") { - expt::bad_expected_access e("val"); - std::string s = std::move(e).error(); - CHECK(s == "val"); -} - -TEST_CASE("coverage: bad_expected_access const rvalue error accessor", "[coverage]") { - const expt::bad_expected_access e("val"); - std::string s = std::move(e).error(); - CHECK(s == "val"); -} - -// ============================================================================= -// Tests using beman::expected::testing types for non-trivial path coverage -// ============================================================================= - -// --------------------------------------------------------------------------- -// expected: non-trivial destructor, copy/move ctor, assignment -// --------------------------------------------------------------------------- - -TEST_CASE("traced: default construct (value state)", "[coverage][traced]") { - expected e(std::in_place, 42); - REQUIRE(e.has_value()); - CHECK(e->val == 42); -} - -TEST_CASE("traced: error construct", "[coverage][traced]") { - expected e(unexpect, 7); - REQUIRE(!e.has_value()); - CHECK(e.error().val == 7); -} - -TEST_CASE("traced: copy construct value state", "[coverage][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", "[coverage][traced]") { - expected a(unexpect, 20); - expected b(a); - REQUIRE(!b.has_value()); - CHECK(b.error().val == 20); -} - -TEST_CASE("traced: move construct value state", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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)", "[coverage][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)", "[coverage][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", "[coverage][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)", "[coverage][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)", "[coverage][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&", "[coverage][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&&", "[coverage][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", "[coverage][traced]") { - expected e(unexpect, 1); - e.emplace(42); - REQUIRE(e.has_value()); - CHECK(e->val == 42); -} - -TEST_CASE("traced: destructor runs for value state", "[coverage][traced]") { - { - expected e(std::in_place, 1); - } -} - -TEST_CASE("traced: destructor runs for error state", "[coverage][traced]") { - { - expected e(unexpect, 1); - } -} - -// --- traced monadic: all 4 overloads × value + error paths --- - -TEST_CASE("traced: and_then lvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { - expected e(unexpect, 3); - auto r = e.transform([](traced&) { return traced(0); }); - REQUIRE(!r.has_value()); -} - -TEST_CASE("traced: transform rvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { - expected e(std::in_place, 1); - auto r = e.transform([](traced&) {}); - REQUIRE(r.has_value()); -} - -TEST_CASE("traced: transform_error lvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { - expected e(unexpect, 42); - CHECK_THROWS_AS(e.value(), expt::bad_expected_access); -} - -TEST_CASE("traced: value() const rvalue throws", "[coverage][traced]") { - const expected e(unexpect, 42); - CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); -} - -// --- value_or / error_or rvalue with non-trivial types --- -TEST_CASE("traced: value_or rvalue returns value", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][init_list]") { - expected e(unexpect, 0); - e.emplace({7, 8}); - REQUIRE(e.has_value()); - CHECK(e->sum == 15); -} - -// --------------------------------------------------------------------------- -// expected: void specialization with non-trivial E -// --------------------------------------------------------------------------- - -TEST_CASE("void: unexpect_t constructor", "[coverage][traced]") { - expected e(unexpect, 42); - REQUIRE(!e.has_value()); - CHECK(e.error().val == 42); -} - -TEST_CASE("void: move construct error state", "[coverage][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", "[coverage][traced]") { - expected a(unexpect, 1); - expected b(unexpect, 2); - b = std::move(a); - CHECK(b.error().val == 1); -} - -TEST_CASE("void: value() rvalue throws", "[coverage][traced]") { - expected e(unexpect, 42); - CHECK_THROWS_AS(std::move(e).value(), expt::bad_expected_access); -} - -TEST_CASE("void: and_then lvalue error path", "[coverage][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", "[coverage][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", "[coverage][traced]") { - expected e; - auto r = e.or_else([](traced&) -> expected { return {}; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("void: or_else rvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { - expected e(unexpect, 5); - auto r = e.transform([]() { return traced(0); }); - REQUIRE(!r.has_value()); -} - -TEST_CASE("void: transform rvalue value path", "[coverage][traced]") { - expected e; - auto r = std::move(e).transform([]() { return traced(2); }); - REQUIRE(r.has_value()); -} - -TEST_CASE("void: transform rvalue error path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { - expected e; - auto r = e.transform([]() {}); - REQUIRE(r.has_value()); -} - -TEST_CASE("void: transform_error lvalue value path", "[coverage][traced]") { - expected e; - auto r = e.transform_error([](traced&) { return traced(0); }); - REQUIRE(r.has_value()); -} - -TEST_CASE("void: transform_error lvalue error path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][init_list]") { - expected e(unexpect, {1, 2, 3}); - REQUIRE(!e.has_value()); - CHECK(e.error().sum == 6); -} - -// --------------------------------------------------------------------------- -// Converting constructors: expected from expected -// --------------------------------------------------------------------------- - -TEST_CASE("converting: copy construct value state", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][converting]") { - expected a(unexpect, narrowed(7)); - expected b(std::move(a)); - REQUIRE(!b.has_value()); - CHECK(b.error().val == 7); -} - -// --------------------------------------------------------------------------- -// expected with non-trivial E: lvalue monadic overloads -// --------------------------------------------------------------------------- - -TEST_CASE("ref: and_then lvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { - int x = 1; - expected e(x); - auto r = e.transform([](int&) {}); - REQUIRE(r.has_value()); -} - -TEST_CASE("ref: transform_error lvalue value path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { - expected a(unexpect, 1); - expected b(unexpect, 1); - CHECK(a == b); -} - -TEST_CASE("ref: equality error vs value", "[coverage][traced]") { - int x = 1; - expected a(x); - expected b(unexpect, 0); - CHECK_FALSE(a == b); -} - -// --------------------------------------------------------------------------- -// expected: lvalue monadic paths with non-trivial T -// --------------------------------------------------------------------------- - -TEST_CASE("ref: and_then lvalue error path", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { - int err = 0; - expected a(std::in_place, 1); - expected b(unexpect, err); - CHECK_FALSE(a == b); -} - -// --------------------------------------------------------------------------- -// expected: lvalue monadic paths -// --------------------------------------------------------------------------- - -TEST_CASE("ref: and_then lvalue value and error paths", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][traced]") { - int e1 = 1, e2 = 1; - expected a(unexpect, e1); - expected b(unexpect, e2); - CHECK(a == b); -} - -// --------------------------------------------------------------------------- -// expected: lvalue monadic paths -// --------------------------------------------------------------------------- - -TEST_CASE("ref: and_then lvalue error path", "[coverage][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", "[coverage][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", "[coverage][traced]") { - expected e; - auto r = e.or_else([](int&) -> expected { return {}; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("ref: or_else rvalue value path", "[coverage][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", "[coverage][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", "[coverage][traced]") { - expected e; - auto r = e.transform([]() { return 42; }); - REQUIRE(r.has_value()); - CHECK(*r == 42); -} - -TEST_CASE("ref: transform lvalue error path", "[coverage][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", "[coverage][traced]") { - expected e; - auto r = std::move(e).transform([]() { return 42; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("ref: transform const rvalue value path", "[coverage][traced]") { - const expected e; - auto r = std::move(e).transform([]() { return 42; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("ref: transform to void lvalue", "[coverage][traced]") { - expected e; - auto r = e.transform([]() {}); - REQUIRE(r.has_value()); -} - -TEST_CASE("ref: transform_error lvalue value path", "[coverage][traced]") { - expected e; - auto r = e.transform_error([](int&) { return 0; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("ref: transform_error rvalue value path", "[coverage][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", "[coverage][traced]") { - const expected e; - auto r = std::move(e).transform_error([](int&) { return 0; }); - REQUIRE(r.has_value()); -} - -TEST_CASE("ref: value() lvalue throws", "[coverage][traced]") { - int err = 42; - expected e(unexpect, err); - CHECK_THROWS_AS(e.value(), expt::bad_expected_access); -} - -// --------------------------------------------------------------------------- -// Cross-type equality with eq_a / eq_b -// --------------------------------------------------------------------------- - -TEST_CASE("cross-eq: expected == expected", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][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", "[coverage][nested]") { - expected e(std::in_place, 42); - REQUIRE(e.has_value()); - CHECK(e->has_value()); - CHECK(**e == 42); -} - -TEST_CASE("nested: expected error state", "[coverage][nested]") { - expected e(unexpect, "err"); - REQUIRE(!e.has_value()); - CHECK(e.error() == "err"); -} - -TEST_CASE("nested: expected copy and move", "[coverage][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_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 index 2c9058b..5580151 100644 --- a/tests/beman/expected/expected_ref.test.cpp +++ b/tests/beman/expected/expected_ref.test.cpp @@ -6,6 +6,8 @@ #include +#include "testing/types.hpp" + #include #include @@ -475,3 +477,314 @@ TEST_CASE("expected: rvalue transform_error moves error", "[expected_ref]") 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); +} + +// --- 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); +} + +// --- 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_both.test.cpp b/tests/beman/expected/expected_ref_both.test.cpp index 93bdd5e..3f58da9 100644 --- a/tests/beman/expected/expected_ref_both.test.cpp +++ b/tests/beman/expected/expected_ref_both.test.cpp @@ -6,6 +6,8 @@ #include +#include "testing/types.hpp" + #include #include @@ -480,3 +482,205 @@ TEST_CASE("expected: lvalue error reference compiles and is addressable", 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_e.test.cpp b/tests/beman/expected/expected_ref_e.test.cpp index 2a7a18d..d5331c0 100644 --- a/tests/beman/expected/expected_ref_e.test.cpp +++ b/tests/beman/expected/expected_ref_e.test.cpp @@ -6,6 +6,8 @@ #include +#include "testing/types.hpp" + #include #include @@ -400,3 +402,196 @@ TEST_CASE("expected: lvalue error reference compiles and is addressable", 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_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_monadic.test.cpp b/tests/beman/expected/expected_void_monadic.test.cpp index d1ac10b..970736d 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; @@ -193,3 +195,228 @@ 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()); +} + +// --- 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/expected_void_ref_e.test.cpp b/tests/beman/expected/expected_void_ref_e.test.cpp index 1e72bb6..1057dfe 100644 --- a/tests/beman/expected/expected_void_ref_e.test.cpp +++ b/tests/beman/expected/expected_void_ref_e.test.cpp @@ -4,6 +4,8 @@ #include +#include "testing/types.hpp" + #include #include @@ -409,3 +411,226 @@ 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); +} From afc43449e810b54beb8255c7d999a695421dd873 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 5 Jun 2026 21:09:09 -0400 Subject: [PATCH 23/42] docs: add human and LLM review guides and feature them in README --- README.md | 6 ++++++ docs/human-design-review-guide.md | 21 +++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8f43eac..a0b293f 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,12 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception **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. diff --git a/docs/human-design-review-guide.md b/docs/human-design-review-guide.md index f47d23d..e3baf4a 100644 --- a/docs/human-design-review-guide.md +++ b/docs/human-design-review-guide.md @@ -225,12 +225,21 @@ Everything is in `expected.hpp`. The `unexpected.hpp` and `bad_expected_access.h --- -## How to Structure Your Review +## How to Approach Your Review -For each decision above, state: +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: -1. **Agree / Disagree / Need More Information** -2. **If disagree:** What alternative would you propose, and what is its cost? -3. **If agree:** Is the implementation faithful to the stated design intent? +- **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? -Do not review this code as if it were a typical pull request. Review it as proposed standard library wording rendered into C++. The question is not "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." +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. From 047ac66fbefa237e9878e90506ae2f5700bdd511 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 6 Jun 2026 17:04:44 -0400 Subject: [PATCH 24/42] fix: replace = delete("message") with = delete; for C++23 compatibility The P2573 deleted-functions-with-messages syntax (= delete("reason")) is a C++26 feature not available in GCC-13 with -std=c++23. Remove the 21 message strings across the four reference specializations (expected, expected, expected, expected), keeping plain = delete; so operations remain deleted. Type safety is unchanged; only the custom diagnostic text is sacrificed. GCC-13 users will see the generic "use of deleted function" error instead of the custom explanation. --- include/beman/expected/expected.hpp | 58 +++++++++++------------------ 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 24f8284..518f6ad 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -2028,7 +2028,7 @@ class expected { // Constructors // ------------------------------------------------------------------------- - expected() = delete ("expected: no default constructor; T& cannot be null"); + expected() = delete; // Copy constructor (trivial path) constexpr expected(const expected&) @@ -2062,8 +2062,7 @@ class expected { // Deleted: no in-place value constructor — T& cannot be constructed in-place template - constexpr expected(std::in_place_t, Args&&...) = - delete ("expected: no in_place value constructor; use expected(lvalue_ref) to bind T&"); + constexpr expected(std::in_place_t, Args&&...) = delete; // Value constructor — takes U that can bind to T& template @@ -2079,7 +2078,7 @@ class expected { // Deleted: binding a temporary to T& creates a dangling reference template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(U&&) = delete ("expected: binding a temporary to T& creates a dangling reference"); + constexpr expected(U&&) = delete; // Converting constructor from expected (copy) template @@ -2790,22 +2789,18 @@ class expected { // Deleted: argument cannot bind to E& (covers rvalue, const lvalue, and temp-creating cases) template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = - delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + constexpr expected(unexpect_t, G&&) = delete; template requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = - delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + constexpr expected(unexpect_t, G&&) = delete; // Deleted: no constructor from unexpected (would bind E& to temporary storage in unexpected) template - constexpr expected(const unexpected&) = - delete ("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); + constexpr expected(const unexpected&) = delete; template - constexpr expected(unexpected&&) = - delete ("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); + constexpr expected(unexpected&&) = delete; // Converting constructor from expected (copy) — mirrors expected's from expected template @@ -2926,12 +2921,10 @@ class expected { // Deleted: no assignment from unexpected (would rebind E& to temporary storage) template - constexpr expected& operator=(const unexpected&) = - delete ("expected: no assignment from unexpected; copy-assign from another expected"); + constexpr expected& operator=(const unexpected&) = delete; template - constexpr expected& operator=(unexpected&&) = - delete ("expected: no assignment from unexpected; copy-assign from another expected"); + constexpr expected& operator=(unexpected&&) = delete; // emplace — construct T in-place (nothrow required for exception safety when T is destroyed) template @@ -3414,7 +3407,7 @@ class expected { // Constructors // ------------------------------------------------------------------------- - expected() = delete ("expected: no default constructor; T& cannot be null"); + expected() = delete; // Copy/move constructors — trivial (union holds only pointers + bool has_val_) constexpr expected(const expected&) = default; @@ -3422,8 +3415,7 @@ class expected { // Deleted: no in-place value constructor — T& cannot be constructed in-place template - constexpr expected(std::in_place_t, Args&&...) = - delete ("expected: no in_place value constructor; use expected(lvalue_ref) to bind T&"); + constexpr expected(std::in_place_t, Args&&...) = delete; // Value constructor — binds T& from lvalue (dangling prevention via deleted rvalue overload) template @@ -3438,7 +3430,7 @@ class expected { // Deleted: binding a temporary to T& creates a dangling reference template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(U&&) = delete ("expected: binding a temporary to T& creates a dangling reference"); + constexpr expected(U&&) = delete; // Error constructor — binds E& (no temporary allowed) template @@ -3451,22 +3443,18 @@ class expected { // Deleted: argument cannot bind to E& (covers rvalue, const lvalue, and temp-creating cases) template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = - delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + constexpr expected(unexpect_t, G&&) = delete; template requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = - delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + constexpr expected(unexpect_t, G&&) = delete; // Deleted: no constructor from unexpected (would bind E& to temporary storage in unexpected) template - constexpr expected(const unexpected&) = - delete ("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); + constexpr expected(const unexpected&) = delete; template - constexpr expected(unexpected&&) = - delete ("expected: no constructor from unexpected; use (unexpect, lvalue_ref)"); + constexpr expected(unexpected&&) = delete; // Converting constructor from expected (copy) template @@ -3528,12 +3516,10 @@ class expected { // Deleted: no assignment from unexpected template - constexpr expected& operator=(const unexpected&) = - delete ("expected: no assignment from unexpected; copy-assign from another expected"); + constexpr expected& operator=(const unexpected&) = delete; template - constexpr expected& operator=(unexpected&&) = - delete ("expected: no assignment from unexpected; copy-assign from another expected"); + constexpr expected& operator=(unexpected&&) = delete; // emplace — rebind T& (pointer transition is trivial) template @@ -3981,13 +3967,11 @@ class expected { // Deleted: argument cannot bind to E& (covers rvalue, const lvalue, and temp-creating cases) template requires(detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = - delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + constexpr expected(unexpect_t, G&&) = delete; template requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = - delete ("expected: argument type cannot bind to non-const E&; provide a mutable lvalue of type E"); + constexpr expected(unexpect_t, G&&) = delete; // Converting constructor from expected template @@ -4087,7 +4071,7 @@ class expected { // ------------------------------------------------------------------------- // Deleted: value_or is not available for void expected template - constexpr void value_or(U&&) const = delete ("expected: no value_or for void specialization"); + constexpr void value_or(U&&) const = delete; // Monadic operations — void value + E& error // ------------------------------------------------------------------------- From 025883fc8d8d6ca45a7aab73cbeb60a4bde1e965 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Sat, 6 Jun 2026 18:23:58 -0400 Subject: [PATCH 25/42] fix: extend negative-compile test patterns for GCC-13 compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 11 negative compile test PASS_REGULAR_EXPRESSION patterns that matched custom = delete("message") strings now also match "use of deleted function" — the output GCC-13 produces for plain = delete;. The original patterns are retained so tests still pass on C++26-capable compilers. No test behaviour changes on the current GCC-16/C++26 build. --- tests/beman/expected/CMakeLists.txt | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index da8263e..17bc740 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -65,7 +65,7 @@ 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 - "no default constructor" + "no default constructor|use of deleted function" ) # Note: expected_e_ref_fail removed — expected is now valid via expected add_fail_test(expected_t_array_fail expected_t_array_fail.cpp @@ -118,32 +118,32 @@ add_fail_test(expected_unexpected_value_type_fail expected_unexpected_value_type # 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|no matching function" + "binding a temporary to T& creates a dangling reference|use of deleted function|no matching function" ) add_fail_test(expected_ref_no_default_fail expected_ref_no_default_fail.cpp - "no default constructor" + "no default constructor|use of deleted function" ) add_fail_test(expected_ref_inplace_value_fail expected_ref_inplace_value_fail.cpp - "no in_place value constructor" + "no in_place value constructor|use of deleted function" ) # 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&" + "cannot bind to non-const E&|use of deleted function" ) add_fail_test(expected_ref_e_const_lvalue_fail expected_ref_e_const_lvalue_fail.cpp - "cannot bind to non-const E&" + "cannot bind to non-const E&|use of deleted function" ) # 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" + "no constructor from unexpected|use of deleted function" ) add_fail_test(expected_ref_e_assign_unexpected_fail expected_ref_e_assign_unexpected_fail.cpp - "no assignment from unexpected" + "no assignment from unexpected|use of deleted function" ) # Step 7/8 parity — expected or_else value_type mandate (added in Step 8 review) @@ -153,24 +153,24 @@ add_fail_test(expected_ref_or_else_wrong_value_type_fail expected_ref_or_else_wr # 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|no matching function" + "binding a temporary to T& creates a dangling reference|use of deleted function|no matching function" ) add_fail_test(expected_ref_both_temporary_err_fail expected_ref_both_temporary_err_fail.cpp - "cannot bind to non-const E&" + "cannot bind to non-const E&|use of deleted function" ) add_fail_test(expected_ref_both_no_default_fail expected_ref_both_no_default_fail.cpp - "no default constructor" + "no default constructor|use of deleted function" ) # 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" + "no constructor from unexpected|use of deleted function" ) add_fail_test(expected_ref_both_assign_unexpected_fail expected_ref_both_assign_unexpected_fail.cpp - "no assignment from unexpected" + "no assignment from unexpected|use of deleted function" ) # Step 9 — expected: or_else value_type mandate @@ -262,7 +262,7 @@ add_fail_test(expected_ref_both_t_unexpected_fail ) add_fail_test(expected_ref_both_inplace_value_fail expected_ref_both_inplace_value_fail.cpp - "no in_place value constructor" + "no in_place value constructor|use of deleted function" ) # Step 9 — expected monadic mandates @@ -278,15 +278,15 @@ add_fail_test(expected_ref_both_transform_error_ref_result_fail # 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&" + "cannot bind to non-const E&|use of deleted function" ) add_fail_test(expected_void_ref_e_const_lvalue_fail expected_void_ref_e_const_lvalue_fail.cpp - "cannot bind to non-const E&" + "cannot bind to non-const E&|use of deleted function" ) 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" + "no value_or for void|use of deleted function" ) # ============================================================================= From 680b4593c2491ea1f0b5066809aaee00705c0427 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Wed, 17 Jun 2026 22:34:25 -0400 Subject: [PATCH 26/42] Update from copier --- .copier-answers.yml | 2 +- .github/workflows/ci_tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 06063e0..dd385cd 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Standard answers and internal state managed by Copier -_commit: 2.0.0-322-gc3c230c +_commit: v2.4.0-40-g8bf7fea _src_path: https://github.com/steve-downey/exemplar.git description: Expected Over References maintainer: steve-downey diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 783d0ae..b02562e 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -9,7 +9,7 @@ on: pull_request: workflow_dispatch: schedule: - - cron: '15 12 * * 2' + - cron: '41 17 * * 0' concurrency: group: ${{format('{0}:{1}', github.repository, github.ref)}} From aff8f1722acf53f10effe3e686b20d162e8236b0 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Thu, 18 Jun 2026 11:18:09 -0400 Subject: [PATCH 27/42] fix: extend negative-compile test patterns for Clang-22 compatibility Clang-22 changed diagnostic wording for deleted function calls: - "call to deleted constructor/member function" instead of "use of deleted function" - "no matching constructor" instead of "no matching function" - "invokes a deleted function" for implicit conversions - "selected deleted operator" for deleted assignment operators --- tests/beman/expected/CMakeLists.txt | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 17bc740..6722480 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -65,7 +65,7 @@ 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 - "no default constructor|use of deleted function" + "no default constructor|use of deleted function|call to deleted" ) # Note: expected_e_ref_fail removed — expected is now valid via expected add_fail_test(expected_t_array_fail expected_t_array_fail.cpp @@ -108,7 +108,7 @@ add_fail_test(void_or_else_wrong_value_type_fail void_or_else_wrong_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 - "no matching function" + "no matching function|no matching constructor" ) # Fix 4 — Mandates: T must not be a specialization of unexpected @@ -118,32 +118,32 @@ add_fail_test(expected_unexpected_value_type_fail expected_unexpected_value_type # 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|no matching function" + "binding a temporary to T& creates a dangling reference|use of deleted function|call to deleted|no matching function" ) add_fail_test(expected_ref_no_default_fail expected_ref_no_default_fail.cpp - "no default constructor|use of deleted function" + "no default constructor|use of deleted function|call to deleted" ) add_fail_test(expected_ref_inplace_value_fail expected_ref_inplace_value_fail.cpp - "no in_place value constructor|use of deleted function" + "no in_place value constructor|use of deleted function|call to deleted" ) # 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" + "cannot bind to non-const E&|use of deleted function|call to deleted" ) 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" + "cannot bind to non-const E&|use of deleted function|call to deleted" ) # 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" + "no constructor from unexpected|use of deleted function|call to deleted|invokes a deleted function" ) add_fail_test(expected_ref_e_assign_unexpected_fail expected_ref_e_assign_unexpected_fail.cpp - "no assignment from unexpected|use of deleted function" + "no assignment from unexpected|use of deleted function|call to deleted|selected deleted operator" ) # Step 7/8 parity — expected or_else value_type mandate (added in Step 8 review) @@ -153,24 +153,24 @@ add_fail_test(expected_ref_or_else_wrong_value_type_fail expected_ref_or_else_wr # 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|no matching function" + "binding a temporary to T& creates a dangling reference|use of deleted function|call to deleted|no matching function" ) 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" + "cannot bind to non-const E&|use of deleted function|call to deleted" ) add_fail_test(expected_ref_both_no_default_fail expected_ref_both_no_default_fail.cpp - "no default constructor|use of deleted function" + "no default constructor|use of deleted function|call to deleted" ) # 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" + "no constructor from unexpected|use of deleted function|call to deleted|invokes a deleted function" ) add_fail_test(expected_ref_both_assign_unexpected_fail expected_ref_both_assign_unexpected_fail.cpp - "no assignment from unexpected|use of deleted function" + "no assignment from unexpected|use of deleted function|call to deleted|selected deleted operator" ) # Step 9 — expected: or_else value_type mandate @@ -262,7 +262,7 @@ add_fail_test(expected_ref_both_t_unexpected_fail ) 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" + "no in_place value constructor|use of deleted function|call to deleted" ) # Step 9 — expected monadic mandates @@ -278,15 +278,15 @@ add_fail_test(expected_ref_both_transform_error_ref_result_fail # 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" + "cannot bind to non-const E&|use of deleted function|call to deleted" ) 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" + "cannot bind to non-const E&|use of deleted function|call to deleted" ) 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" + "no value_or for void|use of deleted function|call to deleted" ) # ============================================================================= From ba9a8b60157aaee3175c7048a9ccfda683a7f027 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Thu, 18 Jun 2026 13:45:24 -0400 Subject: [PATCH 28/42] fix: pre-landing cleanup for expected-over-references PR - Add |attempting to reference to 16 negative-compile test patterns so MSVC (C2280) is covered alongside GCC and Clang - Delete orphaned expected_e_ref_fail.cpp and expected_void_ref_fail.cpp (files claim those types are ill-formed but Steps 8/10 made them valid) - Update README: GoogleTest -> Catch2, badge URLs, paper D4280R0, C++17 -> C++20 minimum, compiler version ranges (GCC 11-16, Clang 17-22) - Apply clang-format alignment fixes in testing/types.hpp --- README.md | 61 +++---------------- tests/beman/expected/CMakeLists.txt | 34 +++++------ tests/beman/expected/expected_e_ref_fail.cpp | 6 -- .../beman/expected/expected_void_ref_fail.cpp | 6 -- tests/beman/expected/testing/types.hpp | 6 +- 5 files changed, 28 insertions(+), 85 deletions(-) delete mode 100644 tests/beman/expected/expected_e_ref_fail.cpp delete mode 100644 tests/beman/expected/expected_void_ref_fail.cpp diff --git a/README.md b/README.md index a0b293f..6f75e0a 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ 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) @@ -34,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)) @@ -146,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 @@ -206,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`. @@ -250,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/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 6722480..6637354 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -65,7 +65,7 @@ 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 - "no default constructor|use of deleted function|call to deleted" + "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 @@ -118,32 +118,32 @@ add_fail_test(expected_unexpected_value_type_fail expected_unexpected_value_type # 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" + "binding a temporary to T& creates a dangling reference|use of deleted function|call to deleted|no matching function|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" + "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" + "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" + "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" + "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" + "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" + "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) @@ -153,24 +153,24 @@ add_fail_test(expected_ref_or_else_wrong_value_type_fail expected_ref_or_else_wr # 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" + "binding a temporary to T& creates a dangling reference|use of deleted function|call to deleted|no matching function|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" + "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" + "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" + "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" + "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 @@ -262,7 +262,7 @@ add_fail_test(expected_ref_both_t_unexpected_fail ) 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" + "no in_place value constructor|use of deleted function|call to deleted|attempting to reference" ) # Step 9 — expected monadic mandates @@ -278,15 +278,15 @@ add_fail_test(expected_ref_both_transform_error_ref_result_fail # 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" + "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" + "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" + "no value_or for void|use of deleted function|call to deleted|attempting to reference" ) # ============================================================================= 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 7d354f2..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: expected is ill-formed [expected.object.general] para 2 -// EXPECT: "E must not be a reference" -#include - -beman::expected::expected e; // must not compile 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 ba531c9..0000000 --- a/tests/beman/expected/expected_void_ref_fail.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// NEGATIVE: expected is ill-formed — T=void not allowed in expected -// EXPECT: "T must not be void" -#include - -beman::expected::expected x; // should fail diff --git a/tests/beman/expected/testing/types.hpp b/tests/beman/expected/testing/types.hpp index 904160e..0962df1 100644 --- a/tests/beman/expected/testing/types.hpp +++ b/tests/beman/expected/testing/types.hpp @@ -24,7 +24,7 @@ struct traced { return *this; } constexpr traced& operator=(traced&& o) noexcept { - val = o.val; + val = o.val; o.val = -1; return *this; } @@ -45,7 +45,7 @@ struct move_only { 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; + val = o.val; o.val = -1; return *this; } @@ -122,7 +122,7 @@ struct eq_a { 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; } + 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; } }; From 34f5d548bd4f22ac511b1a23960837d48e5f094e Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Thu, 18 Jun 2026 14:18:03 -0400 Subject: [PATCH 29/42] fix: resolve three CI failures from first PR push - Add requires(!std::is_reference_v) to expected class declaration and all 35 out-of-class definitions so MSVC can unambiguously distinguish them from the new expected specialization (C2244/C2995/C3864 errors) - Add |no matching constructor to dangling-reference negative test patterns; Clang emits "no matching constructor" (not "no matching function") when reference_constructs_from_temporary_v evaluates false - Check r.has_value()/r.error() in two or_else test cases that left the result variable unused (-Werror=unused-variable on Clang) --- include/beman/expected/expected.hpp | 72 +++++++++---------- tests/beman/expected/CMakeLists.txt | 4 +- tests/beman/expected/expected_ref.test.cpp | 2 + .../expected/expected_void_ref_e.test.cpp | 2 + 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 518f6ad..d8f8201 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -1210,7 +1210,7 @@ constexpr auto expected::transform_error(F&& f) const&& { // ============================================================================= template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_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"); @@ -1477,7 +1477,7 @@ class expected { // ============================================================================= template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) constexpr expected::expected(const expected& rhs) requires(std::is_copy_constructible_v && !std::is_trivially_copy_constructible_v) : has_val_(rhs.has_val_) { @@ -1486,7 +1486,7 @@ constexpr expected::expected(const expected& rhs) } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_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) : has_val_(rhs.has_val_) { @@ -1495,7 +1495,7 @@ constexpr expected::expected(expected&& rhs) noexcept(std::is_nothrow_move } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires(std::is_void_v && std::is_constructible_v && !std::is_constructible_v, expected&> && @@ -1508,7 +1508,7 @@ constexpr expected::expected(const expected& rhs) : has_val_(rhs.has } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires(std::is_void_v && std::is_constructible_v && !std::is_constructible_v, expected&> && @@ -1521,7 +1521,7 @@ constexpr expected::expected(expected&& rhs) : has_val_(rhs.has_valu } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr expected::expected(const unexpected& e) : has_val_(false) { @@ -1529,7 +1529,7 @@ constexpr expected::expected(const unexpected& e) : has_val_(false) { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr expected::expected(unexpected&& e) : has_val_(false) { @@ -1537,7 +1537,7 @@ constexpr expected::expected(unexpected&& e) : has_val_(false) { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr expected::expected(unexpect_t, Args&&... args) : has_val_(false) { @@ -1545,7 +1545,7 @@ constexpr expected::expected(unexpect_t, Args&&... args) : has_val_(false) } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v&, Args...> constexpr expected::expected(unexpect_t, std::initializer_list il, Args&&... args) : has_val_(false) { @@ -1557,7 +1557,7 @@ constexpr expected::expected(unexpect_t, std::initializer_list il, Args // ============================================================================= template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) constexpr expected::~expected() requires(!std::is_trivially_destructible_v) { @@ -1570,7 +1570,7 @@ constexpr expected::~expected() // ============================================================================= template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_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 && @@ -1593,7 +1593,7 @@ constexpr expected& expected::operator=(const expected& rhs) } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_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 && @@ -1617,7 +1617,7 @@ constexpr expected& expected::operator=(expected&& rhs) noexcept(std } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires(std::is_constructible_v && std::is_assignable_v) constexpr expected& expected::operator=(const unexpected& e) { @@ -1631,7 +1631,7 @@ constexpr expected& expected::operator=(const unexpected& e) { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires(std::is_constructible_v && std::is_assignable_v) constexpr expected& expected::operator=(unexpected&& e) { @@ -1645,7 +1645,7 @@ constexpr expected& expected::operator=(unexpected&& e) { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) constexpr void expected::emplace() noexcept { if (!has_val_) { std::destroy_at(std::addressof(unex_)); @@ -1658,7 +1658,7 @@ constexpr void expected::emplace() noexcept { // ============================================================================= template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_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) @@ -1685,7 +1685,7 @@ constexpr void expected::swap(expected& rhs) noexcept(std::is_nothrow_move // ============================================================================= template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) constexpr void expected::value() const& { static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); if (!has_val_) @@ -1693,7 +1693,7 @@ constexpr void expected::value() const& { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_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"); @@ -1702,7 +1702,7 @@ constexpr void expected::value() && { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template constexpr E expected::error_or(G&& def) const& { static_assert(std::is_copy_constructible_v, "error_or requires is_copy_constructible_v"); @@ -1713,7 +1713,7 @@ constexpr E expected::error_or(G&& def) const& { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template constexpr E expected::error_or(G&& def) && { static_assert(std::is_move_constructible_v, "error_or requires is_move_constructible_v"); @@ -1728,7 +1728,7 @@ constexpr E expected::error_or(G&& def) && { // ============================================================================= template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr auto expected::and_then(F&& f) & { @@ -1743,7 +1743,7 @@ constexpr auto expected::and_then(F&& f) & { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr auto expected::and_then(F&& f) && { @@ -1758,7 +1758,7 @@ constexpr auto expected::and_then(F&& f) && { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr auto expected::and_then(F&& f) const& { @@ -1773,7 +1773,7 @@ constexpr auto expected::and_then(F&& f) const& { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr auto expected::and_then(F&& f) const&& { @@ -1788,7 +1788,7 @@ constexpr auto expected::and_then(F&& f) const&& { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template constexpr auto expected::or_else(F&& f) & { using G = std::remove_cvref_t>; @@ -1801,7 +1801,7 @@ constexpr auto expected::or_else(F&& f) & { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template constexpr auto expected::or_else(F&& f) && { using G = std::remove_cvref_t>; @@ -1814,7 +1814,7 @@ constexpr auto expected::or_else(F&& f) && { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template constexpr auto expected::or_else(F&& f) const& { using G = std::remove_cvref_t>; @@ -1827,7 +1827,7 @@ constexpr auto expected::or_else(F&& f) const& { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template constexpr auto expected::or_else(F&& f) const&& { using G = std::remove_cvref_t>; @@ -1840,7 +1840,7 @@ constexpr auto expected::or_else(F&& f) const&& { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr auto expected::transform(F&& f) & { @@ -1866,7 +1866,7 @@ constexpr auto expected::transform(F&& f) & { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr auto expected::transform(F&& f) && { @@ -1892,7 +1892,7 @@ constexpr auto expected::transform(F&& f) && { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr auto expected::transform(F&& f) const& { @@ -1918,7 +1918,7 @@ constexpr auto expected::transform(F&& f) const& { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template requires std::is_constructible_v constexpr auto expected::transform(F&& f) const&& { @@ -1944,7 +1944,7 @@ constexpr auto expected::transform(F&& f) const&& { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template constexpr auto expected::transform_error(F&& f) & { using G = std::remove_cv_t>; @@ -1959,7 +1959,7 @@ constexpr auto expected::transform_error(F&& f) & { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template constexpr auto expected::transform_error(F&& f) && { using G = std::remove_cv_t>; @@ -1974,7 +1974,7 @@ constexpr auto expected::transform_error(F&& f) && { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template constexpr auto expected::transform_error(F&& f) const& { using G = std::remove_cv_t>; @@ -1989,7 +1989,7 @@ constexpr auto expected::transform_error(F&& f) const& { } template - requires std::is_void_v + requires(std::is_void_v && !std::is_reference_v) template constexpr auto expected::transform_error(F&& f) const&& { using G = std::remove_cv_t>; diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 6637354..6d29fda 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -118,7 +118,7 @@ add_fail_test(expected_unexpected_value_type_fail expected_unexpected_value_type # 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|attempting to reference" + "binding a temporary to T& creates a dangling reference|use of deleted function|call to deleted|no matching function|no matching constructor|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" @@ -153,7 +153,7 @@ add_fail_test(expected_ref_or_else_wrong_value_type_fail expected_ref_or_else_wr # 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|attempting to reference" + "binding a temporary to T& creates a dangling reference|use of deleted function|call to deleted|no matching function|no matching constructor|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" diff --git a/tests/beman/expected/expected_ref.test.cpp b/tests/beman/expected/expected_ref.test.cpp index 5580151..f16eb07 100644 --- a/tests/beman/expected/expected_ref.test.cpp +++ b/tests/beman/expected/expected_ref.test.cpp @@ -404,6 +404,8 @@ TEST_CASE("expected: or_else passes error to callable", "[expected_ref]") { 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]") { diff --git a/tests/beman/expected/expected_void_ref_e.test.cpp b/tests/beman/expected/expected_void_ref_e.test.cpp index 1057dfe..578bd3c 100644 --- a/tests/beman/expected/expected_void_ref_e.test.cpp +++ b/tests/beman/expected/expected_void_ref_e.test.cpp @@ -319,6 +319,8 @@ TEST_CASE("expected: or_else propagates E& to F", "[expected_void_ref_e 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]") { From b147bca7387d46ccfb1c52418fcd98cecb4848bf Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Thu, 18 Jun 2026 14:42:58 -0400 Subject: [PATCH 30/42] fix: change expected specialization to explicit void parameter Replace `template requires std::is_void_v` with `template class expected` to eliminate MSVC C2244/ C2995/C3864 errors. MSVC cannot match out-of-class member function definitions to a class template partial specialization that has a requires clause once a second partial specialization with the same first argument (expected) exists. Making void explicit in the specialization pattern removes the requires clause entirely, giving MSVC an unambiguous match. Update all 37 out-of-class definitions accordingly: remove the T parameter and class-level requires, replace expected:: with expected::, and replace the four T references in transform_error return types with void. --- include/beman/expected/expected.hpp | 211 ++++++++++++---------------- 1 file changed, 88 insertions(+), 123 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index d8f8201..d28a779 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -1209,9 +1209,8 @@ constexpr auto expected::transform_error(F&& f) const&& { // [expected.void] Partial specialization for void value type // ============================================================================= -template - requires(std::is_void_v && !std::is_reference_v) -class expected { +template +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"); @@ -1219,7 +1218,7 @@ class expected { static_assert(!detail::is_unexpected_specialization::value, "E must not be an unexpected specialization"); public: - using value_type = T; + using value_type = void; using error_type = E; using unexpected_type = unexpected; @@ -1476,79 +1475,71 @@ class expected { // [expected.void.cons] Out-of-line constructor definitions // ============================================================================= -template - requires(std::is_void_v && !std::is_reference_v) -constexpr expected::expected(const expected& rhs) +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 && !std::is_reference_v) -constexpr expected::expected(expected&& rhs) noexcept(std::is_nothrow_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 && !std::is_reference_v) +template template requires(std::is_void_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 && !std::is_reference_v) +template template requires(std::is_void_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())); } -template - requires(std::is_void_v && !std::is_reference_v) +template template requires std::is_constructible_v -constexpr expected::expected(const unexpected& e) : has_val_(false) { +constexpr expected::expected(const unexpected& e) : has_val_(false) { std::construct_at(std::addressof(unex_), e.error()); } -template - requires(std::is_void_v && !std::is_reference_v) +template template requires std::is_constructible_v -constexpr expected::expected(unexpected&& e) : has_val_(false) { +constexpr expected::expected(unexpected&& e) : has_val_(false) { std::construct_at(std::addressof(unex_), std::move(e.error())); } -template - requires(std::is_void_v && !std::is_reference_v) +template template requires std::is_constructible_v -constexpr expected::expected(unexpect_t, Args&&... args) : has_val_(false) { +constexpr expected::expected(unexpect_t, Args&&... args) : has_val_(false) { std::construct_at(std::addressof(unex_), std::forward(args)...); } -template - requires(std::is_void_v && !std::is_reference_v) +template template requires std::is_constructible_v&, Args...> -constexpr expected::expected(unexpect_t, std::initializer_list il, Args&&... args) : has_val_(false) { +constexpr expected::expected(unexpect_t, std::initializer_list il, Args&&... args) : has_val_(false) { std::construct_at(std::addressof(unex_), il, std::forward(args)...); } @@ -1556,9 +1547,8 @@ constexpr expected::expected(unexpect_t, std::initializer_list il, Args // [expected.void.dtor] Out-of-line destructor // ============================================================================= -template - requires(std::is_void_v && !std::is_reference_v) -constexpr expected::~expected() +template +constexpr expected::~expected() requires(!std::is_trivially_destructible_v) { if (!has_val_) @@ -1569,9 +1559,8 @@ constexpr expected::~expected() // [expected.void.assign] Out-of-line assignment definitions // ============================================================================= -template - requires(std::is_void_v && !std::is_reference_v) -constexpr expected& expected::operator=(const expected& rhs) +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)) @@ -1592,10 +1581,10 @@ constexpr expected& expected::operator=(const expected& rhs) return *this; } -template - requires(std::is_void_v && !std::is_reference_v) -constexpr expected& expected::operator=(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_move_assignable_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)) @@ -1616,11 +1605,10 @@ constexpr expected& expected::operator=(expected&& rhs) noexcept(std return *this; } -template - requires(std::is_void_v && !std::is_reference_v) +template template requires(std::is_constructible_v && std::is_assignable_v) -constexpr expected& expected::operator=(const unexpected& e) { +constexpr expected& expected::operator=(const unexpected& e) { if (!has_val_) { unex_ = e.error(); } else { @@ -1630,11 +1618,10 @@ constexpr expected& expected::operator=(const unexpected& e) { return *this; } -template - requires(std::is_void_v && !std::is_reference_v) +template template requires(std::is_constructible_v && std::is_assignable_v) -constexpr expected& expected::operator=(unexpected&& e) { +constexpr expected& expected::operator=(unexpected&& e) { if (!has_val_) { unex_ = std::move(e.error()); } else { @@ -1644,9 +1631,8 @@ constexpr expected& expected::operator=(unexpected&& e) { return *this; } -template - requires(std::is_void_v && !std::is_reference_v) -constexpr void expected::emplace() noexcept { +template +constexpr void expected::emplace() noexcept { if (!has_val_) { std::destroy_at(std::addressof(unex_)); has_val_ = true; @@ -1657,10 +1643,9 @@ constexpr void expected::emplace() noexcept { // [expected.void.swap] Out-of-line swap definition // ============================================================================= -template - requires(std::is_void_v && !std::is_reference_v) -constexpr void expected::swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v && - std::is_nothrow_swappable_v) +template +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) { if (has_val_ && rhs.has_val_) { @@ -1684,27 +1669,24 @@ 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 && !std::is_reference_v) -constexpr void expected::value() const& { +template +constexpr void expected::value() const& { static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); if (!has_val_) throw bad_expected_access(unex_); } -template - requires(std::is_void_v && !std::is_reference_v) -constexpr void expected::value() && { +template +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"); if (!has_val_) throw bad_expected_access(std::move(unex_)); } -template - requires(std::is_void_v && !std::is_reference_v) +template template -constexpr E expected::error_or(G&& def) const& { +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"); if (!has_val_) @@ -1712,10 +1694,9 @@ constexpr E expected::error_or(G&& def) const& { return static_cast(std::forward(def)); } -template - requires(std::is_void_v && !std::is_reference_v) +template template -constexpr E expected::error_or(G&& def) && { +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"); if (!has_val_) @@ -1727,11 +1708,10 @@ constexpr E expected::error_or(G&& def) && { // [expected.void.monadic] Out-of-line monadic operation definitions // ============================================================================= -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1742,11 +1722,10 @@ constexpr auto expected::and_then(F&& f) & { return U(unexpect, unex_); } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1757,11 +1736,10 @@ constexpr auto expected::and_then(F&& f) && { return U(unexpect, std::move(unex_)); } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1772,11 +1750,10 @@ constexpr auto expected::and_then(F&& f) const& { return U(unexpect, unex_); } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1787,63 +1764,58 @@ constexpr auto expected::and_then(F&& f) const&& { return U(unexpect, std::move(unex_)); } -template - requires(std::is_void_v && !std::is_reference_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_); } -template - requires(std::is_void_v && !std::is_reference_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_)); } -template - requires(std::is_void_v && !std::is_reference_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_); } -template - requires(std::is_void_v && !std::is_reference_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_)); } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1865,11 +1837,10 @@ constexpr auto expected::transform(F&& f) & { } } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1891,11 +1862,10 @@ constexpr auto expected::transform(F&& f) && { } } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1917,11 +1887,10 @@ constexpr auto expected::transform(F&& f) const& { } } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1943,10 +1912,9 @@ constexpr auto expected::transform(F&& f) const&& { } } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1954,14 +1922,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_)); } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1969,14 +1936,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_))); } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1984,14 +1950,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_)); } -template - requires(std::is_void_v && !std::is_reference_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"); @@ -1999,8 +1964,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_))); } // ============================================================================= From 41f1f925e0bbd17464c10bb5c74ba8d1cae3f0fe Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Thu, 18 Jun 2026 16:32:15 -0400 Subject: [PATCH 31/42] fix: add MSVC error phrase variants to four negative-compile test patterns MSVC uses distinct error messages for constrained-overload failures: - 'no matching overloaded function found' (C2672) when constraints filter all emplace candidates (expected_emplace_throwing_fail) - 'no overloaded function could convert all the argument types' (C2665) for bool-ctor and temporary-binding tests The previous patterns only covered GCC/Clang and the C2280 'attempting to reference a deleted function' case added earlier. --- tests/beman/expected/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 6d29fda..6254d1f 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -74,7 +74,7 @@ add_fail_test(expected_t_array_fail expected_t_array_fail.cpp # Step 3 — expected emplace Mandates: nothrow_constructible_v add_fail_test(expected_emplace_throwing_fail expected_emplace_throwing_fail.cpp - "is_nothrow_constructible" + "is_nothrow_constructible|no matching overloaded function found" ) # Step 4 — expected ill-formed E [expected.void.general] @@ -108,7 +108,7 @@ add_fail_test(void_or_else_wrong_value_type_fail void_or_else_wrong_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 - "no matching function|no matching constructor" + "no matching function|no matching constructor|no overloaded function could convert" ) # Fix 4 — Mandates: T must not be a specialization of unexpected @@ -118,7 +118,7 @@ add_fail_test(expected_unexpected_value_type_fail expected_unexpected_value_type # 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|attempting to reference" + "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" @@ -153,7 +153,7 @@ add_fail_test(expected_ref_or_else_wrong_value_type_fail expected_ref_or_else_wr # 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|attempting to reference" + "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" From 059e9c7b8ea75e0aa20f1b18395b08d90bd9515b Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 26 Jun 2026 18:52:55 -0400 Subject: [PATCH 32/42] fix: reset copier _commit to v2.4.0 after template rebase --- .copier-answers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index dd385cd..8859c6b 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Standard answers and internal state managed by Copier -_commit: v2.4.0-40-g8bf7fea +_commit: v2.4.0 _src_path: https://github.com/steve-downey/exemplar.git description: Expected Over References maintainer: steve-downey From 13a7c64ff5b62f8b1573ad277b132f0e35a22689 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 26 Jun 2026 19:32:23 -0400 Subject: [PATCH 33/42] chore: recopy from exemplar copier branch (v2.4.1-14-g66b1f43) Template was rebased; _commit SHA was invalid. Reset base to v2.4.0, then recopied from the unmerged copier branch which introduces _subdirectory: template, config.hpp/config_generated.hpp.in, improved CMakePresets.json paths, updated infra submodule and vcpkg baselines, and moves find_package(Catch2) into tests/CMakeLists.txt. --- .beman-tidy.yaml | 6 +- .copier-answers.yml | 3 +- .gitattributes | 2 - .github/CODEOWNERS | 22 +- .github/workflows/ci_tests.yml | 2 +- .github/workflows/pre-commit-check.yml | 9 +- .github/workflows/vcpkg-release.yml | 1 - .pre-commit-config.yaml | 51 ++--- CMakeLists.txt | 29 +-- CMakePresets.json | 60 +++-- CONTRIBUTING.md | 1 - include/beman/expected/CMakeLists.txt | 14 +- include/beman/expected/config.hpp | 12 + .../beman/expected/config_generated.hpp.in | 8 + infra/.beman_submodule | 4 +- .../enable-experimental-import-std.cmake | 208 ++---------------- lockfile.json | 8 +- tests/beman/expected/CMakeLists.txt | 2 + vcpkg-configuration.json | 4 +- 19 files changed, 160 insertions(+), 286 deletions(-) create mode 100644 include/beman/expected/config.hpp create mode 100644 include/beman/expected/config_generated.hpp.in 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 8859c6b..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: v2.4.0 +_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/ci_tests.yml b/.github/workflows/ci_tests.yml index 8f82987..a297750 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -9,7 +9,7 @@ on: pull_request: workflow_dispatch: schedule: - - cron: '41 17 * * 0' + - cron: '15 12 * * 2' concurrency: group: ${{format('{0}:{1}', github.repository, github.ref)}} 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 fd1ab77..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) @@ -32,6 +31,12 @@ if(BEMAN_EXPECTED_USE_MODULES) set(CMAKE_CXX_SCAN_FOR_MODULES ON) endif() +configure_file( + "${PROJECT_SOURCE_DIR}/include/beman/expected/config_generated.hpp.in" + "${PROJECT_BINARY_DIR}/include/beman/expected/config_generated.hpp" + @ONLY +) + # for find of beman_install_library and configure_build_telemetry include(infra/cmake/beman-install-library.cmake) include(infra/cmake/BuildTelemetryConfig.cmake) @@ -48,14 +53,21 @@ if(BEMAN_EXPECTED_USE_MODULES) beman.expected PUBLIC FILE_SET CXX_MODULES - FILE_SET HEADERS BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" + FILE_SET HEADERS + BASE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}/include" ) set_target_properties(beman.expected PROPERTIES CXX_MODULE_STD ON) target_compile_features(beman.expected PUBLIC cxx_std_23) else() target_sources( beman.expected - PUBLIC FILE_SET HEADERS BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" + PUBLIC + FILE_SET HEADERS + BASE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}/include" ) set_target_properties( beman.expected @@ -68,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) 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/include/beman/expected/CMakeLists.txt b/include/beman/expected/CMakeLists.txt index 92cd8c6..bbb92d3 100644 --- a/include/beman/expected/CMakeLists.txt +++ b/include/beman/expected/CMakeLists.txt @@ -7,13 +7,23 @@ if(BEMAN_EXPECTED_USE_MODULES) PUBLIC FILE_SET CXX_MODULES FILES expected.cppm 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" ) else() target_sources( 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/config.hpp b/include/beman/expected/config.hpp new file mode 100644 index 0000000..bfaf5ac --- /dev/null +++ b/include/beman/expected/config.hpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef BEMAN_EXPECTED_CONFIG_HPP +#define BEMAN_EXPECTED_CONFIG_HPP + +#if !defined(__has_include) || __has_include() + #include +#else + #define BEMAN_EXPECTED_USE_MODULES() 0 +#endif + +#endif diff --git a/include/beman/expected/config_generated.hpp.in b/include/beman/expected/config_generated.hpp.in new file mode 100644 index 0000000..6d71197 --- /dev/null +++ b/include/beman/expected/config_generated.hpp.in @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef BEMAN_EXPECTED_CONFIG_GENERATED_HPP +#define BEMAN_EXPECTED_CONFIG_GENERATED_HPP + +#cmakedefine01 BEMAN_EXPECTED_USE_MODULES() + +#endif 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/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 6254d1f..e20a280 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -1,6 +1,8 @@ # tests/beman/expected/CMakeLists.txt -*-cmake-*- # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +find_package(Catch2 3 REQUIRED) + add_executable(beman.expected.tests.expected) target_sources( beman.expected.tests.expected 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 2732b5091d5646e7e976d8c0113b865807ad6523 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Fri, 26 Jun 2026 22:21:55 -0400 Subject: [PATCH 34/42] docs: add papers/ directory with D4280R0 draft (std::expected) Initial WG21 paper proposing a partial specialization of std::expected for lvalue reference value types, following the design of P2988 (optional). Includes LaTeX infrastructure (wg21-latex subtree) and build system. --- 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 +++++++ 27 files changed, 8626 insertions(+) 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/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 e1db45fe3998a7e84360488a566fd57dd4b610cf Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Mon, 29 Jun 2026 18:13:34 -0400 Subject: [PATCH 35/42] fix: address PR #57 review findings (issues 1, 3, 4, 5) Delete unsafe unexpected constructors in expected that used const_cast (UB risk), add missing deleted operator= overloads. Fix over-constrained value() && static_asserts in expected and expected. Remove tautological requires clause on expected partial specialization. Fix member layout inconsistency in expected to match all other specializations (has_val_ first). --- docs/plan/fix-review-corrections.md | 63 +++++++++++++++++++ include/beman/expected/expected.hpp | 37 +++++------ tests/beman/expected/CMakeLists.txt | 8 +++ ...cted_void_ref_e_assign_unexpected_fail.cpp | 11 ++++ ...d_ref_e_construct_from_unexpected_fail.cpp | 10 +++ 5 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 docs/plan/fix-review-corrections.md create mode 100644 tests/beman/expected/expected_void_ref_e_assign_unexpected_fail.cpp create mode 100644 tests/beman/expected/expected_void_ref_e_construct_from_unexpected_fail.cpp 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/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index d28a779..d4584f2 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -1973,7 +1973,6 @@ constexpr auto expected::transform_error(F&& f) const&& { // ============================================================================= template - requires std::is_lvalue_reference_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"); @@ -2292,8 +2291,7 @@ class expected { } constexpr T& value() && { - static_assert(std::is_copy_constructible_v && std::is_move_constructible_v, - "value() && requires E be copy and move constructible"); + static_assert(std::is_move_constructible_v, "value() && requires is_move_constructible_v"); if (!has_val_) throw bad_expected_access(std::move(unex_)); return *val_; @@ -3896,30 +3894,21 @@ class expected { // ------------------------------------------------------------------------- // Default constructor — void/success state - constexpr expected() noexcept : unex_ptr_(nullptr), has_val_(true) {} + constexpr expected() noexcept : has_val_(true), unex_ptr_(nullptr) {} // Copy/move — trivial (just pointer + bool) constexpr expected(const expected&) = default; constexpr expected(expected&&) noexcept = default; // In-place value constructor - constexpr explicit expected(std::in_place_t) noexcept : unex_ptr_(nullptr), has_val_(true) {} + constexpr explicit expected(std::in_place_t) noexcept : has_val_(true), unex_ptr_(nullptr) {} - // Error constructor from unexpected const& — E& binds to G's stored value via its lvalue accessor + // Deleted: no constructor from unexpected (would bind E& to temporary storage in unexpected) template - requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) noexcept : has_val_(false) { - E& r = const_cast(e.error()); - unex_ptr_ = std::addressof(r); - } + constexpr expected(const unexpected&) = delete; - // Error constructor from unexpected&& (non-const lvalue accessor gives G&, binds to E&) template - requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) noexcept : has_val_(false) { - E& r = e.error(); - unex_ptr_ = std::addressof(r); - } + constexpr expected(unexpected&&) = delete; // In-place error constructor — binds E& directly (no temporary allowed) template @@ -3963,6 +3952,13 @@ class expected { constexpr expected& operator=(const expected&) = default; constexpr expected& operator=(expected&&) noexcept = default; + // Deleted: no assignment from unexpected (would rebind E& to temporary storage) + template + constexpr expected& operator=(const unexpected&) = delete; + + template + constexpr expected& operator=(unexpected&&) = delete; + // emplace — transition to void success state (always noexcept) constexpr void emplace() noexcept { has_val_ = true; } @@ -4009,9 +4005,8 @@ class expected { } constexpr void value() && { - static_assert(std::is_copy_constructible_v> && - std::is_move_constructible_v>, - "value() && requires E to be copy and move constructible"); + static_assert(std::is_copy_constructible_v>, + "value() && requires is_copy_constructible_v"); if (!has_val_) throw bad_expected_access>(*unex_ptr_); } @@ -4305,8 +4300,8 @@ class expected { } private: - E* unex_ptr_; bool has_val_; + E* unex_ptr_; }; } // namespace expected diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 6254d1f..008c56c 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -288,6 +288,14 @@ 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|selected deleted operator|attempting to reference" +) # ============================================================================= # Hardened precondition tests (compiled with -DBEMAN_EXPECTED_HARDENED) 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_construct_from_unexpected_fail.cpp b/tests/beman/expected/expected_void_ref_e_construct_from_unexpected_fail.cpp new file mode 100644 index 0000000..526b6ed --- /dev/null +++ b/tests/beman/expected/expected_void_ref_e_construct_from_unexpected_fail.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// NEGATIVE: expected cannot be constructed from unexpected. +// unexpected stores G by value; binding E& to it would create a dangling +// reference when the unexpected object is destroyed. +// EXPECT: "no matching function" +#include +using namespace beman::expected; +void test() { + expected e = unexpected(7); // must not compile +} From a647181cb068a4afa8c28dc5dff193983e7d66e9 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 30 Jun 2026 23:33:30 -0400 Subject: [PATCH 36/42] refactor: unify expected<>'s E-axis via unexpected Add a partial specialization unexpected that stores a pointer instead of a value, then use unexpected as the uniform error-storage type inside expected<>'s union. Since a union can't hold a reference member directly but can hold unexpected (just a pointer), this collapses the three E-reference specializations into their value-E siblings: expected absorbs expected expected absorbs expected expected absorbs expected cutting expected<>'s partial-specialization count from 6 down to 3, with zero intended behavior change. All existing unit and compile-fail tests pass unmodified, except expected_ref_e_ref_fail.cpp (+ its CMakeLists.txt reason string), which is reworded from "E must not be a reference" to "E must not be an rvalue reference" now that lvalue-reference E is valid in that specialization. --- include/beman/expected/expected.hpp | 4610 +++++------------ include/beman/expected/unexpected.hpp | 88 + tests/beman/expected/CMakeLists.txt | 2 +- .../expected/expected_ref_e_ref_fail.cpp | 2 +- 4 files changed, 1439 insertions(+), 3263 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index d28a779..50465a4 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -83,22 +83,19 @@ constexpr void reinit_expected(NewVal& newval, CurVal& oldval, Args&&... args) { } } -// 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; -#else -template -concept reference_converts_from_temporary_v = - std::is_reference_v && - ((!std::is_reference_v && std::is_convertible_v*, std::remove_cvref_t*>) || - (std::is_lvalue_reference_v && std::is_const_v> && - std::is_convertible_v&&> && - !std::is_convertible_v&>)); - -template -concept reference_constructs_from_temporary_v = reference_converts_from_temporary_v; -#endif +// 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 @@ -124,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; @@ -147,49 +147,105 @@ class expected { // Default constructor: value-initializes T constexpr expected() noexcept(std::is_nothrow_default_constructible_v) - requires std::is_default_constructible_v; + requires std::is_default_constructible_v + : has_val_(true) { + std::construct_at(std::addressof(val_)); + } // 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>)) + : has_val_(rhs.has_val_) { + if (has_val_) + std::construct_at(std::addressof(val_), rhs.val_); + else + std::construct_at(std::addressof(unex_), rhs.unex_); + } // 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>)) + : has_val_(rhs.has_val_) { + if (has_val_) + std::construct_at(std::addressof(val_), std::move(rhs.val_)); + else + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); + } - // Converting copy constructor from expected + // Converting copy constructor from expected — value-E path (works as today) 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 &&> && !std::is_constructible_v, const expected&> && !std::is_constructible_v, const expected &&>) constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) - expected(const expected& rhs); + 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()); + } - // Converting move constructor from expected + // Converting move constructor from expected — value-E path (works as today) 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 &&> && !std::is_constructible_v, const expected&> && !std::is_constructible_v, const expected &&>) - constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) expected(expected&& rhs); + constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) 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_), std::move(rhs.error())); + } + + // Converting constructor from expected — reference-E path (mirrors pre-merge + // expected, which only accepted sources whose error type is also a reference) + 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) + : 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 + 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) + : 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()); + } // Constructor from value U&& template > @@ -203,46 +259,86 @@ class expected { std::construct_at(std::addressof(val_), std::forward(v)); } - // Constructor from unexpected const& + // Constructor from unexpected const& / && — value-E path (SFINAE'd, works as today) + template + requires(!std::is_reference_v && std::is_constructible_v) + constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); + } + + template + requires(!std::is_reference_v && std::is_constructible_v) + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::move(e.error())); + } + + // Deleted for reference E: unexpected stores G by value; binding E& to it would create a + // dangling reference once the unexpected temporary is destroyed (mirrors pre-merge expected). template - requires std::is_constructible_v - constexpr explicit(!std::is_convertible_v) expected(const unexpected& e); + requires std::is_reference_v + constexpr expected(const unexpected&) = delete; - // Constructor from unexpected&& template - requires std::is_constructible_v - constexpr explicit(!std::is_convertible_v) expected(unexpected&& e); + requires std::is_reference_v + constexpr expected(unexpected&&) = delete; // In-place constructor for value template requires std::is_constructible_v - constexpr explicit expected(std::in_place_t, Args&&... args); + constexpr explicit expected(std::in_place_t, Args&&... args) : has_val_(true) { + std::construct_at(std::addressof(val_), std::forward(args)...); + } // In-place constructor for value with initializer_list template requires std::is_constructible_v&, Args...> - constexpr explicit expected(std::in_place_t, std::initializer_list il, Args&&... args); + constexpr explicit expected(std::in_place_t, std::initializer_list il, Args&&... args) : has_val_(true) { + std::construct_at(std::addressof(val_), il, std::forward(args)...); + } // In-place constructor for error template - requires std::is_constructible_v - constexpr explicit expected(unexpect_t, Args&&... args); + requires(std::is_constructible_v && !detail::unexpect_dangles_v) + constexpr explicit expected(unexpect_t, Args&&... args) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::in_place, std::forward(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) — mirrors the pre-merge reference + // specializations' three-overload dangling-prevention pattern. + 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); + constexpr explicit 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)...); + } // ------------------------------------------------------------------------- // [expected.object.dtor] Destructor // ------------------------------------------------------------------------- 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>)) + { + if (has_val_) + std::destroy_at(std::addressof(val_)); + else + std::destroy_at(std::addressof(unex_)); + } // ------------------------------------------------------------------------- // [expected.object.assign] Assignment @@ -251,37 +347,70 @@ 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>)) + { + if (has_val_ && rhs.has_val_) { + val_ = rhs.val_; + } else if (!has_val_ && !rhs.has_val_) { + unex_ = rhs.unex_; + } else if (has_val_) { + // was value, now error + detail::reinit_expected(unex_, val_, rhs.unex_); + has_val_ = false; + } else { + // was error, now value + detail::reinit_expected(val_, unex_, rhs.val_); + has_val_ = true; + } + return *this; + } // 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>)) + { + if (has_val_ && rhs.has_val_) { + val_ = std::move(rhs.val_); + } else if (!has_val_ && !rhs.has_val_) { + unex_ = std::move(rhs.unex_); + } else if (has_val_) { + detail::reinit_expected(unex_, val_, std::move(rhs.unex_)); + has_val_ = false; + } else { + detail::reinit_expected(val_, unex_, std::move(rhs.val_)); + has_val_ = true; + } + return *this; + } // Assignment from value U&& template > @@ -289,7 +418,7 @@ 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)) + std::is_nothrow_move_constructible_v>)) constexpr expected& operator=(U&& v) { if (has_val_) { val_ = std::forward(v); @@ -300,39 +429,126 @@ class expected { return *this; } - // Assignment from unexpected const& + // Assignment from unexpected — value-E path (SFINAE'd, works as today) 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)) - constexpr expected& operator=(const unexpected& e); + std::is_nothrow_move_constructible_v>)) + constexpr expected& operator=(const unexpected& e) { + if (!has_val_) { + unex_.error() = e.error(); + } else { + detail::reinit_expected(unex_, val_, e.error()); + has_val_ = false; + } + return *this; + } - // 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)) - constexpr expected& operator=(unexpected&& e); + std::is_nothrow_move_constructible_v>)) + constexpr expected& operator=(unexpected&& e) { + if (!has_val_) { + unex_.error() = std::move(e.error()); + } else { + detail::reinit_expected(unex_, val_, std::move(e.error())); + has_val_ = false; + } + return *this; + } + + // Deleted for reference E: would rebind E& to unexpected's temporary storage (mirrors + // pre-merge expected). + template + requires std::is_reference_v + constexpr expected& operator=(const unexpected&) = delete; + + template + requires 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 - constexpr T& emplace(Args&&... args) noexcept; + constexpr T& emplace(Args&&... args) noexcept { + if (has_val_) + std::destroy_at(std::addressof(val_)); + else + std::destroy_at(std::addressof(unex_)); + std::construct_at(std::addressof(val_), std::forward(args)...); + has_val_ = true; + return val_; + } template requires std::is_nothrow_constructible_v&, Args...> - constexpr T& emplace(std::initializer_list il, Args&&... args) noexcept; + constexpr T& emplace(std::initializer_list il, Args&&... args) noexcept { + if (has_val_) + std::destroy_at(std::addressof(val_)); + else + std::destroy_at(std::addressof(unex_)); + std::construct_at(std::addressof(val_), il, std::forward(args)...); + has_val_ = true; + return val_; + } // ------------------------------------------------------------------------- // [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>)) + { + if (has_val_ && rhs.has_val_) { + using std::swap; + 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, rhs has error + 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_)); + std::destroy_at(std::addressof(val_)); + std::construct_at(std::addressof(unex_), std::move(tmp)); + } else { + try { + std::construct_at(std::addressof(rhs.val_), std::move(val_)); + std::destroy_at(std::addressof(val_)); + std::construct_at(std::addressof(unex_), std::move(tmp)); + } catch (...) { + std::construct_at(std::addressof(rhs.unex_), std::move(tmp)); + throw; + } + } + } else { + T tmp(std::move(val_)); + std::destroy_at(std::addressof(val_)); + try { + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); + std::destroy_at(std::addressof(rhs.unex_)); + std::construct_at(std::addressof(rhs.val_), std::move(tmp)); + } catch (...) { + std::construct_at(std::addressof(val_), std::move(tmp)); + throw; + } + } + has_val_ = false; + rhs.has_val_ = true; + } else { + // this has error, rhs has value + rhs.swap(*this); + } + } friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } @@ -340,2836 +556,273 @@ class expected { // [expected.object.obs] Observers // ------------------------------------------------------------------------- - constexpr const T* operator->() const noexcept; - constexpr T* operator->() noexcept; + constexpr const T* operator->() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::addressof(val_); + } - constexpr const T& operator*() const& noexcept; - constexpr T& operator*() & noexcept; - constexpr const T&& operator*() const&& noexcept; - constexpr T&& operator*() && noexcept; + constexpr T* operator->() noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::addressof(val_); + } - constexpr explicit operator bool() const noexcept; - constexpr bool has_value() const noexcept; + constexpr const T& operator*() const& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return val_; + } - constexpr const T& value() const&; - constexpr T& value() &; - constexpr const T&& value() const&&; - constexpr T&& value() &&; + constexpr T& operator*() & noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return val_; + } - constexpr const E& error() const& noexcept; - constexpr E& error() & noexcept; - constexpr const E&& error() const&& noexcept; - constexpr E&& error() && noexcept; + constexpr const T&& operator*() const&& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(val_); + } - template > - constexpr T value_or(U&& def) const&; + constexpr T&& operator*() && noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(val_); + } - template > - constexpr T value_or(U&& def) &&; + constexpr explicit operator bool() const noexcept { return has_val_; } + constexpr bool has_value() const noexcept { return has_val_; } - template - constexpr E error_or(G&& def) const&; + constexpr const T& 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 E error_or(G&& def) &&; + constexpr T& value() & { + static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); + if (!has_val_) + throw bad_expected_access(unex_.error()); + return val_; + } - // ------------------------------------------------------------------------- - // [expected.object.monadic] Monadic operations - // ------------------------------------------------------------------------- + constexpr const T&& value() const&& { + 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_).error()); + return std::move(val_); + } - 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&&; + constexpr T&& value() && { + 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_).error()); + return std::move(val_); + } - template - requires std::is_constructible_v - constexpr auto or_else(F&& f) &; - template - requires std::is_constructible_v - constexpr auto or_else(F&& f) &&; - template - requires std::is_constructible_v - constexpr auto or_else(F&& f) const&; - template - requires std::is_constructible_v - constexpr auto or_else(F&& f) const&&; - - 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&&; - - template - requires std::is_constructible_v - constexpr auto transform_error(F&& f) &; - template - requires std::is_constructible_v - constexpr auto transform_error(F&& f) &&; - template - requires std::is_constructible_v - constexpr auto transform_error(F&& f) const&; - template - requires std::is_constructible_v - constexpr auto transform_error(F&& f) const&&; - - // ------------------------------------------------------------------------- - // [expected.object.eq] 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_; - E unex_; - }; -}; - -// ============================================================================= -// [expected.object.cons] Out-of-line constructor definitions -// ============================================================================= - -template -constexpr expected::expected() noexcept(std::is_nothrow_default_constructible_v) - requires std::is_default_constructible_v - : has_val_(true) { - std::construct_at(std::addressof(val_)); -} - -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)) - : has_val_(rhs.has_val_) { - if (has_val_) - std::construct_at(std::addressof(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 && - 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_)); - else - std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); -} - -template -template - requires(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 &&> && - !std::is_constructible_v, const expected&> && - !std::is_constructible_v, const expected &&>) -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_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 &&> && - !std::is_constructible_v, const expected&> && - !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)); - else - std::construct_at(std::addressof(unex_), std::move(rhs.error())); -} - -template -template - requires 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 -constexpr expected::expected(unexpected&& e) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::move(e.error())); -} - -template -template - requires std::is_constructible_v -constexpr expected::expected(std::in_place_t, Args&&... args) : has_val_(true) { - std::construct_at(std::addressof(val_), std::forward(args)...); -} - -template -template - requires std::is_constructible_v&, Args...> -constexpr expected::expected(std::in_place_t, std::initializer_list il, Args&&... args) : has_val_(true) { - std::construct_at(std::addressof(val_), il, std::forward(args)...); -} - -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)...); -} - -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)...); -} - -// ============================================================================= -// [expected.object.dtor] Out-of-line destructor -// ============================================================================= - -template -constexpr expected::~expected() - requires(!(std::is_trivially_destructible_v && std::is_trivially_destructible_v)) -{ - if (has_val_) - std::destroy_at(std::addressof(val_)); - else - std::destroy_at(std::addressof(unex_)); -} - -// ============================================================================= -// [expected.object.assign] 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_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)) -{ - if (has_val_ && rhs.has_val_) { - val_ = rhs.val_; - } else if (!has_val_ && !rhs.has_val_) { - unex_ = rhs.unex_; - } else if (has_val_) { - // was value, now error - detail::reinit_expected(unex_, val_, rhs.unex_); - has_val_ = false; - } else { - // was error, now value - detail::reinit_expected(val_, unex_, 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 && - 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)) -{ - if (has_val_ && rhs.has_val_) { - val_ = std::move(rhs.val_); - } else if (!has_val_ && !rhs.has_val_) { - unex_ = std::move(rhs.unex_); - } else if (has_val_) { - detail::reinit_expected(unex_, val_, std::move(rhs.unex_)); - has_val_ = false; - } else { - detail::reinit_expected(val_, unex_, std::move(rhs.val_)); - has_val_ = true; - } - return *this; -} - -template -template - requires(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=(const unexpected& e) { - if (!has_val_) { - unex_ = e.error(); - } else { - detail::reinit_expected(unex_, val_, e.error()); - has_val_ = false; - } - return *this; -} - -template -template - requires(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=(unexpected&& e) { - if (!has_val_) { - unex_ = std::move(e.error()); - } else { - detail::reinit_expected(unex_, val_, std::move(e.error())); - has_val_ = false; - } - return *this; -} - -// ============================================================================= -// [expected.object.assign] Out-of-line emplace definitions -// ============================================================================= - -template -template - requires std::is_nothrow_constructible_v -constexpr T& expected::emplace(Args&&... args) noexcept { - if (has_val_) - std::destroy_at(std::addressof(val_)); - else - std::destroy_at(std::addressof(unex_)); - std::construct_at(std::addressof(val_), std::forward(args)...); - has_val_ = true; - return val_; -} - -template -template - requires std::is_nothrow_constructible_v&, Args...> -constexpr T& expected::emplace(std::initializer_list il, Args&&... args) noexcept { - if (has_val_) - std::destroy_at(std::addressof(val_)); - else - std::destroy_at(std::addressof(unex_)); - std::construct_at(std::addressof(val_), il, std::forward(args)...); - has_val_ = true; - return val_; -} - -// ============================================================================= -// [expected.object.swap] Out-of-line swap definition -// ============================================================================= - -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)) -{ - if (has_val_ && rhs.has_val_) { - using std::swap; - 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, rhs has error - if constexpr (std::is_nothrow_move_constructible_v) { - E 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_)); - std::destroy_at(std::addressof(val_)); - std::construct_at(std::addressof(unex_), std::move(tmp)); - } else { - try { - std::construct_at(std::addressof(rhs.val_), std::move(val_)); - std::destroy_at(std::addressof(val_)); - std::construct_at(std::addressof(unex_), std::move(tmp)); - } catch (...) { - std::construct_at(std::addressof(rhs.unex_), std::move(tmp)); - throw; - } - } - } else { - T tmp(std::move(val_)); - std::destroy_at(std::addressof(val_)); - try { - std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); - std::destroy_at(std::addressof(rhs.unex_)); - std::construct_at(std::addressof(rhs.val_), std::move(tmp)); - } catch (...) { - std::construct_at(std::addressof(val_), std::move(tmp)); - throw; - } - } - has_val_ = false; - rhs.has_val_ = true; - } else { - // this has error, rhs has value - rhs.swap(*this); - } -} - -// ============================================================================= -// [expected.object.obs] Out-of-line observer definitions -// ============================================================================= - -template -constexpr const T* expected::operator->() const noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::addressof(val_); -} - -template -constexpr T* expected::operator->() noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::addressof(val_); -} - -template -constexpr const T& expected::operator*() const& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return val_; -} - -template -constexpr T& expected::operator*() & noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return val_; -} - -template -constexpr const T&& expected::operator*() const&& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::move(val_); -} - -template -constexpr T&& expected::operator*() && noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::move(val_); -} - -template -constexpr expected::operator bool() const noexcept { - return has_val_; -} - -template -constexpr bool expected::has_value() const noexcept { - return has_val_; -} - -template -constexpr const 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_); - return val_; -} - -template -constexpr T& expected::value() & { - static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); - if (!has_val_) - throw bad_expected_access(unex_); - 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 (!has_val_) - throw bad_expected_access(std::move(unex_)); - 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 (!has_val_) - throw bad_expected_access(std::move(unex_)); - return std::move(val_); -} - -template -constexpr const E& expected::error() const& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return unex_; -} - -template -constexpr E& expected::error() & noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return unex_; -} - -template -constexpr const E&& expected::error() const&& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::move(unex_); -} - -template -constexpr E&& expected::error() && noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::move(unex_); -} - -template -template -constexpr T expected::value_or(U&& def) const& { - static_assert(std::is_copy_constructible_v, "value_or requires is_copy_constructible_v"); - 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 -constexpr T expected::value_or(U&& def) && { - static_assert(std::is_move_constructible_v, "value_or requires is_move_constructible_v"); - static_assert(std::is_convertible_v, "value_or requires is_convertible_v"); - if (has_val_) - return std::move(val_); - return static_cast(std::forward(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"); - if (!has_val_) - return unex_; - 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"); - if (!has_val_) - return std::move(unex_); - return static_cast(std::forward(def)); -} - -// ============================================================================= -// [expected.object.monadic] 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_); -} - -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), std::move(val_)); - return U(unexpect, std::move(unex_)); -} - -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_); -} - -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), std::move(val_)); - return U(unexpect, std::move(unex_)); -} - -template -template - requires std::is_constructible_v -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(std::in_place, val_); - return std::invoke(std::forward(f), unex_); -} - -template -template - requires std::is_constructible_v -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(std::in_place, std::move(val_)); - return std::invoke(std::forward(f), std::move(unex_)); -} - -template -template - requires std::is_constructible_v -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(std::in_place, val_); - return std::invoke(std::forward(f), unex_); -} - -template -template - requires std::is_constructible_v -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(std::in_place, std::move(val_)); - return std::invoke(std::forward(f), std::move(unex_)); -} - -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_); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f), val_)); - return expected(unexpect, unex_); - } -} - -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), std::move(val_)); - if (has_val_) - return expected(); - return expected(unexpect, std::move(unex_)); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f), std::move(val_))); - return expected(unexpect, std::move(unex_)); - } -} - -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_); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f), val_)); - return expected(unexpect, unex_); - } -} - -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), std::move(val_)); - if (has_val_) - return expected(); - return expected(unexpect, std::move(unex_)); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f), std::move(val_))); - return expected(unexpect, std::move(unex_)); - } -} - -template -template - requires std::is_constructible_v -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(std::in_place, val_); - return expected(unexpect, std::invoke(std::forward(f), unex_)); -} - -template -template - requires std::is_constructible_v -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(std::in_place, std::move(val_)); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_))); -} - -template -template - requires std::is_constructible_v -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(std::in_place, val_); - return expected(unexpect, std::invoke(std::forward(f), unex_)); -} - -template -template - requires std::is_constructible_v -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(std::in_place, std::move(val_)); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_))); -} - -// ============================================================================= -// [expected.void] Partial specialization for void value type -// ============================================================================= - -template -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"); - - public: - using value_type = void; - using error_type = E; - using unexpected_type = unexpected; - - template - using rebind = expected; - - // ------------------------------------------------------------------------- - // [expected.void.cons] Constructors - // ------------------------------------------------------------------------- - - constexpr expected() noexcept : has_val_(true) {} - - constexpr expected(const expected&) - 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); - - constexpr expected(expected&&) noexcept - 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); - - // Converting constructor from expected where is_void_v - template - requires(std::is_void_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 && - !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& - template - requires std::is_constructible_v - constexpr explicit(!std::is_convertible_v) expected(const unexpected& e); - - // Constructor from unexpected&& - template - requires std::is_constructible_v - constexpr explicit(!std::is_convertible_v) expected(unexpected&& e); - - // In-place constructor for value (no args, just marks has-value) - constexpr explicit expected(std::in_place_t) noexcept : has_val_(true) {} - - // In-place constructor for error - template - requires std::is_constructible_v - constexpr explicit expected(unexpect_t, Args&&... args); - - // 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); - - // ------------------------------------------------------------------------- - // [expected.void.dtor] Destructor - // ------------------------------------------------------------------------- - - constexpr ~expected() - requires std::is_trivially_destructible_v - = default; - - constexpr ~expected() - requires(!std::is_trivially_destructible_v); - - // ------------------------------------------------------------------------- - // [expected.void.assign] Assignment - // ------------------------------------------------------------------------- - - // 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)); - - template - requires(std::is_constructible_v && std::is_assignable_v) - constexpr expected& operator=(const unexpected& e); - - template - requires(std::is_constructible_v && std::is_assignable_v) - constexpr expected& operator=(unexpected&& e); - - 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); - - friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } - - // ------------------------------------------------------------------------- - // [expected.void.obs] Observers - // ------------------------------------------------------------------------- - - constexpr explicit operator bool() const noexcept { return has_val_; } - constexpr bool has_value() const noexcept { return has_val_; } - - constexpr void operator*() const noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - } - - 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_); - } - - template - constexpr E error_or(G&& def) const&; - - template - constexpr E error_or(G&& def) &&; - - // ------------------------------------------------------------------------- - // [expected.void.monadic] 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&&; - - 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&&; - - 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&&; - - // ------------------------------------------------------------------------- - // [expected.void.eq] 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 true; - return x.error() == y.error(); - } - - 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 { - E unex_; - }; -}; - -// ============================================================================= -// [expected.void.cons] Out-of-line constructor definitions -// ============================================================================= - -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 -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 -template - requires(std::is_void_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()) { - if (!has_val_) - std::construct_at(std::addressof(unex_), rhs.error()); -} - -template -template - requires(std::is_void_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()) { - if (!has_val_) - std::construct_at(std::addressof(unex_), std::move(rhs.error())); -} - -template -template - requires 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 -constexpr expected::expected(unexpected&& e) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::move(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)...); -} - -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)...); -} - -// ============================================================================= -// [expected.void.dtor] Out-of-line destructor -// ============================================================================= - -template -constexpr expected::~expected() - requires(!std::is_trivially_destructible_v) -{ - if (!has_val_) - std::destroy_at(std::addressof(unex_)); -} - -// ============================================================================= -// [expected.void.assign] 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_) { - // 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 -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 -template - requires(std::is_constructible_v && std::is_assignable_v) -constexpr expected& expected::operator=(const unexpected& e) { - if (!has_val_) { - unex_ = e.error(); - } else { - std::construct_at(std::addressof(unex_), e.error()); - has_val_ = false; - } - return *this; -} - -template -template - requires(std::is_constructible_v && std::is_assignable_v) -constexpr expected& expected::operator=(unexpected&& e) { - if (!has_val_) { - unex_ = std::move(e.error()); - } else { - std::construct_at(std::addressof(unex_), std::move(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; - } -} - -// ============================================================================= -// [expected.void.swap] Out-of-line swap definition -// ============================================================================= - -template -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) -{ - if (has_val_ && rhs.has_val_) { - // both value: no-op - } else if (!has_val_ && !rhs.has_val_) { - 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); - } -} - -// ============================================================================= -// [expected.void.obs] Out-of-line observer definitions -// ============================================================================= - -template -constexpr void expected::value() const& { - static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); - if (!has_val_) - throw bad_expected_access(unex_); -} - -template -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"); - if (!has_val_) - throw bad_expected_access(std::move(unex_)); -} - -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"); - if (!has_val_) - return unex_; - 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"); - if (!has_val_) - return std::move(unex_); - return static_cast(std::forward(def)); -} - -// ============================================================================= -// [expected.void.monadic] 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)); - return U(unexpect, unex_); -} - -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)); - return U(unexpect, std::move(unex_)); -} - -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)); - return U(unexpect, unex_); -} - -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)); - return U(unexpect, std::move(unex_)); -} - -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(); - return std::invoke(std::forward(f), unex_); -} - -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(); - return std::invoke(std::forward(f), std::move(unex_)); -} - -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(); - return std::invoke(std::forward(f), unex_); -} - -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(); - return std::invoke(std::forward(f), std::move(unex_)); -} - -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)); - if (has_val_) - return expected(); - return expected(unexpect, unex_); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, unex_); - } -} - -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)); - if (has_val_) - return expected(); - return expected(unexpect, std::move(unex_)); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, std::move(unex_)); - } -} - -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)); - if (has_val_) - return expected(); - return expected(unexpect, unex_); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, unex_); - } -} - -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)); - if (has_val_) - return expected(); - return expected(unexpect, std::move(unex_)); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, std::move(unex_)); - } -} - -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(); - return expected(unexpect, std::invoke(std::forward(f), unex_)); -} - -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(); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_))); -} - -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(); - return expected(unexpect, std::invoke(std::forward(f), unex_)); -} - -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(); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_))); -} - -// ============================================================================= -// Partial specialization: expected — reference value type (P2988) -// ============================================================================= - -template - requires std::is_lvalue_reference_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_object_v, "E must be an object type"); - static_assert(std::is_same_v, E>, "E must not be cv-qualified"); - - 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) - : has_val_(rhs.has_val_) { - if (has_val_) - val_ = rhs.val_; - else - std::construct_at(std::addressof(unex_), rhs.unex_); - } - - // 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) - : has_val_(rhs.has_val_) { - if (has_val_) - val_ = rhs.val_; - else - std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); - } - - // 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 : 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) - template - requires(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) - : has_val_(rhs.has_value()) { - if (has_val_) { - T& r = *rhs; - val_ = std::addressof(r); - } else { - std::construct_at(std::addressof(unex_), rhs.error()); - } - } - - // Converting constructor from expected (move) - template - requires(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) - : 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())); - } - } - - // Constructor from unexpected const& - template - requires std::is_constructible_v - constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) : has_val_(false) { - std::construct_at(std::addressof(unex_), e.error()); - } - - // Constructor from unexpected&& - template - requires std::is_constructible_v - constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::move(e.error())); - } - - // In-place constructor for error - template - requires std::is_constructible_v - constexpr explicit expected(unexpect_t, Args&&... args) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::forward(args)...); - } - - // 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) : has_val_(false) { - std::construct_at(std::addressof(unex_), il, std::forward(args)...); - } - - // ------------------------------------------------------------------------- - // Destructor - // ------------------------------------------------------------------------- - - constexpr ~expected() - requires std::is_trivially_destructible_v - = default; - - constexpr ~expected() - requires(!std::is_trivially_destructible_v) - { - if (!has_val_) - std::destroy_at(std::addressof(unex_)); - } - - // ------------------------------------------------------------------------- - // 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)) - { - 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; - } - - // 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)) - { - 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; - } - - // 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 { - std::destroy_at(std::addressof(unex_)); - T& r = std::forward(u); - val_ = std::addressof(r); - has_val_ = true; - } - return *this; - } - - // Assignment from unexpected const& - template - requires(std::is_constructible_v && std::is_assignable_v) - constexpr expected& operator=(const unexpected& e) { - if (!has_val_) { - unex_ = e.error(); - } else { - std::construct_at(std::addressof(unex_), e.error()); - has_val_ = false; - } - return *this; - } - - // Assignment from unexpected&& - template - requires(std::is_constructible_v && std::is_assignable_v) - constexpr expected& operator=(unexpected&& e) { - if (!has_val_) { - unex_ = std::move(e.error()); - } else { - std::construct_at(std::addressof(unex_), std::move(e.error())); - has_val_ = false; - } - return *this; - } - - // emplace — rebind the reference - template - requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr T& emplace(U&& u) noexcept { - if (!has_val_) { - std::destroy_at(std::addressof(unex_)); - has_val_ = true; - } - T& r = std::forward(u); - val_ = std::addressof(r); - return *val_; - } - - // ------------------------------------------------------------------------- - // 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) - { - 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); - } - } - - friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } - - // ------------------------------------------------------------------------- - // Observers - // ------------------------------------------------------------------------- - - constexpr T* operator->() const noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return val_; - } - - constexpr T& operator*() const noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return *val_; - } - - constexpr explicit operator bool() const noexcept { return has_val_; } - constexpr bool has_value() const noexcept { return has_val_; } - - constexpr T& value() const& { - static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); - if (!has_val_) - throw bad_expected_access(unex_); - return *val_; - } - - constexpr T& value() && { - 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_)); - return *val_; - } - - 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_); - } - - template > - requires(std::is_object_v && !std::is_array_v) - constexpr std::remove_cv_t 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 - constexpr E 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"); - if (!has_val_) - return unex_; - return static_cast(std::forward(def)); - } - - template - constexpr E 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"); - if (!has_val_) - return std::move(unex_); - return static_cast(std::forward(def)); - } - - // ------------------------------------------------------------------------- - // Monadic operations - // ------------------------------------------------------------------------- - - // and_then - template - requires std::is_constructible_v - constexpr auto 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_); - } - - template - requires std::is_constructible_v - constexpr auto 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_)); - } - - template - requires std::is_constructible_v - constexpr auto 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_); - } - - template - requires std::is_constructible_v - constexpr auto 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_)); - } - - // or_else - template - constexpr auto 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_); - } - - template - constexpr auto 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_)); - } - - template - constexpr auto 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_); - } - - template - constexpr auto 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_)); - } - - // transform - template - requires std::is_constructible_v - constexpr auto 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_); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f), *val_)); - return expected(unexpect, unex_); - } - } - - template - requires std::is_constructible_v - constexpr auto 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_)); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f), *val_)); - return expected(unexpect, std::move(unex_)); - } - } - - template - requires std::is_constructible_v - constexpr auto 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_); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f), *val_)); - return expected(unexpect, unex_); - } - } - - template - requires std::is_constructible_v - constexpr auto 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_)); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f), *val_)); - return expected(unexpect, std::move(unex_)); - } - } - - // transform_error - template - constexpr auto 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_)); - } - - template - constexpr auto 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_))); - } - - template - constexpr auto 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_)); - } - - template - constexpr auto 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_))); - } - - // ------------------------------------------------------------------------- - // 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_; - E unex_; - }; -}; - -// ============================================================================= -// Partial specialization: expected — reference error type (P2988) -// ============================================================================= - -template -class expected { - static_assert(!std::is_void_v, "T must not be void in expected; use expected"); - static_assert(!std::is_reference_v, "T must not be a reference in expected; use expected"); - static_assert(!std::is_array_v, "T must not be an array type in expected"); - static_assert(std::is_object_v, "T must be an object type in expected"); - 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_object_v, "E must be an object type in expected"); - static_assert(!std::is_array_v, "E must not be an array type in expected"); - - public: - using value_type = T; - using error_type = E&; - using unexpected_type = unexpected>; - - template - using rebind = expected; - - // ------------------------------------------------------------------------- - // Constructors - // ------------------------------------------------------------------------- - - // Default constructor (value-initializes T) - constexpr expected() noexcept(std::is_nothrow_default_constructible_v) - requires std::is_default_constructible_v - : has_val_(true) { - std::construct_at(std::addressof(val_)); - } - - // 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) - : has_val_(rhs.has_val_) { - if (has_val_) - std::construct_at(std::addressof(val_), rhs.val_); - else - unex_ptr_ = rhs.unex_ptr_; - } - - // 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) - : has_val_(rhs.has_val_) { - if (has_val_) - std::construct_at(std::addressof(val_), std::move(rhs.val_)); - else - unex_ptr_ = rhs.unex_ptr_; - } - - // Value constructor - template - requires(!std::is_same_v, std::in_place_t> && - !std::is_same_v, expected> && - !detail::is_unexpected_specialization>::value && std::is_constructible_v) - constexpr explicit(!std::is_convertible_v) expected(U&& v) noexcept(std::is_nothrow_constructible_v) - : has_val_(true) { - std::construct_at(std::addressof(val_), std::forward(v)); - } - - // In-place value construction - template - requires std::is_constructible_v - constexpr explicit expected(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) - : has_val_(true) { - std::construct_at(std::addressof(val_), std::forward(args)...); - } - - template - requires std::is_constructible_v&, Args...> - constexpr explicit expected(std::in_place_t, std::initializer_list il, Args&&... args) : has_val_(true) { - std::construct_at(std::addressof(val_), il, std::forward(args)...); - } - - // Error constructor — binds E& (no temporary allowed) - template - requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr explicit expected(unexpect_t, G&& err) noexcept : has_val_(false) { - E& r = std::forward(err); - unex_ptr_ = std::addressof(r); - } - - // Deleted: argument cannot bind to E& (covers rvalue, const lvalue, and temp-creating cases) - template - requires(detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = delete; - - template - requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = delete; - - // Deleted: no constructor from unexpected (would bind E& to temporary storage in unexpected) - template - constexpr expected(const unexpected&) = delete; - - template - constexpr expected(unexpected&&) = delete; - - // Converting constructor from expected (copy) — mirrors expected's from expected - template - requires(std::is_constructible_v && std::is_convertible_v) - constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) - expected(const expected& rhs) - : has_val_(rhs.has_value()) { - if (has_val_) - std::construct_at(std::addressof(val_), *rhs); - else { - E& r = rhs.error(); - unex_ptr_ = std::addressof(r); - } - } - - // Converting constructor from expected (move) — moves owned value, rebinds error pointer - template - requires(std::is_constructible_v && std::is_convertible_v) - constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) - expected(expected&& rhs) - : has_val_(rhs.has_value()) { - if (has_val_) - std::construct_at(std::addressof(val_), std::move(*rhs)); - else { - E& r = rhs.error(); - unex_ptr_ = std::addressof(r); - } - } - - // ------------------------------------------------------------------------- - // Destructor - // ------------------------------------------------------------------------- - - constexpr ~expected() - requires std::is_trivially_destructible_v - = default; - - constexpr ~expected() - requires(!std::is_trivially_destructible_v) - { - if (has_val_) - std::destroy_at(std::addressof(val_)); - } - - // ------------------------------------------------------------------------- - // Assignment - // ------------------------------------------------------------------------- - - // 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)) - { - if (has_val_ && rhs.has_val_) { - val_ = rhs.val_; - } else if (!has_val_ && !rhs.has_val_) { - unex_ptr_ = rhs.unex_ptr_; - } else if (has_val_) { - std::destroy_at(std::addressof(val_)); - unex_ptr_ = rhs.unex_ptr_; - has_val_ = false; - } else { - std::construct_at(std::addressof(val_), rhs.val_); - has_val_ = true; - } - return *this; - } - - // 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)) - { - if (has_val_ && rhs.has_val_) { - val_ = std::move(rhs.val_); - } else if (!has_val_ && !rhs.has_val_) { - unex_ptr_ = rhs.unex_ptr_; - } else if (has_val_) { - std::destroy_at(std::addressof(val_)); - unex_ptr_ = rhs.unex_ptr_; - has_val_ = false; - } else { - std::construct_at(std::addressof(val_), std::move(rhs.val_)); - has_val_ = true; - } - return *this; - } - - // Value assignment - template - requires(!std::is_same_v, expected> && - !detail::is_unexpected_specialization>::value && - std::is_constructible_v && std::is_assignable_v) - constexpr expected& operator=(U&& v) { - if (has_val_) { - val_ = std::forward(v); - } else { - std::construct_at(std::addressof(val_), std::forward(v)); - has_val_ = true; - } - return *this; - } - - // Deleted: no assignment from unexpected (would rebind E& to temporary storage) - template - constexpr expected& operator=(const unexpected&) = delete; - - template - constexpr expected& operator=(unexpected&&) = delete; - - // emplace — construct T in-place (nothrow required for exception safety when T is destroyed) - template - requires std::is_nothrow_constructible_v - constexpr T& emplace(Args&&... args) noexcept { - if (has_val_) - std::destroy_at(std::addressof(val_)); - has_val_ = false; - std::construct_at(std::addressof(val_), std::forward(args)...); - has_val_ = true; - return val_; - } - - template - requires std::is_nothrow_constructible_v&, Args...> - constexpr T& emplace(std::initializer_list il, Args&&... args) noexcept { - if (has_val_) - std::destroy_at(std::addressof(val_)); - has_val_ = false; - std::construct_at(std::addressof(val_), il, std::forward(args)...); - has_val_ = true; - return val_; - } - - // ------------------------------------------------------------------------- - // 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) - { - if (has_val_ && rhs.has_val_) { - using std::swap; - swap(val_, rhs.val_); - } else if (!has_val_ && !rhs.has_val_) { - std::swap(unex_ptr_, rhs.unex_ptr_); - } else if (has_val_) { - // this has value, rhs has error — exchange - E* tmp = rhs.unex_ptr_; - std::construct_at(std::addressof(rhs.val_), std::move(val_)); - std::destroy_at(std::addressof(val_)); - unex_ptr_ = tmp; - has_val_ = false; - rhs.has_val_ = true; - } else { - rhs.swap(*this); - } - } - - friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } - - // ------------------------------------------------------------------------- - // Observers - // ------------------------------------------------------------------------- - - constexpr T* operator->() noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::addressof(val_); - } - - constexpr const T* operator->() const noexcept { + constexpr const E& error() const& noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::addressof(val_); - } - - constexpr T& operator*() & noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return val_; - } - - constexpr const T& operator*() const& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) + if (has_val_) BEMAN_EXPECTED_TRAP(); #endif - return val_; + return unex_.error(); } - - constexpr T&& operator*() && noexcept { + constexpr E& error() & noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) + if (has_val_) BEMAN_EXPECTED_TRAP(); #endif - return std::move(val_); + return unex_.error(); } - - constexpr const T&& operator*() const&& noexcept { + constexpr const E&& error() const&& noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) + if (has_val_) BEMAN_EXPECTED_TRAP(); #endif - return std::move(val_); - } - - constexpr explicit operator bool() const noexcept { return has_val_; } - constexpr bool has_value() const noexcept { return has_val_; } - - constexpr T& value() & { - static_assert(std::is_copy_constructible_v>, - "value() requires E to be copy constructible"); - if (!has_val_) - throw bad_expected_access>(*unex_ptr_); - return val_; - } - - constexpr const T& value() const& { - static_assert(std::is_copy_constructible_v>, - "value() requires E to be copy constructible"); - if (!has_val_) - throw bad_expected_access>(*unex_ptr_); - return val_; - } - - constexpr T&& 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_ptr_)); - return std::move(val_); - } - - constexpr const T&& value() const&& { - static_assert(std::is_copy_constructible_v> && - std::is_move_constructible_v>, - "value() const&& requires E to be copy and move constructible"); - if (!has_val_) - throw bad_expected_access>(std::move(*unex_ptr_)); - return std::move(val_); + return std::move(unex_).error(); } - - // error() returns E& (shallow const — does not propagate const to the referent) - constexpr E& error() const noexcept { + constexpr E&& error() && noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (has_val_) BEMAN_EXPECTED_TRAP(); #endif - return *unex_ptr_; + return std::move(unex_).error(); } - template - requires(std::is_copy_constructible_v && std::is_convertible_v) + template > constexpr T value_or(U&& def) const& { - return has_val_ ? val_ : static_cast(std::forward(def)); + static_assert(std::is_copy_constructible_v, "value_or requires is_copy_constructible_v"); + static_assert(std::is_convertible_v, "value_or requires is_convertible_v"); + if (has_val_) + return val_; + return static_cast(std::forward(def)); } - template - requires(std::is_move_constructible_v && std::is_convertible_v) + template > constexpr T value_or(U&& def) && { - return has_val_ ? std::move(val_) : static_cast(std::forward(def)); + static_assert(std::is_move_constructible_v, "value_or requires is_move_constructible_v"); + static_assert(std::is_convertible_v, "value_or requires is_convertible_v"); + if (has_val_) + return std::move(val_); + return static_cast(std::forward(def)); + } + + template + requires(std::is_copy_constructible_v && std::is_convertible_v) + constexpr error_value_type error_or(G&& def) const& { + if (!has_val_) + return unex_.error(); + return static_cast(std::forward(def)); } - template > - requires(std::is_copy_constructible_v> && std::is_convertible_v>) - constexpr std::remove_cv_t error_or(G&& def) const { + template + requires(std::is_move_constructible_v && std::is_convertible_v) + constexpr error_value_type error_or(G&& def) && { if (!has_val_) - return *unex_ptr_; - return static_cast>(std::forward(def)); + return std::move(unex_).error(); + return static_cast(std::forward(def)); } // ------------------------------------------------------------------------- - // Monadic operations — all overloads pass E& to callables + // [expected.object.monadic] Monadic operations // ------------------------------------------------------------------------- - // and_then: f receives T (value); error propagates as E& template + requires std::is_constructible_v constexpr auto 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, + 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_ptr_); + return U(unexpect, unex_.error()); } template + requires std::is_constructible_v constexpr auto 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, + 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), std::move(val_)); - return U(unexpect, *unex_ptr_); + return U(unexpect, std::move(unex_).error()); } template + requires std::is_constructible_v constexpr auto 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, + 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_ptr_); + return U(unexpect, unex_.error()); } template + requires std::is_constructible_v constexpr auto 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, + 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), std::move(val_)); - return U(unexpect, *unex_ptr_); + return U(unexpect, std::move(unex_).error()); } - // or_else: f receives E& (the referenced error); value propagates template + requires std::is_constructible_v constexpr auto 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(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_ptr_); + return G(std::in_place, val_); + return std::invoke(std::forward(f), unex_.error()); } template + requires std::is_constructible_v constexpr auto 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"); + 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(std::move(val_)); - return std::invoke(std::forward(f), *unex_ptr_); + return G(std::in_place, std::move(val_)); + return std::invoke(std::forward(f), std::move(unex_).error()); } template + requires std::is_constructible_v constexpr auto 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"); + 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_ptr_); + return G(std::in_place, val_); + return std::invoke(std::forward(f), unex_.error()); } template + requires std::is_constructible_v constexpr auto 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"); + 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(std::move(val_)); - return std::invoke(std::forward(f), *unex_ptr_); + return G(std::in_place, std::move(val_)); + return std::invoke(std::forward(f), std::move(unex_).error()); } - // transform: f receives T (value); error propagates as E& template + requires std::is_constructible_v constexpr auto 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, 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"); @@ -3178,22 +831,22 @@ class expected { if (has_val_) std::invoke(std::forward(f), val_); if (has_val_) - return expected(); - return expected(unexpect, *unex_ptr_); + return expected(); + return expected(unexpect, unex_.error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f), val_)); - return expected(unexpect, *unex_ptr_); + return expected(std::invoke(std::forward(f), val_)); + return expected(unexpect, unex_.error()); } } template + requires std::is_constructible_v constexpr auto 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, 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"); @@ -3202,22 +855,22 @@ class expected { if (has_val_) std::invoke(std::forward(f), std::move(val_)); if (has_val_) - return expected(); - return expected(unexpect, *unex_ptr_); + return expected(); + return expected(unexpect, std::move(unex_).error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f), std::move(val_))); - return expected(unexpect, *unex_ptr_); + return expected(std::invoke(std::forward(f), std::move(val_))); + return expected(unexpect, std::move(unex_).error()); } } template + requires std::is_constructible_v constexpr auto 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, 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"); @@ -3226,22 +879,22 @@ class expected { if (has_val_) std::invoke(std::forward(f), val_); if (has_val_) - return expected(); - return expected(unexpect, *unex_ptr_); + return expected(); + return expected(unexpect, unex_.error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f), val_)); - return expected(unexpect, *unex_ptr_); + return expected(std::invoke(std::forward(f), val_)); + return expected(unexpect, unex_.error()); } } template + requires std::is_constructible_v constexpr auto 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, 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"); @@ -3250,17 +903,17 @@ class expected { if (has_val_) std::invoke(std::forward(f), std::move(val_)); if (has_val_) - return expected(); - return expected(unexpect, *unex_ptr_); + return expected(); + return expected(unexpect, std::move(unex_).error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f), std::move(val_))); - return expected(unexpect, *unex_ptr_); + return expected(std::invoke(std::forward(f), std::move(val_))); + return expected(unexpect, std::move(unex_).error()); } } - // transform_error: f receives E& (the referenced error); value propagates template + requires std::is_constructible_v constexpr auto transform_error(F&& f) & { using G = std::remove_cv_t>; static_assert(std::is_object_v, "transform_error: G must be an object type"); @@ -3269,51 +922,54 @@ class expected { 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_ptr_)); + return expected(std::in_place, val_); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); } template + requires std::is_constructible_v constexpr auto transform_error(F&& f) && { - using G = std::remove_cv_t>; + 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(std::move(val_)); - return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + return expected(std::in_place, std::move(val_)); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } template + requires std::is_constructible_v constexpr auto transform_error(F&& f) const& { - using G = std::remove_cv_t>; + 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_ptr_)); + return expected(std::in_place, val_); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); } template + requires std::is_constructible_v constexpr auto transform_error(F&& f) const&& { - using G = std::remove_cv_t>; + 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(std::move(val_)); - return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + return expected(std::in_place, std::move(val_)); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } // ------------------------------------------------------------------------- - // Equality operators (hidden friends) + // [expected.object.eq] Equality operators (hidden friends) // ------------------------------------------------------------------------- template @@ -3340,176 +996,284 @@ class expected { private: bool has_val_; union { - T val_; - E* unex_ptr_; + T val_; + unexpected unex_; }; }; // ============================================================================= -// Partial specialization: expected — both value and error are references (P2988) +// [expected.void] Partial specialization for void value type // ============================================================================= -template -class expected { - static_assert(!std::is_array_v, "T must not be an array type in expected"); - static_assert(std::is_object_v, "T must be an object type in expected"); - 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_object_v, "E must be an object type in expected"); - static_assert(!std::is_array_v, "E must not be an array type in expected"); +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 error_type = E&; - using unexpected_type = unexpected>; + using value_type = void; + using error_type = E; + using unexpected_type = unexpected; template - using rebind = expected; + using rebind = expected; // ------------------------------------------------------------------------- - // Constructors + // [expected.void.cons] Constructors // ------------------------------------------------------------------------- - expected() = delete; + constexpr expected() noexcept : has_val_(true) {} - // Copy/move constructors — trivial (union holds only pointers + bool has_val_) - constexpr expected(const expected&) = default; - constexpr expected(expected&&) = default; + constexpr expected(const expected&) + requires std::is_trivially_copy_constructible_v> + = default; - // Deleted: no in-place value constructor — T& cannot be constructed in-place - template - constexpr expected(std::in_place_t, Args&&...) = delete; + constexpr 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_); + } - // Value constructor — binds T& from lvalue (dangling prevention via deleted rvalue overload) - template - requires(!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 : has_val_(true) { - T& r = std::forward(u); - val_ = std::addressof(r); + constexpr expected(expected&&) noexcept + 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>) + : has_val_(rhs.has_val_) { + if (!has_val_) + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); } - // 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 where is_void_v + template + requires(std::is_void_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) + : has_val_(rhs.has_value()) { + if (!has_val_) + std::construct_at(std::addressof(unex_), rhs.error()); + } - // Error constructor — binds E& (no temporary allowed) - template - requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr explicit expected(unexpect_t, G&& err) noexcept : has_val_(false) { - E& r = std::forward(err); - unex_ = std::addressof(r); + template + requires(std::is_void_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) + : has_val_(rhs.has_value()) { + if (!has_val_) + std::construct_at(std::addressof(unex_), std::move(rhs.error())); } - // Deleted: argument cannot bind to E& (covers rvalue, const lvalue, and temp-creating cases) + // Constructor from unexpected const& / && — value-E path (SFINAE'd, works as today) template - requires(detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = delete; + requires(!std::is_reference_v && std::is_constructible_v) + constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); + } template - requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = delete; + requires(!std::is_reference_v && std::is_constructible_v) + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::move(e.error())); + } - // Deleted: no constructor from unexpected (would bind E& to temporary storage in unexpected) + // Constructor from unexpected const& / && — reference-E path (mirrors pre-merge expected, + // including its use of const_cast to bind E& through G's lvalue accessor) template - constexpr expected(const unexpected&) = delete; + requires(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 : has_val_(false) { + std::construct_at(std::addressof(unex_), const_cast(e.error())); + } template - constexpr expected(unexpected&&) = delete; + requires(std::is_reference_v && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) noexcept : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); + } - // Converting constructor from expected (copy) - template - requires(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) - : has_val_(rhs.has_value()) { - if (has_val_) { - T& r = *rhs; - val_ = std::addressof(r); - } else { - E& e = rhs.error(); - unex_ = std::addressof(e); - } + // In-place constructor for value (no args, just marks has-value) + constexpr explicit expected(std::in_place_t) noexcept : has_val_(true) {} + + // In-place constructor for error + template + requires(std::is_constructible_v && !detail::unexpect_dangles_v) + constexpr explicit expected(unexpect_t, Args&&... args) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::in_place, std::forward(args)...); } - // Converting constructor from expected (move — pointers, so same as copy) - template - requires(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) + // 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) — mirrors the pre-merge reference + // specializations' three-overload dangling-prevention pattern. + 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) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::in_place, il, std::forward(args)...); + } + + // Converting constructor from expected (reference-E path only, mirrors pre-merge expected) + template + requires(std::is_reference_v && std::is_convertible_v) + constexpr explicit(!std::is_convertible_v) expected(const expected& rhs) : has_val_(rhs.has_value()) { - if (has_val_) { - T& r = *rhs; - val_ = std::addressof(r); - } else { - E& e = rhs.error(); - unex_ = std::addressof(e); - } + if (!has_val_) + std::construct_at(std::addressof(unex_), rhs.error()); } // ------------------------------------------------------------------------- - // Destructor — trivial (union holds only pointers) + // [expected.void.dtor] Destructor // ------------------------------------------------------------------------- - constexpr ~expected() = default; + constexpr ~expected() + requires std::is_trivially_destructible_v> + = default; + + constexpr ~expected() + requires(!std::is_trivially_destructible_v>) + { + if (!has_val_) + std::destroy_at(std::addressof(unex_)); + } // ------------------------------------------------------------------------- - // Assignment + // [expected.void.assign] Assignment // ------------------------------------------------------------------------- - // Copy/move — trivial (just pointers + bool) - constexpr expected& operator=(const expected&) = default; - constexpr expected& operator=(expected&&) = default; + // 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; - // Value rebind — rebinds T* (no destruction needed, pointer is trivial) - 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) noexcept { - T& r = std::forward(u); - val_ = std::addressof(r); - has_val_ = true; + // 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>)) + { + if (has_val_ && rhs.has_val_) { + // both value: no-op + } 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_)); + has_val_ = true; + } + return *this; + } + + // 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>)) + { + 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_) { + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); + has_val_ = false; + } else { + std::destroy_at(std::addressof(unex_)); + has_val_ = true; + } return *this; } - // Deleted: no assignment from unexpected + // Assignment from unexpected — value-E only; reference-E has no such assignment (matches pre-merge + // expected, which never declared one) template - constexpr expected& operator=(const unexpected&) = delete; + requires(!std::is_reference_v && std::is_constructible_v && + std::is_assignable_v) + constexpr 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 - constexpr expected& operator=(unexpected&&) = delete; + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) + constexpr 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; + } - // emplace — rebind T& (pointer transition is trivial) - template - requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr T& emplace(U&& u) noexcept { - T& r = std::forward(u); - val_ = std::addressof(r); - has_val_ = true; - return *val_; + constexpr void emplace() noexcept { + if (!has_val_) { + std::destroy_at(std::addressof(unex_)); + has_val_ = true; + } } // ------------------------------------------------------------------------- - // Swap + // [expected.void.swap] Swap // ------------------------------------------------------------------------- - constexpr void swap(expected& rhs) noexcept { + 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>) + { if (has_val_ && rhs.has_val_) { - std::swap(val_, rhs.val_); + // both value: no-op } else if (!has_val_ && !rhs.has_val_) { - std::swap(unex_, rhs.unex_); + using std::swap; + swap(unex_, rhs.unex_); } else if (has_val_) { - T* my_val = val_; - E* rhs_err = rhs.unex_; - unex_ = rhs_err; - rhs.val_ = my_val; + 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 { @@ -3517,278 +1281,284 @@ class expected { } } - friend constexpr void swap(expected& x, expected& y) noexcept { x.swap(y); } + friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } // ------------------------------------------------------------------------- - // Observers — shallow const on both sides (references don't propagate const) + // [expected.void.obs] Observers // ------------------------------------------------------------------------- - constexpr T* operator->() const noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return val_; - } + constexpr explicit operator bool() const noexcept { return has_val_; } + constexpr bool has_value() const noexcept { return has_val_; } - constexpr T& operator*() const noexcept { + constexpr void operator*() const noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (!has_val_) BEMAN_EXPECTED_TRAP(); #endif - return *val_; } - constexpr explicit operator bool() const noexcept { return has_val_; } - constexpr bool has_value() const noexcept { return has_val_; } - - constexpr T& value() const& { - static_assert(std::is_copy_constructible_v>, - "value() requires E to be copy constructible"); + constexpr void value() const& { + static_assert(std::is_copy_constructible_v, "value() requires E to be copy constructible"); if (!has_val_) - throw bad_expected_access>(*unex_); - return *val_; + throw bad_expected_access(unex_.error()); } - constexpr T& value() && { - static_assert(std::is_copy_constructible_v>, - "value() requires E to be copy constructible"); + constexpr void 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>(*unex_); - return *val_; + throw bad_expected_access(std::move(unex_).error()); } - // error() — shallow const: always returns E& regardless of const on expected - constexpr E& error() const noexcept { + // error() — shallow const for reference E: always returns E& regardless of const on expected + constexpr const E& error() const& noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (has_val_) BEMAN_EXPECTED_TRAP(); #endif - return *unex_; + return unex_.error(); } - - template > - requires(std::is_object_v && !std::is_array_v) - constexpr std::remove_cv_t 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>"); + constexpr E& error() & noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) if (has_val_) - return *val_; - return static_cast(std::forward(def)); + BEMAN_EXPECTED_TRAP(); +#endif + return unex_.error(); + } + constexpr const E&& error() const&& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(unex_).error(); + } + constexpr E&& error() && noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(unex_).error(); } - template > - constexpr std::remove_cv_t error_or(G&& def) const { - static_assert(std::is_copy_constructible_v>, - "error_or requires E to be copy constructible"); - static_assert(std::is_convertible_v>, - "error_or requires is_convertible_v>"); + template + requires(std::is_copy_constructible_v && std::is_convertible_v) + constexpr error_value_type 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_move_constructible_v && std::is_convertible_v) + constexpr error_value_type error_or(G&& def) && { + if (!has_val_) + return std::move(unex_).error(); + return static_cast(std::forward(def)); + } + + // Deleted: value_or is not available for void expected. Gated to reference E only so it plays no role + // (and adds no overload) when E is a value type — matches pre-merge expected's total absence of + // value_or, while preserving pre-merge expected's explicit deletion. + template + requires std::is_reference_v + constexpr void value_or(U&&) const = delete; + // ------------------------------------------------------------------------- - // Monadic operations — T& value side, E& error side (shallow const on both) + // [expected.void.monadic] Monadic operations // ------------------------------------------------------------------------- - // and_then: f receives T& (value); error propagates as E& template + requires std::is_constructible_v constexpr auto and_then(F&& f) & { - using U = std::remove_cvref_t>; + 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, + 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_); + return std::invoke(std::forward(f)); + return U(unexpect, unex_.error()); } template + requires std::is_constructible_v constexpr auto and_then(F&& f) && { - using U = std::remove_cvref_t>; + 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, + 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_); + return std::invoke(std::forward(f)); + return U(unexpect, std::move(unex_).error()); } template + requires std::is_constructible_v constexpr auto and_then(F&& f) const& { - using U = std::remove_cvref_t>; + 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, + 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_); + return std::invoke(std::forward(f)); + return U(unexpect, unex_.error()); } template + requires std::is_constructible_v constexpr auto and_then(F&& f) const&& { - using U = std::remove_cvref_t>; + 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, + 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_); + return std::invoke(std::forward(f)); + return U(unexpect, std::move(unex_).error()); } - // or_else: f receives E& (the referenced error); value propagates as T& template constexpr auto 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(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_); + return G(); + return std::invoke(std::forward(f), unex_.error()); } template constexpr auto 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, + 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_); + return G(); + return std::invoke(std::forward(f), std::move(unex_).error()); } template constexpr auto 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, + 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_); + return G(); + return std::invoke(std::forward(f), unex_.error()); } template constexpr auto 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, + 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_); + return G(); + return std::invoke(std::forward(f), std::move(unex_).error()); } - // transform: f receives T& (value); error propagates as E&; result is expected template + requires std::is_constructible_v constexpr auto transform(F&& f) & { - using U = std::remove_cv_t>; + 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, 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_); + std::invoke(std::forward(f)); if (has_val_) - return expected(); - return expected(unexpect, *unex_); + return expected(); + return expected(unexpect, unex_.error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f), *val_)); - return expected(unexpect, *unex_); + return expected(std::invoke(std::forward(f))); + return expected(unexpect, unex_.error()); } } template + requires std::is_constructible_v constexpr auto transform(F&& f) && { - using U = std::remove_cv_t>; + 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, 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_); + std::invoke(std::forward(f)); if (has_val_) - return expected(); - return expected(unexpect, *unex_); + return expected(); + return expected(unexpect, std::move(unex_).error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f), *val_)); - return expected(unexpect, *unex_); + return expected(std::invoke(std::forward(f))); + return expected(unexpect, std::move(unex_).error()); } } template + requires std::is_constructible_v constexpr auto transform(F&& f) const& { - using U = std::remove_cv_t>; + 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, 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_); + std::invoke(std::forward(f)); if (has_val_) - return expected(); - return expected(unexpect, *unex_); + return expected(); + return expected(unexpect, unex_.error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f), *val_)); - return expected(unexpect, *unex_); + return expected(std::invoke(std::forward(f))); + return expected(unexpect, unex_.error()); } } template + requires std::is_constructible_v constexpr auto transform(F&& f) const&& { - using U = std::remove_cv_t>; + 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, 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_); + std::invoke(std::forward(f)); if (has_val_) - return expected(); - return expected(unexpect, *unex_); + return expected(); + return expected(unexpect, std::move(unex_).error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f), *val_)); - return expected(unexpect, *unex_); + return expected(std::invoke(std::forward(f))); + return expected(unexpect, std::move(unex_).error()); } } - // transform_error: f receives E& (the referenced error); value propagates as T&; result is expected template constexpr auto transform_error(F&& f) & { using G = std::remove_cv_t>; @@ -3798,69 +1568,63 @@ class expected { 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_)); + return expected(); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); } template constexpr auto transform_error(F&& f) && { - using G = std::remove_cv_t>; + 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_)); + return expected(); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } template constexpr auto transform_error(F&& f) const& { - using G = std::remove_cv_t>; + 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_)); + return expected(); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); } template constexpr auto transform_error(F&& f) const&& { - using G = std::remove_cv_t>; + 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_)); + return expected(); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } // ------------------------------------------------------------------------- - // Equality operators (hidden friends) + // [expected.void.eq] Equality operators (hidden friends) // ------------------------------------------------------------------------- template - requires(!std::is_void_v) + 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 true; 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()); @@ -3869,370 +1633,685 @@ class expected { private: bool has_val_; union { - T* val_; - E* unex_; + unexpected unex_; }; }; - // ============================================================================= -// Partial specialization: expected — void value + reference error type (P2988) +// 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_object_v, "E must be an object type in expected"); - static_assert(!std::is_array_v, "E must not be an array type in expected"); +template + requires std::is_lvalue_reference_v +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 = void; - using error_type = E&; - using unexpected_type = unexpected>; + using value_type = T&; + using error_type = E; + using unexpected_type = unexpected; template - using rebind = expected; + using rebind = expected; // ------------------------------------------------------------------------- // Constructors // ------------------------------------------------------------------------- - // Default constructor — void/success state - constexpr expected() noexcept : unex_ptr_(nullptr), has_val_(true) {} + 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>) + : has_val_(rhs.has_val_) { + if (has_val_) + val_ = rhs.val_; + else + std::construct_at(std::addressof(unex_), rhs.unex_); + } + + // 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>) + : has_val_(rhs.has_val_) { + if (has_val_) + val_ = rhs.val_; + else + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); + } + + // 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 : 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 (works as today) + 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) + : has_val_(rhs.has_value()) { + if (has_val_) { + T& r = *rhs; + val_ = std::addressof(r); + } else { + std::construct_at(std::addressof(unex_), rhs.error()); + } + } + + // Converting constructor from expected (move) — value-E path (works as today) + 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) + : 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())); + } + } + + // Converting constructor from expected (copy/move) — reference-E path (mirrors + // pre-merge expected, which only accepted sources whose error type is also a reference) + 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) + : 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 + 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) + : has_val_(rhs.has_value()) { + if (has_val_) { + T& r = *rhs; + val_ = std::addressof(r); + } else { + std::construct_at(std::addressof(unex_), rhs.error()); + } + } + + // Constructor from unexpected const& / && — value-E path (SFINAE'd, works as today) + template + requires(!std::is_reference_v && std::is_constructible_v) + constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), e.error()); + } + + template + requires(!std::is_reference_v && std::is_constructible_v) + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::move(e.error())); + } + + // Deleted for reference E: unexpected stores G by value; binding E& to it would create a + // dangling reference once the unexpected temporary is destroyed (mirrors pre-merge expected). + template + requires std::is_reference_v + constexpr expected(const unexpected&) = delete; + + template + requires 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) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::in_place, std::forward(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) : has_val_(false) { + std::construct_at(std::addressof(unex_), std::in_place, il, std::forward(args)...); + } + + // ------------------------------------------------------------------------- + // Destructor + // ------------------------------------------------------------------------- + + constexpr ~expected() + requires std::is_trivially_destructible_v> + = default; + + constexpr ~expected() + requires(!std::is_trivially_destructible_v>) + { + if (!has_val_) + std::destroy_at(std::addressof(unex_)); + } + + // ------------------------------------------------------------------------- + // 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>)) + { + 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; + } - // Copy/move — trivial (just pointer + bool) - constexpr expected(const expected&) = default; - constexpr expected(expected&&) noexcept = default; + // 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; - // In-place value constructor - constexpr explicit expected(std::in_place_t) noexcept : unex_ptr_(nullptr), has_val_(true) {} + // 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>)) + { + 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; + } - // Error constructor from unexpected const& — E& binds to G's stored value via its lvalue accessor - template - requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) noexcept : has_val_(false) { - E& r = const_cast(e.error()); - unex_ptr_ = std::addressof(r); + // 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 { + std::destroy_at(std::addressof(unex_)); + T& r = std::forward(u); + val_ = std::addressof(r); + has_val_ = true; + } + return *this; } - // Error constructor from unexpected&& (non-const lvalue accessor gives G&, binds to E&) + // Assignment from unexpected — value-E path (SFINAE'd, works as today) template - requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) noexcept : has_val_(false) { - E& r = e.error(); - unex_ptr_ = std::addressof(r); + requires(!std::is_reference_v && std::is_constructible_v && + std::is_assignable_v) + constexpr 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; } - // In-place error constructor — binds E& directly (no temporary allowed) - template - requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr explicit expected(unexpect_t, G&& err) noexcept : has_val_(false) { - E& r = std::forward(err); - unex_ptr_ = std::addressof(r); + template + requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) + constexpr 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; } - // Deleted: argument cannot bind to E& (covers rvalue, const lvalue, and temp-creating cases) + // Deleted for reference E: would rebind E& to unexpected's temporary storage (mirrors + // pre-merge expected). template - requires(detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = delete; + requires std::is_reference_v + constexpr expected& operator=(const unexpected&) = delete; template - requires(!std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr expected(unexpect_t, G&&) = delete; + requires std::is_reference_v + constexpr expected& operator=(unexpected&&) = delete; - // Converting constructor from expected - template - requires std::is_convertible_v - constexpr explicit(!std::is_convertible_v) expected(const expected& rhs) - : has_val_(rhs.has_value()) { + // emplace — rebind the reference + template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) + constexpr T& emplace(U&& u) noexcept { if (!has_val_) { - E& r = rhs.error(); - unex_ptr_ = std::addressof(r); + std::destroy_at(std::addressof(unex_)); + has_val_ = true; } + T& r = std::forward(u); + val_ = std::addressof(r); + return *val_; } - // ------------------------------------------------------------------------- - // Destructor — trivial (pointer + bool only) - // ------------------------------------------------------------------------- - - constexpr ~expected() = default; - - // ------------------------------------------------------------------------- - // Assignment - // ------------------------------------------------------------------------- - - // Copy/move — trivial - constexpr expected& operator=(const expected&) = default; - constexpr expected& operator=(expected&&) noexcept = default; - - // emplace — transition to void success state (always noexcept) - constexpr void emplace() noexcept { has_val_ = true; } - // ------------------------------------------------------------------------- // Swap // ------------------------------------------------------------------------- - constexpr void swap(expected& rhs) noexcept { + 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>) + { if (has_val_ && rhs.has_val_) { - // both success: nothing to do + std::swap(val_, rhs.val_); } else if (!has_val_ && !rhs.has_val_) { - std::swap(unex_ptr_, rhs.unex_ptr_); + using std::swap; + swap(unex_, rhs.unex_); } else if (has_val_) { - unex_ptr_ = rhs.unex_ptr_; - rhs.unex_ptr_ = nullptr; - has_val_ = false; - rhs.has_val_ = true; + // 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); } } - friend constexpr void swap(expected& x, expected& y) noexcept { x.swap(y); } + friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } // ------------------------------------------------------------------------- // Observers // ------------------------------------------------------------------------- - constexpr explicit operator bool() const noexcept { return has_val_; } - constexpr bool has_value() const noexcept { return has_val_; } + constexpr T* operator->() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return val_; + } - constexpr void operator*() const noexcept { + constexpr T& operator*() const noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (!has_val_) BEMAN_EXPECTED_TRAP(); #endif + return *val_; } - constexpr void value() const& { - static_assert(std::is_copy_constructible_v>, - "value() requires E to be copy constructible"); + constexpr explicit operator bool() const noexcept { return has_val_; } + constexpr bool has_value() const noexcept { return has_val_; } + + constexpr T& value() const& { + static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); if (!has_val_) - throw bad_expected_access>(*unex_ptr_); + throw bad_expected_access(unex_.error()); + return *val_; } - constexpr void value() && { - static_assert(std::is_copy_constructible_v> && - std::is_move_constructible_v>, - "value() && requires E to be copy and move constructible"); + constexpr T& 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>(*unex_ptr_); + throw bad_expected_access(std::move(unex_).error()); + return *val_; } // error() — shallow const: always returns E& regardless of const on expected - constexpr E& error() const noexcept { + constexpr const E& error() const& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return unex_.error(); + } + + constexpr E& error() & noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return unex_.error(); + } + + constexpr const E&& error() const&& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(unex_).error(); + } + + constexpr E&& error() && noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (has_val_) BEMAN_EXPECTED_TRAP(); #endif - return *unex_ptr_; + return std::move(unex_).error(); + } + + template > + requires(std::is_object_v && !std::is_array_v) + constexpr std::remove_cv_t 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 > - requires(std::is_copy_constructible_v> && std::is_convertible_v>) - constexpr std::remove_cv_t error_or(G&& def) const { + template + constexpr error_value_type 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"); if (!has_val_) - return *unex_ptr_; - return static_cast>(std::forward(def)); + return unex_.error(); + return static_cast(std::forward(def)); } - // ------------------------------------------------------------------------- - // Deleted: value_or is not available for void expected - template - constexpr void value_or(U&&) const = delete; + template + constexpr error_value_type 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"); + if (!has_val_) + return std::move(unex_).error(); + return static_cast(std::forward(def)); + } - // Monadic operations — void value + E& error + // ------------------------------------------------------------------------- + // Monadic operations // ------------------------------------------------------------------------- - // and_then: F called with no args (void value); error propagates as E& + // and_then template + requires std::is_constructible_v constexpr auto and_then(F&& f) & { - using U = std::remove_cvref_t>; + 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, + 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)); - return U(unexpect, *unex_ptr_); + return std::invoke(std::forward(f), *val_); + return U(unexpect, unex_.error()); } template + requires std::is_constructible_v constexpr auto and_then(F&& f) && { - using U = std::remove_cvref_t>; + 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, + 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)); - return U(unexpect, *unex_ptr_); + return std::invoke(std::forward(f), *val_); + return U(unexpect, std::move(unex_).error()); } template + requires std::is_constructible_v constexpr auto and_then(F&& f) const& { - using U = std::remove_cvref_t>; + 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, + 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)); - return U(unexpect, *unex_ptr_); + return std::invoke(std::forward(f), *val_); + return U(unexpect, unex_.error()); } template + requires std::is_constructible_v constexpr auto and_then(F&& f) const&& { - using U = std::remove_cvref_t>; + 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, + 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)); - return U(unexpect, *unex_ptr_); + return std::invoke(std::forward(f), *val_); + return U(unexpect, std::move(unex_).error()); } - // or_else: F receives E& (the referenced error); value propagates as void success + // or_else template constexpr auto 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_void_v, "or_else: F must return expected with void value_type"); + 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_ptr_); + return G(*val_); + return std::invoke(std::forward(f), unex_.error()); } template constexpr auto or_else(F&& f) && { - using G = std::remove_cvref_t>; + 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_void_v, "or_else: F must return expected with void value_type"); + 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_ptr_); + return G(*val_); + return std::invoke(std::forward(f), std::move(unex_).error()); } template constexpr auto or_else(F&& f) const& { - using G = std::remove_cvref_t>; + 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_void_v, "or_else: F must return expected with void value_type"); + 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_ptr_); + return G(*val_); + return std::invoke(std::forward(f), unex_.error()); } template constexpr auto or_else(F&& f) const&& { - using G = std::remove_cvref_t>; + 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_void_v, "or_else: F must return expected with void value_type"); + 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_ptr_); + return G(*val_); + return std::invoke(std::forward(f), std::move(unex_).error()); } - // transform: F called with no args; error propagates as E& + // transform: f receives T& (value); error propagates as E; result is expected template + requires std::is_constructible_v constexpr auto transform(F&& f) & { - using U = std::remove_cv_t>; + 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, 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)); + std::invoke(std::forward(f), *val_); if (has_val_) - return expected(); - return expected(unexpect, *unex_ptr_); + return expected(); + return expected(unexpect, unex_.error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, *unex_ptr_); + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, unex_.error()); } } template + requires std::is_constructible_v constexpr auto transform(F&& f) && { - using U = std::remove_cv_t>; + 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, 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)); + std::invoke(std::forward(f), *val_); if (has_val_) - return expected(); - return expected(unexpect, *unex_ptr_); + return expected(); + return expected(unexpect, std::move(unex_).error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, *unex_ptr_); + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, std::move(unex_).error()); } } template + requires std::is_constructible_v constexpr auto transform(F&& f) const& { - using U = std::remove_cv_t>; + 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, 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)); + std::invoke(std::forward(f), *val_); if (has_val_) - return expected(); - return expected(unexpect, *unex_ptr_); + return expected(); + return expected(unexpect, unex_.error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, *unex_ptr_); + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, unex_.error()); } } template + requires std::is_constructible_v constexpr auto transform(F&& f) const&& { - using U = std::remove_cv_t>; + 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, 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)); + std::invoke(std::forward(f), *val_); if (has_val_) - return expected(); - return expected(unexpect, *unex_ptr_); + return expected(); + return expected(unexpect, std::move(unex_).error()); } else { if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, *unex_ptr_); + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, std::move(unex_).error()); } } - // transform_error: F receives E&; value propagates as void success + // transform_error: f receives E; value propagates as T&; result is expected template constexpr auto transform_error(F&& f) & { using G = std::remove_cv_t>; @@ -4242,47 +2321,47 @@ class expected { 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_ptr_)); + return expected(*val_); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); } template constexpr auto transform_error(F&& f) && { - using G = std::remove_cv_t>; + 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(); - return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + return expected(*val_); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } template constexpr auto transform_error(F&& f) const& { - using G = std::remove_cv_t>; + 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(); - return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + return expected(*val_); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); } template constexpr auto transform_error(F&& f) const&& { - using G = std::remove_cv_t>; + 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(); - return expected(unexpect, std::invoke(std::forward(f), *unex_ptr_)); + return expected(*val_); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); } // ------------------------------------------------------------------------- @@ -4290,23 +2369,32 @@ class expected { // ------------------------------------------------------------------------- template - requires std::is_void_v + 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 true; + 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: - E* unex_ptr_; bool has_val_; + union { + T* val_; + unexpected unex_; + }; }; } // namespace expected diff --git a/include/beman/expected/unexpected.hpp b/include/beman/expected/unexpected.hpp index b3654f4..9cb86dc 100644 --- a/include/beman/expected/unexpected.hpp +++ b/include/beman/expected/unexpected.hpp @@ -27,6 +27,24 @@ 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; +#else +template +concept reference_converts_from_temporary_v = + std::is_reference_v && + ((!std::is_reference_v && std::is_convertible_v*, std::remove_cvref_t*>) || + (std::is_lvalue_reference_v && std::is_const_v> && + std::is_convertible_v&&> && + !std::is_convertible_v&>)); + +template +concept reference_constructs_from_temporary_v = reference_converts_from_temporary_v; +#endif + } // namespace detail // [expected.unexpected] @@ -92,6 +110,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; dangling prevention mirrors expected's unexpect_t,G&& constructor. + 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 diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index e20a280..1c1f07b 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -183,7 +183,7 @@ add_fail_test(expected_ref_both_or_else_wrong_value_type_fail # Step 7 — expected Mandates: E constraints add_fail_test(expected_ref_e_ref_fail expected_ref_e_ref_fail.cpp - "E must not be a reference" + "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" diff --git a/tests/beman/expected/expected_ref_e_ref_fail.cpp b/tests/beman/expected/expected_ref_e_ref_fail.cpp index 575d3d8..db7f2ac 100644 --- a/tests/beman/expected/expected_ref_e_ref_fail.cpp +++ b/tests/beman/expected/expected_ref_e_ref_fail.cpp @@ -1,7 +1,7 @@ // 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 a reference" +// EXPECT: "E must not be an rvalue reference" #include void test() { int x = 0; From e00081816fa6c4893542a6819d165c069075e145 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 30 Jun 2026 23:37:56 -0400 Subject: [PATCH 37/42] fix: remove history-relative wording from comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several comments referenced "pre-merge" specializations or said things like "works as today" — legible only to someone who knew the prior 6-specialization layout. Reword them to describe the current code's behavior and invariants standalone. --- include/beman/expected/expected.hpp | 57 ++++++++++++--------------- include/beman/expected/unexpected.hpp | 2 +- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 50465a4..a3e060c 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -187,7 +187,7 @@ class expected { std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); } - // Converting copy constructor from expected — value-E path (works as today) + // Converting copy constructor from expected — value-E path template requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && @@ -205,7 +205,7 @@ class expected { std::construct_at(std::addressof(unex_), rhs.error()); } - // Converting move constructor from expected — value-E path (works as today) + // Converting move constructor from expected — value-E path template requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && (std::is_same_v> || !detail::converts_from_any_cvref>) && @@ -221,8 +221,8 @@ class expected { std::construct_at(std::addressof(unex_), std::move(rhs.error())); } - // Converting constructor from expected — reference-E path (mirrors pre-merge - // expected, which only accepted sources whose error type is also a reference) + // 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) @@ -259,7 +259,7 @@ class expected { std::construct_at(std::addressof(val_), std::forward(v)); } - // Constructor from unexpected const& / && — value-E path (SFINAE'd, works as today) + // 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) : has_val_(false) { @@ -273,7 +273,7 @@ class expected { } // Deleted for reference E: unexpected stores G by value; binding E& to it would create a - // dangling reference once the unexpected temporary is destroyed (mirrors pre-merge expected). + // dangling reference once the unexpected temporary is destroyed. template requires std::is_reference_v constexpr expected(const unexpected&) = delete; @@ -309,8 +309,7 @@ class expected { 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) — mirrors the pre-merge reference - // specializations' three-overload dangling-prevention pattern. + // (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) @@ -429,7 +428,7 @@ class expected { return *this; } - // Assignment from unexpected — value-E path (SFINAE'd, works as today) + // Assignment from unexpected — value-E path template requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v && @@ -459,8 +458,7 @@ class expected { return *this; } - // Deleted for reference E: would rebind E& to unexpected's temporary storage (mirrors - // pre-merge expected). + // Deleted for reference E: would rebind E& to unexpected's temporary storage. template requires std::is_reference_v constexpr expected& operator=(const unexpected&) = delete; @@ -1080,7 +1078,7 @@ class expected { std::construct_at(std::addressof(unex_), std::move(rhs.error())); } - // Constructor from unexpected const& / && — value-E path (SFINAE'd, works as today) + // 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) : has_val_(false) { @@ -1093,8 +1091,9 @@ class expected { std::construct_at(std::addressof(unex_), std::move(e.error())); } - // Constructor from unexpected const& / && — reference-E path (mirrors pre-merge expected, - // including its use of const_cast to bind E& through G's lvalue accessor) + // Constructor from unexpected const& / && — reference-E path. The const_cast strips the + // constness introduced by binding e as `const unexpected&`; the referenced G object itself + // is not const, so this does not violate constness of the object actually being pointed to. template requires(std::is_reference_v && std::is_constructible_v && !detail::reference_constructs_from_temporary_v) @@ -1125,8 +1124,7 @@ class expected { 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) — mirrors the pre-merge reference - // specializations' three-overload dangling-prevention pattern. + // (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) @@ -1139,7 +1137,7 @@ class expected { std::construct_at(std::addressof(unex_), std::in_place, il, std::forward(args)...); } - // Converting constructor from expected (reference-E path only, mirrors pre-merge expected) + // 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) @@ -1224,8 +1222,7 @@ class expected { return *this; } - // Assignment from unexpected — value-E only; reference-E has no such assignment (matches pre-merge - // expected, which never declared one) + // Assignment from unexpected — value-E only; no such overload is declared for reference E template requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) @@ -1356,9 +1353,8 @@ class expected { return static_cast(std::forward(def)); } - // Deleted: value_or is not available for void expected. Gated to reference E only so it plays no role - // (and adds no overload) when E is a value type — matches pre-merge expected's total absence of - // value_or, while preserving pre-merge expected's explicit deletion. + // 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; @@ -1723,7 +1719,7 @@ class expected { requires(detail::reference_constructs_from_temporary_v) constexpr expected(U&&) = delete; - // Converting constructor from expected (copy) — value-E path (works as today) + // 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) @@ -1738,7 +1734,7 @@ class expected { } } - // Converting constructor from expected (move) — value-E path (works as today) + // 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) @@ -1752,8 +1748,8 @@ class expected { } } - // Converting constructor from expected (copy/move) — reference-E path (mirrors - // pre-merge expected, which only accepted sources whose error type is also a reference) + // 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) @@ -1782,7 +1778,7 @@ class expected { } } - // Constructor from unexpected const& / && — value-E path (SFINAE'd, works as today) + // 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) : has_val_(false) { @@ -1796,7 +1792,7 @@ class expected { } // Deleted for reference E: unexpected stores G by value; binding E& to it would create a - // dangling reference once the unexpected temporary is destroyed (mirrors pre-merge expected). + // dangling reference once the unexpected temporary is destroyed. template requires std::is_reference_v constexpr expected(const unexpected&) = delete; @@ -1926,7 +1922,7 @@ class expected { return *this; } - // Assignment from unexpected — value-E path (SFINAE'd, works as today) + // Assignment from unexpected — value-E path template requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) @@ -1952,8 +1948,7 @@ class expected { return *this; } - // Deleted for reference E: would rebind E& to unexpected's temporary storage (mirrors - // pre-merge expected). + // Deleted for reference E: would rebind E& to unexpected's temporary storage. template requires std::is_reference_v constexpr expected& operator=(const unexpected&) = delete; diff --git a/include/beman/expected/unexpected.hpp b/include/beman/expected/unexpected.hpp index 9cb86dc..2a01921 100644 --- a/include/beman/expected/unexpected.hpp +++ b/include/beman/expected/unexpected.hpp @@ -127,7 +127,7 @@ class unexpected { constexpr unexpected(const unexpected&) = default; constexpr unexpected(unexpected&&) = default; - // Binds E& directly; dangling prevention mirrors expected's unexpect_t,G&& constructor. + // 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 && From 912a99f4add91b418d798f8395a2629923d05d1c Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Tue, 30 Jun 2026 23:58:28 -0400 Subject: [PATCH 38/42] refactor: split class bodies into declarations and out-of-line definitions Move member function bodies (constructors, destructor, assignment, swap, emplace, observers, monadic operations) out of each expected<> class body into out-of-line definitions afterward, leaving only declarations in the class. This matches std library convention and makes it straightforward to write a synopsis for each specialization separately from its detailed wording. Kept inline where the language requires it: defaulted/deleted special members, and the hidden-friend operator== and swap (moving these out would turn them into ordinary namespace-scope functions and break their ADL-only lookup). expected and expected already used this style before the prior reference-support refactor collapsed them into fully-inline bodies; this restores it and extends it to expected, which had always been fully inline. --- include/beman/expected/expected.hpp | 3933 ++++++++++++++++----------- 1 file changed, 2367 insertions(+), 1566 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index a3e060c..1c670b8 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -147,10 +147,7 @@ class expected { // Default constructor: value-initializes T constexpr expected() noexcept(std::is_nothrow_default_constructible_v) - requires std::is_default_constructible_v - : has_val_(true) { - std::construct_at(std::addressof(val_)); - } + requires std::is_default_constructible_v; // Copy constructor (trivial path) constexpr expected(const expected&) @@ -161,13 +158,7 @@ class expected { 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>)) - : has_val_(rhs.has_val_) { - if (has_val_) - std::construct_at(std::addressof(val_), rhs.val_); - else - std::construct_at(std::addressof(unex_), rhs.unex_); - } + std::is_trivially_copy_constructible_v>)); // Move constructor (trivial path) constexpr expected(expected&&) noexcept @@ -179,13 +170,7 @@ class expected { 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_)); - else - std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); - } + std::is_trivially_move_constructible_v>)); // Converting copy constructor from expected — value-E path template @@ -197,13 +182,7 @@ class expected { !std::is_constructible_v, const expected&> && !std::is_constructible_v, const expected &&>) constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) - 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()); - } + expected(const expected& rhs); // Converting move constructor from expected — value-E path template @@ -213,13 +192,7 @@ class expected { !std::is_constructible_v, expected &&> && !std::is_constructible_v, const expected&> && !std::is_constructible_v, const expected &&>) - constexpr explicit(!std::is_convertible_v || !std::is_convertible_v) 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_), std::move(rhs.error())); - } + 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. @@ -227,25 +200,13 @@ class expected { 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) - : has_val_(rhs.has_value()) { - if (has_val_) - std::construct_at(std::addressof(val_), *rhs); - else - std::construct_at(std::addressof(unex_), rhs.error()); - } + 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) - : 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()); - } + expected(expected&& rhs); // Constructor from value U&& template > @@ -255,22 +216,16 @@ 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& / && — value-E path template requires(!std::is_reference_v && std::is_constructible_v) - constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) : has_val_(false) { - std::construct_at(std::addressof(unex_), e.error()); - } + 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) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::move(e.error())); - } + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e); // Deleted for reference E: unexpected stores G by value; binding E& to it would create a // dangling reference once the unexpected temporary is destroyed. @@ -285,23 +240,17 @@ class expected { // In-place constructor for value template requires std::is_constructible_v - constexpr explicit expected(std::in_place_t, Args&&... args) : has_val_(true) { - std::construct_at(std::addressof(val_), std::forward(args)...); - } + constexpr explicit expected(std::in_place_t, Args&&... args); // In-place constructor for value with initializer_list template requires std::is_constructible_v&, Args...> - constexpr explicit expected(std::in_place_t, std::initializer_list il, Args&&... args) : has_val_(true) { - std::construct_at(std::addressof(val_), il, std::forward(args)...); - } + constexpr explicit expected(std::in_place_t, std::initializer_list il, Args&&... args); // In-place constructor for error template requires(std::is_constructible_v && !detail::unexpect_dangles_v) - constexpr explicit expected(unexpect_t, Args&&... args) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::in_place, std::forward(args)...); - } + constexpr explicit expected(unexpect_t, Args&&... args); // Deleted: single argument would bind E& to a temporary — dangling prevention template @@ -318,9 +267,7 @@ class expected { // 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) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::in_place, il, std::forward(args)...); - } + constexpr explicit expected(unexpect_t, std::initializer_list il, Args&&... args); // ------------------------------------------------------------------------- // [expected.object.dtor] Destructor @@ -331,13 +278,7 @@ class expected { = default; constexpr ~expected() - requires(!(std::is_trivially_destructible_v && std::is_trivially_destructible_v>)) - { - if (has_val_) - std::destroy_at(std::addressof(val_)); - else - std::destroy_at(std::addressof(unex_)); - } + requires(!(std::is_trivially_destructible_v && std::is_trivially_destructible_v>)); // ------------------------------------------------------------------------- // [expected.object.assign] Assignment @@ -359,23 +300,7 @@ class expected { !(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>)) - { - if (has_val_ && rhs.has_val_) { - val_ = rhs.val_; - } else if (!has_val_ && !rhs.has_val_) { - unex_ = rhs.unex_; - } else if (has_val_) { - // was value, now error - detail::reinit_expected(unex_, val_, rhs.unex_); - has_val_ = false; - } else { - // was error, now value - detail::reinit_expected(val_, unex_, rhs.val_); - has_val_ = true; - } - return *this; - } + std::is_trivially_destructible_v>)); // Move assignment (trivial path) constexpr expected& operator=(expected&&) noexcept @@ -395,21 +320,7 @@ class expected { !(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>)) - { - if (has_val_ && rhs.has_val_) { - val_ = std::move(rhs.val_); - } else if (!has_val_ && !rhs.has_val_) { - unex_ = std::move(rhs.unex_); - } else if (has_val_) { - detail::reinit_expected(unex_, val_, std::move(rhs.unex_)); - has_val_ = false; - } else { - detail::reinit_expected(val_, unex_, std::move(rhs.val_)); - has_val_ = true; - } - return *this; - } + std::is_trivially_destructible_v>)); // Assignment from value U&& template > @@ -418,15 +329,7 @@ class expected { 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; - } + constexpr expected& operator=(U&& v); // Assignment from unexpected — value-E path template @@ -434,29 +337,13 @@ class expected { std::is_assignable_v && (std::is_nothrow_constructible_v || std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v>)) - constexpr expected& operator=(const unexpected& e) { - if (!has_val_) { - unex_.error() = e.error(); - } else { - detail::reinit_expected(unex_, val_, e.error()); - has_val_ = false; - } - return *this; - } + constexpr expected& operator=(const unexpected& e); template 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>)) - constexpr expected& operator=(unexpected&& e) { - if (!has_val_) { - unex_.error() = std::move(e.error()); - } else { - detail::reinit_expected(unex_, val_, std::move(e.error())); - has_val_ = false; - } - return *this; - } + constexpr expected& operator=(unexpected&& e); // Deleted for reference E: would rebind E& to unexpected's temporary storage. template @@ -470,27 +357,11 @@ class expected { // Emplace: destroy current value/error, construct value in-place template requires std::is_nothrow_constructible_v - constexpr T& emplace(Args&&... args) noexcept { - if (has_val_) - std::destroy_at(std::addressof(val_)); - else - std::destroy_at(std::addressof(unex_)); - std::construct_at(std::addressof(val_), std::forward(args)...); - has_val_ = true; - return val_; - } + constexpr T& emplace(Args&&... args) noexcept; template requires std::is_nothrow_constructible_v&, Args...> - constexpr T& emplace(std::initializer_list il, Args&&... args) noexcept { - if (has_val_) - std::destroy_at(std::addressof(val_)); - else - std::destroy_at(std::addressof(unex_)); - std::construct_at(std::addressof(val_), il, std::forward(args)...); - has_val_ = true; - return val_; - } + constexpr T& emplace(std::initializer_list il, Args&&... args) noexcept; // ------------------------------------------------------------------------- // [expected.object.swap] Swap @@ -499,54 +370,9 @@ class expected { 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>)) - { - if (has_val_ && rhs.has_val_) { - using std::swap; - 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, rhs has error - 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_)); - std::destroy_at(std::addressof(val_)); - std::construct_at(std::addressof(unex_), std::move(tmp)); - } else { - try { - std::construct_at(std::addressof(rhs.val_), std::move(val_)); - std::destroy_at(std::addressof(val_)); - std::construct_at(std::addressof(unex_), std::move(tmp)); - } catch (...) { - std::construct_at(std::addressof(rhs.unex_), std::move(tmp)); - throw; - } - } - } else { - T tmp(std::move(val_)); - std::destroy_at(std::addressof(val_)); - try { - std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); - std::destroy_at(std::addressof(rhs.unex_)); - std::construct_at(std::addressof(rhs.val_), std::move(tmp)); - } catch (...) { - std::construct_at(std::addressof(val_), std::move(tmp)); - throw; - } - } - has_val_ = false; - rhs.has_val_ = true; - } else { - // this has error, rhs has value - rhs.swap(*this); - } - } + 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); } @@ -554,161 +380,40 @@ class expected { // [expected.object.obs] Observers // ------------------------------------------------------------------------- - constexpr const T* operator->() const noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::addressof(val_); - } - - constexpr T* operator->() noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::addressof(val_); - } - - constexpr const T& operator*() const& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return val_; - } - - constexpr T& operator*() & noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return val_; - } - - constexpr const T&& operator*() const&& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::move(val_); - } - - constexpr T&& operator*() && noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::move(val_); - } - - constexpr explicit operator bool() const noexcept { return has_val_; } - constexpr bool has_value() const noexcept { return has_val_; } - - constexpr const T& 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_; - } + constexpr const T* operator->() const noexcept; + constexpr T* operator->() noexcept; - constexpr T& value() & { - static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); - if (!has_val_) - throw bad_expected_access(unex_.error()); - return val_; - } + constexpr const T& operator*() const& noexcept; + constexpr T& operator*() & noexcept; + constexpr const T&& operator*() const&& noexcept; + constexpr T&& operator*() && noexcept; - constexpr const T&& value() const&& { - 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_).error()); - return std::move(val_); - } + constexpr explicit operator bool() const noexcept; + constexpr bool has_value() const noexcept; - constexpr T&& value() && { - 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_).error()); - return std::move(val_); - } + constexpr const T& value() const&; + constexpr T& value() &; + constexpr const T&& value() const&&; + constexpr T&& value() &&; - constexpr const E& error() const& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return unex_.error(); - } - constexpr E& error() & noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return unex_.error(); - } - constexpr const E&& error() const&& noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::move(unex_).error(); - } - constexpr E&& error() && noexcept { -#if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); -#endif - return std::move(unex_).error(); - } + constexpr const E& error() const& noexcept; + constexpr E& error() & noexcept; + constexpr const E&& error() const&& noexcept; + constexpr E&& error() && noexcept; template > - constexpr T value_or(U&& def) const& { - static_assert(std::is_copy_constructible_v, "value_or requires is_copy_constructible_v"); - static_assert(std::is_convertible_v, "value_or requires is_convertible_v"); - if (has_val_) - return val_; - return static_cast(std::forward(def)); - } + constexpr T value_or(U&& def) const&; template > - constexpr T value_or(U&& def) && { - static_assert(std::is_move_constructible_v, "value_or requires is_move_constructible_v"); - static_assert(std::is_convertible_v, "value_or requires is_convertible_v"); - if (has_val_) - return std::move(val_); - return static_cast(std::forward(def)); - } + constexpr T value_or(U&& def) &&; template requires(std::is_copy_constructible_v && std::is_convertible_v) - constexpr error_value_type error_or(G&& def) const& { - if (!has_val_) - return unex_.error(); - return static_cast(std::forward(def)); - } + 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) && { - if (!has_val_) - return std::move(unex_).error(); - return static_cast(std::forward(def)); - } + constexpr error_value_type error_or(G&& def) &&; // ------------------------------------------------------------------------- // [expected.object.monadic] Monadic operations @@ -716,255 +421,55 @@ class expected { template requires std::is_constructible_v - constexpr auto 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()); - } - + constexpr auto and_then(F&& f) &; template requires std::is_constructible_v - constexpr auto 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), std::move(val_)); - return U(unexpect, std::move(unex_).error()); - } - + constexpr auto and_then(F&& f) &&; template requires std::is_constructible_v - constexpr auto 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()); - } - + constexpr auto and_then(F&& f) const&; template requires std::is_constructible_v - constexpr auto 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), std::move(val_)); - return U(unexpect, std::move(unex_).error()); - } + constexpr auto and_then(F&& f) const&&; template requires std::is_constructible_v - constexpr auto 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(std::in_place, val_); - return std::invoke(std::forward(f), unex_.error()); - } - + constexpr auto or_else(F&& f) &; template requires std::is_constructible_v - constexpr auto 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(std::in_place, std::move(val_)); - return std::invoke(std::forward(f), std::move(unex_).error()); - } - + constexpr auto or_else(F&& f) &&; template requires std::is_constructible_v - constexpr auto 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(std::in_place, val_); - return std::invoke(std::forward(f), unex_.error()); - } - + constexpr auto or_else(F&& f) const&; template requires std::is_constructible_v - constexpr auto 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(std::in_place, std::move(val_)); - return std::invoke(std::forward(f), std::move(unex_).error()); - } + constexpr auto or_else(F&& f) const&&; template requires std::is_constructible_v - constexpr auto 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()); - } - } - + constexpr auto transform(F&& f) &; template requires std::is_constructible_v - constexpr auto 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), std::move(val_)); - if (has_val_) - return expected(); - 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_).error()); - } - } - + constexpr auto transform(F&& f) &&; template requires std::is_constructible_v - constexpr auto 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()); - } - } - + constexpr auto transform(F&& f) const&; template requires std::is_constructible_v - constexpr auto 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), std::move(val_)); - if (has_val_) - return expected(); - 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_).error()); - } - } + constexpr auto transform(F&& f) const&&; template requires std::is_constructible_v - constexpr auto 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(std::in_place, val_); - return expected(unexpect, std::invoke(std::forward(f), unex_.error())); - } - + constexpr auto transform_error(F&& f) &; template requires std::is_constructible_v - constexpr auto 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(std::in_place, std::move(val_)); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); - } - + constexpr auto transform_error(F&& f) &&; template requires std::is_constructible_v - constexpr auto 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(std::in_place, val_); - return expected(unexpect, std::invoke(std::forward(f), unex_.error())); - } - + constexpr auto transform_error(F&& f) const&; template requires std::is_constructible_v - constexpr auto 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(std::in_place, std::move(val_)); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); - } + constexpr auto transform_error(F&& f) const&&; // ------------------------------------------------------------------------- // [expected.object.eq] Equality operators (hidden friends) @@ -1000,96 +505,874 @@ class expected { }; // ============================================================================= -// [expected.void] Partial specialization for void value type +// [expected.object.cons] Out-of-line constructor definitions // ============================================================================= -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"); +template +constexpr expected::expected() noexcept(std::is_nothrow_default_constructible_v) + requires std::is_default_constructible_v + : has_val_(true) { + std::construct_at(std::addressof(val_)); +} - private: - using error_value_type = std::remove_cv_t>; +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>)) + : has_val_(rhs.has_val_) { + if (has_val_) + std::construct_at(std::addressof(val_), rhs.val_); + else + std::construct_at(std::addressof(unex_), rhs.unex_); +} - public: - using value_type = void; - using error_type = E; - using unexpected_type = unexpected; +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>)) + : has_val_(rhs.has_val_) { + if (has_val_) + std::construct_at(std::addressof(val_), std::move(rhs.val_)); + else + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); +} - template - using rebind = expected; +template +template + 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 &&> && + !std::is_constructible_v, const expected&> && + !std::is_constructible_v, const expected &&>) +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()); +} - // ------------------------------------------------------------------------- - // [expected.void.cons] Constructors - // ------------------------------------------------------------------------- +template +template + 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 &&> && + !std::is_constructible_v, const expected&> && + !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)); + else + std::construct_at(std::addressof(unex_), std::move(rhs.error())); +} - constexpr expected() noexcept : has_val_(true) {} +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()); +} - constexpr expected(const expected&) - requires std::is_trivially_copy_constructible_v> - = default; +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()); +} - constexpr 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 +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)); +} - constexpr expected(expected&&) noexcept - requires std::is_trivially_move_constructible_v> - = default; +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()); +} - constexpr 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 +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())); +} - // Converting constructor from expected where is_void_v - template - requires(std::is_void_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) - : has_val_(rhs.has_value()) { - if (!has_val_) - std::construct_at(std::addressof(unex_), rhs.error()); - } +template +template + requires std::is_constructible_v +constexpr expected::expected(std::in_place_t, Args&&... args) : has_val_(true) { + std::construct_at(std::addressof(val_), std::forward(args)...); +} - template - requires(std::is_void_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) - : has_val_(rhs.has_value()) { - if (!has_val_) - std::construct_at(std::addressof(unex_), std::move(rhs.error())); - } +template +template + requires std::is_constructible_v&, Args...> +constexpr expected::expected(std::in_place_t, std::initializer_list il, Args&&... args) : has_val_(true) { + std::construct_at(std::addressof(val_), il, std::forward(args)...); +} - // Constructor from unexpected const& / && — value-E path +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)...); +} + +// ============================================================================= +// [expected.object.dtor] Out-of-line destructor +// ============================================================================= + +template +constexpr expected::~expected() + requires(!(std::is_trivially_destructible_v && std::is_trivially_destructible_v>)) +{ + if (has_val_) + std::destroy_at(std::addressof(val_)); + else + std::destroy_at(std::addressof(unex_)); +} + +// ============================================================================= +// [expected.object.assign] 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_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>)) +{ + if (has_val_ && rhs.has_val_) { + val_ = rhs.val_; + } else if (!has_val_ && !rhs.has_val_) { + unex_ = rhs.unex_; + } else if (has_val_) { + // was value, now error + detail::reinit_expected(unex_, val_, rhs.unex_); + has_val_ = false; + } else { + // was error, now value + detail::reinit_expected(val_, unex_, 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 && + 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>)) +{ + if (has_val_ && rhs.has_val_) { + val_ = std::move(rhs.val_); + } else if (!has_val_ && !rhs.has_val_) { + unex_ = std::move(rhs.unex_); + } else if (has_val_) { + detail::reinit_expected(unex_, val_, std::move(rhs.unex_)); + has_val_ = false; + } else { + detail::reinit_expected(val_, unex_, std::move(rhs.val_)); + has_val_ = true; + } + 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_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>)) +constexpr expected& expected::operator=(const unexpected& e) { + if (!has_val_) { + unex_.error() = e.error(); + } else { + detail::reinit_expected(unex_, val_, e.error()); + has_val_ = false; + } + return *this; +} + +template +template + 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>)) +constexpr expected& expected::operator=(unexpected&& e) { + if (!has_val_) { + unex_.error() = std::move(e.error()); + } else { + detail::reinit_expected(unex_, val_, std::move(e.error())); + has_val_ = false; + } + return *this; +} + +// ============================================================================= +// [expected.object.assign] Out-of-line emplace definitions +// ============================================================================= + +template +template + requires std::is_nothrow_constructible_v +constexpr T& expected::emplace(Args&&... args) noexcept { + if (has_val_) + std::destroy_at(std::addressof(val_)); + else + std::destroy_at(std::addressof(unex_)); + std::construct_at(std::addressof(val_), std::forward(args)...); + has_val_ = true; + return val_; +} + +template +template + requires std::is_nothrow_constructible_v&, Args...> +constexpr T& expected::emplace(std::initializer_list il, Args&&... args) noexcept { + if (has_val_) + std::destroy_at(std::addressof(val_)); + else + std::destroy_at(std::addressof(unex_)); + std::construct_at(std::addressof(val_), il, std::forward(args)...); + has_val_ = true; + return val_; +} + +// ============================================================================= +// [expected.object.swap] Out-of-line swap definition +// ============================================================================= + +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_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; + 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, rhs has error + 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_)); + std::destroy_at(std::addressof(val_)); + std::construct_at(std::addressof(unex_), std::move(tmp)); + } else { + try { + std::construct_at(std::addressof(rhs.val_), std::move(val_)); + std::destroy_at(std::addressof(val_)); + std::construct_at(std::addressof(unex_), std::move(tmp)); + } catch (...) { + std::construct_at(std::addressof(rhs.unex_), std::move(tmp)); + throw; + } + } + } else { + T tmp(std::move(val_)); + std::destroy_at(std::addressof(val_)); + try { + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); + std::destroy_at(std::addressof(rhs.unex_)); + std::construct_at(std::addressof(rhs.val_), std::move(tmp)); + } catch (...) { + std::construct_at(std::addressof(val_), std::move(tmp)); + throw; + } + } + has_val_ = false; + rhs.has_val_ = true; + } else { + // this has error, rhs has value + rhs.swap(*this); + } +} + +// ============================================================================= +// [expected.object.obs] Out-of-line observer definitions +// ============================================================================= + +template +constexpr const T* expected::operator->() const noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::addressof(val_); +} + +template +constexpr T* expected::operator->() noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::addressof(val_); +} + +template +constexpr const T& expected::operator*() const& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return val_; +} + +template +constexpr T& expected::operator*() & noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return val_; +} + +template +constexpr const T&& expected::operator*() const&& noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(val_); +} + +template +constexpr T&& expected::operator*() && noexcept { +#if defined(BEMAN_EXPECTED_HARDENED) + if (!has_val_) + BEMAN_EXPECTED_TRAP(); +#endif + return std::move(val_); +} + +template +constexpr expected::operator bool() const noexcept { + return has_val_; +} + +template +constexpr bool expected::has_value() const noexcept { + return has_val_; +} + +template +constexpr const 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() & { + 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 const T&& expected::value() const&& { + 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_).error()); + return std::move(val_); +} + +template +constexpr T&& expected::value() && { + 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_).error()); + return std::move(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 +constexpr T expected::value_or(U&& def) const& { + static_assert(std::is_copy_constructible_v, "value_or requires is_copy_constructible_v"); + 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 +constexpr T expected::value_or(U&& def) && { + static_assert(std::is_move_constructible_v, "value_or requires is_move_constructible_v"); + static_assert(std::is_convertible_v, "value_or requires is_convertible_v"); + if (has_val_) + return std::move(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)); +} + +// ============================================================================= +// [expected.object.monadic] 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), std::move(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), std::move(val_)); + return U(unexpect, std::move(unex_).error()); +} + +template +template + requires std::is_constructible_v +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(std::in_place, val_); + return std::invoke(std::forward(f), unex_.error()); +} + +template +template + requires std::is_constructible_v +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(std::in_place, std::move(val_)); + return std::invoke(std::forward(f), std::move(unex_).error()); +} + +template +template + requires std::is_constructible_v +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(std::in_place, val_); + return std::invoke(std::forward(f), unex_.error()); +} + +template +template + requires std::is_constructible_v +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(std::in_place, std::move(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), std::move(val_)); + if (has_val_) + return expected(); + 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_).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), std::move(val_)); + if (has_val_) + return expected(); + 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_).error()); + } +} + +template +template + requires std::is_constructible_v +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(std::in_place, val_); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); +} + +template +template + requires std::is_constructible_v +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(std::in_place, std::move(val_)); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); +} + +template +template + requires std::is_constructible_v +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(std::in_place, val_); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); +} + +template +template + requires std::is_constructible_v +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(std::in_place, std::move(val_)); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); +} + +// ============================================================================= +// [expected.void] Partial specialization for void value type +// ============================================================================= + +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 = void; + using error_type = E; + using unexpected_type = unexpected; + + template + using rebind = expected; + + // ------------------------------------------------------------------------- + // [expected.void.cons] Constructors + // ------------------------------------------------------------------------- + + constexpr expected() noexcept; + + constexpr expected(const expected&) + 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>); + + constexpr expected(expected&&) noexcept + 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>); + + // Converting constructor from expected where is_void_v + template + requires(std::is_void_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 && + !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& / && — value-E path template requires(!std::is_reference_v && std::is_constructible_v) - constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) : has_val_(false) { - std::construct_at(std::addressof(unex_), e.error()); - } + 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) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::move(e.error())); - } + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e); // Constructor from unexpected const& / && — reference-E path. The const_cast strips the // constness introduced by binding e as `const unexpected&`; the referenced G object itself @@ -1097,26 +1380,20 @@ class expected { template requires(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 : has_val_(false) { - std::construct_at(std::addressof(unex_), const_cast(e.error())); - } + constexpr explicit(!std::is_convertible_v) expected(const unexpected& e) noexcept; template requires(std::is_reference_v && std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) noexcept : has_val_(false) { - std::construct_at(std::addressof(unex_), e.error()); - } + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) noexcept; // 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 && !detail::unexpect_dangles_v) - constexpr explicit expected(unexpect_t, Args&&... args) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::in_place, std::forward(args)...); - } + constexpr explicit expected(unexpect_t, Args&&... args); // Deleted: single argument would bind E& to a temporary — dangling prevention template @@ -1133,18 +1410,12 @@ class expected { // 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) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::in_place, il, std::forward(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) - : has_val_(rhs.has_value()) { - if (!has_val_) - std::construct_at(std::addressof(unex_), rhs.error()); - } + constexpr explicit(!std::is_convertible_v) expected(const expected& rhs); // ------------------------------------------------------------------------- // [expected.void.dtor] Destructor @@ -1155,483 +1426,745 @@ class expected { = default; constexpr ~expected() - requires(!std::is_trivially_destructible_v>) - { - if (!has_val_) - std::destroy_at(std::addressof(unex_)); + requires(!std::is_trivially_destructible_v>); + + // ------------------------------------------------------------------------- + // [expected.void.assign] Assignment + // ------------------------------------------------------------------------- + + // 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>)); + + // Assignment from unexpected — value-E only; no such overload is declared for reference E + 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); + + constexpr void emplace() noexcept; + + // ------------------------------------------------------------------------- + // [expected.void.swap] 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); } + + // ------------------------------------------------------------------------- + // [expected.void.obs] Observers + // ------------------------------------------------------------------------- + + constexpr explicit operator bool() const noexcept; + constexpr bool has_value() const noexcept; + + constexpr void operator*() const noexcept; + + constexpr void value() const&; + constexpr void value() &&; + + // 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 + 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) &&; + + // 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 + // ------------------------------------------------------------------------- + + 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&&; + + 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&&; + + 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&&; + + // ------------------------------------------------------------------------- + // [expected.void.eq] 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 true; + return x.error() == y.error(); } - // ------------------------------------------------------------------------- - // [expected.void.assign] Assignment - // ------------------------------------------------------------------------- + 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 { + unexpected unex_; + }; +}; + +// ============================================================================= +// [expected.void.cons] Out-of-line constructor definitions +// ============================================================================= + +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 +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 +template + requires(std::is_void_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()) { + if (!has_val_) + std::construct_at(std::addressof(unex_), rhs.error()); +} + +template +template + requires(std::is_void_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()) { + if (!has_val_) + std::construct_at(std::addressof(unex_), std::move(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_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected::expected(const unexpected& e) noexcept : has_val_(false) { + std::construct_at(std::addressof(unex_), const_cast(e.error())); +} + +template +template + requires(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)...); +} + +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 +// ============================================================================= - // 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; +template +constexpr expected::~expected() + requires(!std::is_trivially_destructible_v>) +{ + if (!has_val_) + std::destroy_at(std::addressof(unex_)); +} - // 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>)) - { - if (has_val_ && rhs.has_val_) { - // both value: no-op - } 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_)); - has_val_ = true; - } - return *this; - } +// ============================================================================= +// [expected.void.assign] Out-of-line assignment definitions +// ============================================================================= - // 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; +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_) { + std::construct_at(std::addressof(unex_), rhs.unex_); + has_val_ = false; + } else { + std::destroy_at(std::addressof(unex_)); + has_val_ = true; + } + return *this; +} - // 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>)) - { - 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_) { - std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); - has_val_ = false; - } else { - std::destroy_at(std::addressof(unex_)); - 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_) { + // both value: no-op + } 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_)); + has_val_ = true; } + return *this; +} - // Assignment from unexpected — value-E only; no such overload is declared for reference E - template - requires(!std::is_reference_v && std::is_constructible_v && - std::is_assignable_v) - constexpr 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=(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 - requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) - constexpr 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; +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; +} - constexpr void emplace() noexcept { - if (!has_val_) { - std::destroy_at(std::addressof(unex_)); - has_val_ = true; - } +template +constexpr void expected::emplace() noexcept { + if (!has_val_) { + std::destroy_at(std::addressof(unex_)); + has_val_ = true; } +} - // ------------------------------------------------------------------------- - // [expected.void.swap] Swap - // ------------------------------------------------------------------------- +// ============================================================================= +// [expected.void.swap] Out-of-line swap definition +// ============================================================================= - 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>) - { - if (has_val_ && rhs.has_val_) { - // both value: no-op - } else if (!has_val_ && !rhs.has_val_) { - using std::swap; - swap(unex_, rhs.unex_); - } else if (has_val_) { - 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 { - rhs.swap(*this); - } +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 + } else if (!has_val_ && !rhs.has_val_) { + using std::swap; + swap(unex_, rhs.unex_); + } else if (has_val_) { + 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 { + rhs.swap(*this); } +} - friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } +// ============================================================================= +// [expected.void.obs] Out-of-line observer definitions +// ============================================================================= - // ------------------------------------------------------------------------- - // [expected.void.obs] Observers - // ------------------------------------------------------------------------- +template +constexpr expected::operator bool() const noexcept { + return has_val_; +} - constexpr explicit operator bool() const noexcept { return has_val_; } - constexpr bool has_value() const noexcept { return has_val_; } +template +constexpr bool expected::has_value() const noexcept { + return has_val_; +} - constexpr void operator*() const noexcept { +template +constexpr void expected::operator*() const noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); + if (!has_val_) + BEMAN_EXPECTED_TRAP(); #endif - } +} - constexpr void value() const& { - static_assert(std::is_copy_constructible_v, "value() requires E to be copy constructible"); - if (!has_val_) - throw bad_expected_access(unex_.error()); - } +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(unex_.error()); +} - constexpr void 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 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()); +} - // error() — shallow const for reference E: always returns E& regardless of const on expected - constexpr const E& error() const& noexcept { +template +constexpr const E& expected::error() const& noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); + if (has_val_) + BEMAN_EXPECTED_TRAP(); #endif - return unex_.error(); - } - constexpr E& error() & noexcept { + return unex_.error(); +} + +template +constexpr E& expected::error() & noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); + if (has_val_) + BEMAN_EXPECTED_TRAP(); #endif - return unex_.error(); - } - constexpr const E&& error() const&& noexcept { + return unex_.error(); +} + +template +constexpr const E&& expected::error() const&& noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); + if (has_val_) + BEMAN_EXPECTED_TRAP(); #endif - return std::move(unex_).error(); - } - constexpr E&& error() && noexcept { + return std::move(unex_).error(); +} + +template +constexpr E&& expected::error() && noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); + if (has_val_) + BEMAN_EXPECTED_TRAP(); #endif + return std::move(unex_).error(); +} + +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)); +} - template - requires(std::is_copy_constructible_v && std::is_convertible_v) - constexpr error_value_type error_or(G&& def) const& { - if (!has_val_) - return unex_.error(); - return static_cast(std::forward(def)); - } +// ============================================================================= +// [expected.void.monadic] Out-of-line monadic operation definitions +// ============================================================================= - template - requires(std::is_move_constructible_v && std::is_convertible_v) - constexpr error_value_type error_or(G&& def) && { - if (!has_val_) - return std::move(unex_).error(); - return static_cast(std::forward(def)); - } +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)); + return U(unexpect, unex_.error()); +} - // 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; +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)); + return U(unexpect, std::move(unex_).error()); +} - // ------------------------------------------------------------------------- - // [expected.void.monadic] Monadic operations - // ------------------------------------------------------------------------- +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)); + return U(unexpect, unex_.error()); +} - template - requires std::is_constructible_v - constexpr auto 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)); - 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)); + return U(unexpect, std::move(unex_).error()); +} - template - requires std::is_constructible_v - constexpr auto 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)); - 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(); + return std::invoke(std::forward(f), unex_.error()); +} - template - requires std::is_constructible_v - constexpr auto 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)); - return U(unexpect, 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(); + return std::invoke(std::forward(f), std::move(unex_).error()); +} - template - requires std::is_constructible_v - constexpr auto 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)); - return U(unexpect, 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(); + return std::invoke(std::forward(f), unex_.error()); +} - template - constexpr auto 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(); - 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(); + return std::invoke(std::forward(f), std::move(unex_).error()); +} - template - constexpr auto 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"); +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_) - return G(); - return std::invoke(std::forward(f), std::move(unex_).error()); - } - - template - constexpr auto 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"); + std::invoke(std::forward(f)); if (has_val_) - return G(); - return std::invoke(std::forward(f), unex_.error()); - } - - template - constexpr auto 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"); + return expected(); + return expected(unexpect, unex_.error()); + } else { if (has_val_) - return G(); - return std::invoke(std::forward(f), std::move(unex_).error()); - } - - template - requires std::is_constructible_v - constexpr auto 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)); - if (has_val_) - return expected(); - return expected(unexpect, unex_.error()); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, unex_.error()); - } - } - - template - requires std::is_constructible_v - constexpr auto 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)); - if (has_val_) - return expected(); - return expected(unexpect, std::move(unex_).error()); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, std::move(unex_).error()); - } - } - - template - requires std::is_constructible_v - constexpr auto 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)); - if (has_val_) - return expected(); - return expected(unexpect, unex_.error()); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, unex_.error()); - } - } - - template - requires std::is_constructible_v - constexpr auto 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)); - if (has_val_) - return expected(); - return expected(unexpect, std::move(unex_).error()); - } else { - if (has_val_) - return expected(std::invoke(std::forward(f))); - return expected(unexpect, std::move(unex_).error()); - } + return expected(std::invoke(std::forward(f))); + return expected(unexpect, unex_.error()); } +} - template - constexpr auto 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"); +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_) - return expected(); - return expected(unexpect, std::invoke(std::forward(f), unex_.error())); - } - - template - constexpr auto 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"); + std::invoke(std::forward(f)); + if (has_val_) + return expected(); + return expected(unexpect, std::move(unex_).error()); + } else { if (has_val_) - return expected(); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); + return expected(std::invoke(std::forward(f))); + return expected(unexpect, std::move(unex_).error()); } +} - template - constexpr auto 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"); +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_) - return expected(); - return expected(unexpect, std::invoke(std::forward(f), unex_.error())); + std::invoke(std::forward(f)); + if (has_val_) + return expected(); + return expected(unexpect, unex_.error()); + } else { + if (has_val_) + return expected(std::invoke(std::forward(f))); + return expected(unexpect, unex_.error()); } +} - template - constexpr auto 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"); +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)); + if (has_val_) + return expected(); + return expected(unexpect, std::move(unex_).error()); + } else { if (has_val_) - return expected(); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); + return expected(std::invoke(std::forward(f))); + return expected(unexpect, std::move(unex_).error()); } +} - // ------------------------------------------------------------------------- - // [expected.void.eq] Equality operators (hidden friends) - // ------------------------------------------------------------------------- +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(); + return expected(unexpect, std::invoke(std::forward(f), unex_.error())); +} - 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 true; - return x.error() == y.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(); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); +} - template - friend constexpr bool operator==(const expected& x, const unexpected& e) { - return !x.has_value() && static_cast(x.error() == e.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(); + 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(); + return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); +} - private: - bool has_val_; - union { - unexpected unex_; - }; -}; // ============================================================================= // Partial specialization: expected — reference value type // (E may be an object type or an lvalue reference to one) @@ -1676,13 +2209,7 @@ class expected { // 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>) - : has_val_(rhs.has_val_) { - if (has_val_) - val_ = rhs.val_; - else - std::construct_at(std::addressof(unex_), rhs.unex_); - } + requires(std::is_copy_constructible_v> && !std::is_trivially_copy_constructible_v>); // Move constructor (trivial path) constexpr expected(expected&&) noexcept @@ -1691,13 +2218,7 @@ class expected { // 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>) - : has_val_(rhs.has_val_) { - if (has_val_) - val_ = rhs.val_; - else - std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); - } + 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 @@ -1709,10 +2230,7 @@ class expected { !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 : has_val_(true) { - T& r = std::forward(u); - val_ = std::addressof(r); - } + constexpr explicit(!std::is_convertible_v) expected(U&& u) noexcept; // Deleted: binding a temporary to T& creates a dangling reference template @@ -1724,29 +2242,13 @@ class expected { 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) - : has_val_(rhs.has_value()) { - if (has_val_) { - T& r = *rhs; - val_ = std::addressof(r); - } else { - std::construct_at(std::addressof(unex_), rhs.error()); - } - } + 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) - : 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())); - } - } + 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. @@ -1754,42 +2256,22 @@ class expected { 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) - : has_val_(rhs.has_value()) { - if (has_val_) { - T& r = *rhs; - val_ = std::addressof(r); - } else { - std::construct_at(std::addressof(unex_), rhs.error()); - } - } + 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) - : has_val_(rhs.has_value()) { - if (has_val_) { - T& r = *rhs; - val_ = std::addressof(r); - } else { - std::construct_at(std::addressof(unex_), rhs.error()); - } - } + 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) : has_val_(false) { - std::construct_at(std::addressof(unex_), e.error()); - } + 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) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::move(e.error())); - } + constexpr explicit(!std::is_convertible_v) expected(unexpected&& e); // Deleted for reference E: unexpected stores G by value; binding E& to it would create a // dangling reference once the unexpected temporary is destroyed. @@ -1804,9 +2286,7 @@ class expected { // In-place constructor for error template requires(std::is_constructible_v && !detail::unexpect_dangles_v) - constexpr explicit expected(unexpect_t, Args&&... args) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::in_place, std::forward(args)...); - } + constexpr explicit expected(unexpect_t, Args&&... args); // Deleted: single argument would bind E& to a temporary — dangling prevention template @@ -1822,9 +2302,7 @@ class expected { // 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) : has_val_(false) { - std::construct_at(std::addressof(unex_), std::in_place, il, std::forward(args)...); - } + constexpr explicit expected(unexpect_t, std::initializer_list il, Args&&... args); // ------------------------------------------------------------------------- // Destructor @@ -1835,11 +2313,7 @@ class expected { = default; constexpr ~expected() - requires(!std::is_trivially_destructible_v>) - { - if (!has_val_) - std::destroy_at(std::addressof(unex_)); - } + requires(!std::is_trivially_destructible_v>); // ------------------------------------------------------------------------- // Assignment (rebind semantics) @@ -1857,22 +2331,7 @@ class expected { 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; - } + std::is_trivially_destructible_v>)); // Move assignment (trivial path) constexpr expected& operator=(expected&&) noexcept @@ -1887,66 +2346,24 @@ class expected { 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; - } + 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 { - std::destroy_at(std::addressof(unex_)); - T& r = std::forward(u); - val_ = std::addressof(r); - has_val_ = true; - } - return *this; - } + constexpr expected& operator=(U&& u); // 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) { - if (!has_val_) { - unex_.error() = e.error(); - } else { - std::construct_at(std::addressof(unex_), e.error()); - has_val_ = false; - } - return *this; - } + 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) { - 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; - } + constexpr expected& operator=(unexpected&& e); // Deleted for reference E: would rebind E& to unexpected's temporary storage. template @@ -1960,437 +2377,821 @@ class expected { // emplace — rebind the reference template requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) - constexpr T& emplace(U&& u) noexcept { - if (!has_val_) { - std::destroy_at(std::addressof(unex_)); - has_val_ = true; - } + constexpr T& emplace(U&& u) noexcept; + + // ------------------------------------------------------------------------- + // 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 + constexpr error_value_type error_or(G&& def) const&; + + template + 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 + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_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>) + : has_val_(rhs.has_val_) { + if (has_val_) + val_ = rhs.val_; + else + std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); +} + +template + requires std::is_lvalue_reference_v +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 expected::expected(U&& u) noexcept : has_val_(true) { + T& r = std::forward(u); + val_ = std::addressof(r); +} + +template + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +constexpr expected::~expected() + requires(!std::is_trivially_destructible_v>) +{ + if (!has_val_) + std::destroy_at(std::addressof(unex_)); +} + +// ============================================================================= +// Out-of-line assignment definitions +// ============================================================================= + +template + requires std::is_lvalue_reference_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>)) +{ + 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 + requires std::is_lvalue_reference_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>)) +{ + 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 + requires std::is_lvalue_reference_v +template + requires(!std::is_same_v, expected> && + !detail::is_unexpected_specialization>::value && std::is_constructible_v && + !detail::reference_constructs_from_temporary_v) +constexpr expected& expected::operator=(U&& u) { + if (has_val_) { T& r = std::forward(u); val_ = std::addressof(r); - return *val_; + } else { + std::destroy_at(std::addressof(unex_)); + T& r = std::forward(u); + val_ = std::addressof(r); + has_val_ = true; + } + return *this; +} + +template + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +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; +} + +template + requires std::is_lvalue_reference_v +template + requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) +constexpr T& expected::emplace(U&& u) noexcept { + if (!has_val_) { + std::destroy_at(std::addressof(unex_)); + has_val_ = true; } + T& r = std::forward(u); + val_ = std::addressof(r); + return *val_; +} - // ------------------------------------------------------------------------- - // Swap - // ------------------------------------------------------------------------- +// ============================================================================= +// Out-of-line swap definition +// ============================================================================= - 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>) - { - 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); - } +template + requires std::is_lvalue_reference_v +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); } +} - friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); } - - // ------------------------------------------------------------------------- - // Observers - // ------------------------------------------------------------------------- +// ============================================================================= +// Out-of-line observer definitions +// ============================================================================= - constexpr T* operator->() const noexcept { +template + requires std::is_lvalue_reference_v +constexpr T* expected::operator->() const noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); + if (!has_val_) + BEMAN_EXPECTED_TRAP(); #endif - return val_; - } + return val_; +} - constexpr T& operator*() const noexcept { +template + requires std::is_lvalue_reference_v +constexpr T& expected::operator*() const noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (!has_val_) - BEMAN_EXPECTED_TRAP(); + if (!has_val_) + BEMAN_EXPECTED_TRAP(); #endif - return *val_; - } + return *val_; +} - constexpr explicit operator bool() const noexcept { return has_val_; } - constexpr bool has_value() const noexcept { return has_val_; } +template + requires std::is_lvalue_reference_v +constexpr expected::operator bool() const noexcept { + return has_val_; +} - constexpr T& 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 + requires std::is_lvalue_reference_v +constexpr bool expected::has_value() const noexcept { + return has_val_; +} - constexpr T& 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 + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +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_; +} - // error() — shallow const: always returns E& regardless of const on expected - constexpr const E& error() const& noexcept { +template + requires std::is_lvalue_reference_v +constexpr const E& expected::error() const& noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); + if (has_val_) + BEMAN_EXPECTED_TRAP(); #endif - return unex_.error(); - } + return unex_.error(); +} - constexpr E& error() & noexcept { +template + requires std::is_lvalue_reference_v +constexpr E& expected::error() & noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); + if (has_val_) + BEMAN_EXPECTED_TRAP(); #endif - return unex_.error(); - } + return unex_.error(); +} - constexpr const E&& error() const&& noexcept { +template + requires std::is_lvalue_reference_v +constexpr const E&& expected::error() const&& noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); + if (has_val_) + BEMAN_EXPECTED_TRAP(); #endif - return std::move(unex_).error(); - } + return std::move(unex_).error(); +} - constexpr E&& error() && noexcept { +template + requires std::is_lvalue_reference_v +constexpr E&& expected::error() && noexcept { #if defined(BEMAN_EXPECTED_HARDENED) - if (has_val_) - BEMAN_EXPECTED_TRAP(); + if (has_val_) + BEMAN_EXPECTED_TRAP(); #endif + return std::move(unex_).error(); +} + +template + requires std::is_lvalue_reference_v +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 + requires std::is_lvalue_reference_v +template +constexpr typename expected::error_value_type 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"); + if (!has_val_) + return unex_.error(); + return static_cast(std::forward(def)); +} + +template + requires std::is_lvalue_reference_v +template +constexpr typename expected::error_value_type 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"); + if (!has_val_) return std::move(unex_).error(); - } + return static_cast(std::forward(def)); +} - template > - requires(std::is_object_v && !std::is_array_v) - constexpr std::remove_cv_t 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)); - } +// ============================================================================= +// Out-of-line monadic operation definitions +// ============================================================================= - template - constexpr error_value_type 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"); - if (!has_val_) - return unex_.error(); - return static_cast(std::forward(def)); - } +template + requires std::is_lvalue_reference_v +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 - constexpr error_value_type 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"); - if (!has_val_) - return std::move(unex_).error(); - return static_cast(std::forward(def)); - } +template + requires std::is_lvalue_reference_v +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()); +} - // ------------------------------------------------------------------------- - // Monadic operations - // ------------------------------------------------------------------------- +template + requires std::is_lvalue_reference_v +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()); +} - // and_then - template - requires std::is_constructible_v - constexpr auto 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 + requires std::is_lvalue_reference_v +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 - requires std::is_constructible_v - constexpr auto 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 + requires std::is_lvalue_reference_v +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 - requires std::is_constructible_v - constexpr auto 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 + requires std::is_lvalue_reference_v +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 - requires std::is_constructible_v - constexpr auto 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 + requires std::is_lvalue_reference_v +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()); +} - // or_else - template - constexpr auto 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 + requires std::is_lvalue_reference_v +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 - constexpr auto 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"); +template + requires std::is_lvalue_reference_v +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_) - return G(*val_); - return std::invoke(std::forward(f), std::move(unex_).error()); - } - - template - constexpr auto 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"); + std::invoke(std::forward(f), *val_); if (has_val_) - return G(*val_); - return std::invoke(std::forward(f), unex_.error()); - } - - template - constexpr auto 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"); + return expected(); + return expected(unexpect, unex_.error()); + } else { if (has_val_) - return G(*val_); - return std::invoke(std::forward(f), std::move(unex_).error()); - } - - // transform: f receives T& (value); error propagates as E; result is expected - template - requires std::is_constructible_v - constexpr auto 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 - requires std::is_constructible_v - constexpr auto 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 - requires std::is_constructible_v - constexpr auto 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 - requires std::is_constructible_v - constexpr auto 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()); - } + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, unex_.error()); } +} - // transform_error: f receives E; value propagates as T&; result is expected - template - constexpr auto 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"); +template + requires std::is_lvalue_reference_v +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_) - return expected(*val_); - return expected(unexpect, std::invoke(std::forward(f), unex_.error())); - } - - template - constexpr auto 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"); + std::invoke(std::forward(f), *val_); + if (has_val_) + return expected(); + return expected(unexpect, std::move(unex_).error()); + } else { if (has_val_) - return expected(*val_); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); + return expected(std::invoke(std::forward(f), *val_)); + return expected(unexpect, std::move(unex_).error()); } +} - template - constexpr auto 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"); +template + requires std::is_lvalue_reference_v +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_) - return expected(*val_); - return expected(unexpect, std::invoke(std::forward(f), unex_.error())); + 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 - constexpr auto 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"); +template + requires std::is_lvalue_reference_v +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(*val_); - return expected(unexpect, std::invoke(std::forward(f), std::move(unex_).error())); + 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()); } +} - // ------------------------------------------------------------------------- - // 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 std::is_lvalue_reference_v +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 - 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 + requires std::is_lvalue_reference_v +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 - friend constexpr bool operator==(const expected& x, const unexpected& e) { - return !x.has_value() && static_cast(x.error() == e.error()); - } +template + requires std::is_lvalue_reference_v +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())); +} - private: - bool has_val_; - union { - T* val_; - unexpected unex_; - }; -}; +template + requires std::is_lvalue_reference_v +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 From 3d02a915433c3d3f91f5a06351da2319925447cc Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Wed, 1 Jul 2026 00:47:10 -0400 Subject: [PATCH 39/42] fix: exclude self-type from expected's converting constructor Under C++23/26 (make TOOLCHAIN=gcc-16 builds in -std=gnu++26), copying expected onto itself failed with "satisfaction of atomic constraint ... depends on itself". expected's converting constructor from expected (unlike its T,E and T&,E counterparts) was never split by value/reference E and had no exclusion for U,G exactly matching the class's own void,E. Instantiating it for that self-referential case probes unexpected's constructibility from the whole expected type, which recurses back into evaluating the very same constraint under libstdc++'s real (non-fallback) reference_constructs_from_temporary_v, available starting in C++23. The real copy/move constructors already handle the self-type case, so excluding it from the templated converting constructor is a no-op for overload resolution and breaks the cycle. --- 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 1c670b8..b637f7a 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -1348,17 +1348,21 @@ class expected { 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, expected> && + 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, expected> && std::is_constructible_v && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && !std::is_constructible_v, const expected&> && @@ -1617,7 +1621,8 @@ constexpr expected::expected(expected&& rhs) noexcept(std::is_nothrow_m template template - requires(std::is_void_v && std::is_constructible_v && + requires(std::is_void_v && !std::is_same_v, expected> && + std::is_constructible_v && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && !std::is_constructible_v, const expected&> && @@ -1629,7 +1634,7 @@ constexpr expected::expected(const expected& rhs) : has_val_(rhs. template template - requires(std::is_void_v && std::is_constructible_v && + requires(std::is_void_v && !std::is_same_v, expected> && std::is_constructible_v && !std::is_constructible_v, expected&> && !std::is_constructible_v, expected &&> && !std::is_constructible_v, const expected&> && From 05fdd9947c2c3da05d39a27a0ee858bb47208bc7 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Wed, 1 Jul 2026 10:05:08 -0400 Subject: [PATCH 40/42] test: close branch-coverage gaps found via gcc-16 coverage build Add 14 test cases exercising code paths that make TOOLCHAIN=gcc-16 coverage showed as never executed: - expected: assign from unexpected&& while already in error state - expected: or_else rvalue/const-rvalue error paths; transform with a void-returning F on an error-holding expected - expected: copy-construct from value state with non-trivial E; the three non-trivial move-assignment transition branches; assign from unexpected&& while already in error state; or_else const-rvalue error path; transform/const transform with a void-returning F on both value and error states; transform_error const-lvalue error path Line coverage 83.2% -> 83.6%, function coverage 94.8% -> 95.4%, branch coverage 53.6% -> 53.9%. Remaining gaps are BEMAN_EXPECTED_TRAP() calls, which only fire on hardened-mode contract violations and would need a death-test harness to exercise. --- tests/beman/expected/expected.test.cpp | 7 ++ tests/beman/expected/expected_ref.test.cpp | 98 +++++++++++++++++++ .../expected/expected_void_monadic.test.cpp | 24 +++++ 3 files changed, 129 insertions(+) diff --git a/tests/beman/expected/expected.test.cpp b/tests/beman/expected/expected.test.cpp index 9a76dde..c22fcfd 100644 --- a/tests/beman/expected/expected.test.cpp +++ b/tests/beman/expected/expected.test.cpp @@ -340,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 // ============================================================================= diff --git a/tests/beman/expected/expected_ref.test.cpp b/tests/beman/expected/expected_ref.test.cpp index f16eb07..26f6775 100644 --- a/tests/beman/expected/expected_ref.test.cpp +++ b/tests/beman/expected/expected_ref.test.cpp @@ -91,6 +91,14 @@ TEST_CASE("expected: copy construct error state", "[expected_ref]") { 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; @@ -179,6 +187,39 @@ TEST_CASE("expected: copy assignment error-to-value", "[expected_ref]") { 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 // ============================================================================= @@ -441,6 +482,24 @@ TEST_CASE("expected: transform to void", "[expected_ref]") { 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 // ============================================================================= @@ -461,6 +520,27 @@ TEST_CASE("expected: const transform", "[expected_ref]") { 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 // ============================================================================= @@ -543,6 +623,17 @@ TEST_CASE("or_else rvalue on error calls F", "[expected_ref]") { 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"); @@ -589,6 +680,13 @@ TEST_CASE("transform_error const rvalue on error calls F", "[expected_ref]") { 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); diff --git a/tests/beman/expected/expected_void_monadic.test.cpp b/tests/beman/expected/expected_void_monadic.test.cpp index 970736d..173ab7c 100644 --- a/tests/beman/expected/expected_void_monadic.test.cpp +++ b/tests/beman/expected/expected_void_monadic.test.cpp @@ -127,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"; }); @@ -262,6 +272,20 @@ TEST_CASE("or_else const rvalue on value short-circuits", "[expected_void_monadi 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"); From befe604c194585083e1e548a4b827bb8f01f3d41 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Wed, 1 Jul 2026 21:31:09 -0400 Subject: [PATCH 41/42] fix: resolve post-merge build failures across gcc-15/16 and clang-22 Remove spurious `requires is_lvalue_reference_v` constraints from expected out-of-line definitions (gcc-15 -Wtemplate-body), delete expected constructors from unexpected to prevent dangling (negative tests now pass), add include for std::addressof in unexpected.hpp, and work around clang's partial-specialization constraint matching by rewriting is_same_v checks and inlining two functions whose constraints name the enclosing specialization. Also moves find_package(Catch2) to tests/CMakeLists with explicit CMAKE_MODULE_PATH for the Catch extras (FetchContent's PARENT_SCOPE does not propagate through the dependency-provider function boundary). --- include/beman/expected/expected.hpp | 152 +++++++------------------- include/beman/expected/unexpected.hpp | 1 + tests/beman/expected/CMakeLists.txt | 3 +- 3 files changed, 40 insertions(+), 116 deletions(-) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp index 30cebb2..63a3e30 100644 --- a/include/beman/expected/expected.hpp +++ b/include/beman/expected/expected.hpp @@ -1354,7 +1354,7 @@ class expected { // 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_same_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&> && @@ -1362,7 +1362,7 @@ class expected { constexpr explicit(!std::is_convertible_v) expected(const expected& rhs); template - requires(std::is_void_v && !std::is_same_v, expected> && 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&> && @@ -1378,18 +1378,15 @@ class expected { requires(!std::is_reference_v && std::is_constructible_v) constexpr explicit(!std::is_convertible_v) expected(unexpected&& e); - // Constructor from unexpected const& / && — reference-E path. The const_cast strips the - // constness introduced by binding e as `const unexpected&`; the referenced G object itself - // is not const, so this does not violate constness of the object actually being pointed to. + // Deleted: constructing expected from unexpected would bind E& to storage + // inside the temporary unexpected — creating a dangling reference when it's destroyed. template - requires(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; + requires std::is_reference_v + constexpr expected(const unexpected&) = delete; template - requires(std::is_reference_v && std::is_constructible_v && - !detail::reference_constructs_from_temporary_v) - constexpr explicit(!std::is_convertible_v) expected(unexpected&& e) noexcept; + requires 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; @@ -1506,11 +1503,13 @@ class expected { constexpr E&& error() && noexcept; 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) &&; // Deleted: value_or is not available for void expected. Gated to reference E only so that, @@ -1621,9 +1620,8 @@ constexpr expected::expected(expected&& rhs) noexcept(std::is_nothrow_m template template - requires(std::is_void_v && !std::is_same_v, expected> && - 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 &&>) @@ -1634,7 +1632,7 @@ constexpr expected::expected(const expected& rhs) : has_val_(rhs. template template - requires(std::is_void_v && !std::is_same_v, expected> && 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&> && @@ -1658,21 +1656,6 @@ 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_constructible_v && - !detail::reference_constructs_from_temporary_v) -constexpr expected::expected(const unexpected& e) noexcept : has_val_(false) { - std::construct_at(std::addressof(unex_), const_cast(e.error())); -} - -template -template - requires(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 @@ -1888,8 +1871,8 @@ constexpr E&& expected::error() && noexcept { 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(); @@ -1898,8 +1881,8 @@ constexpr typename expected::error_value_type expected::error_ 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(); @@ -2231,10 +2214,13 @@ class expected { // Value constructor — takes U that can bind to T& template requires(!std::is_same_v, std::in_place_t> && - !std::is_same_v, expected> && + !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; + constexpr explicit(!std::is_convertible_v) expected(U&& u) noexcept : has_val_(true) { + T& r = std::forward(u); + val_ = std::addressof(r); + } // Deleted: binding a temporary to T& creates a dangling reference template @@ -2354,10 +2340,21 @@ class expected { // Rebind reference from lvalue template - requires(!std::is_same_v, expected> && + 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); + constexpr expected& operator=(U&& u) { + if (has_val_) { + T& r = std::forward(u); + val_ = std::addressof(r); + } else { + std::destroy_at(std::addressof(unex_)); + T& r = std::forward(u); + val_ = std::addressof(r); + has_val_ = true; + } + return *this; + } // Assignment from unexpected — value-E path template @@ -2510,7 +2507,6 @@ class expected { // ============================================================================= template - requires std::is_lvalue_reference_v 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_) { @@ -2521,7 +2517,6 @@ constexpr expected::expected(const expected& rhs) noexcept(std::is_nothro } template - requires std::is_lvalue_reference_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>) : has_val_(rhs.has_val_) { @@ -2531,20 +2526,8 @@ constexpr expected::expected(expected&& rhs) noexcept(std::is_nothrow_mov std::construct_at(std::addressof(unex_), std::move(rhs.unex_)); } -template - requires std::is_lvalue_reference_v -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 expected::expected(U&& u) noexcept : has_val_(true) { - T& r = std::forward(u); - val_ = std::addressof(r); -} template - requires std::is_lvalue_reference_v template requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && !detail::reference_constructs_from_temporary_v) @@ -2558,7 +2541,6 @@ constexpr expected::expected(const expected& rhs) : has_val_(rhs.h } template - requires std::is_lvalue_reference_v template requires(!std::is_reference_v && std::is_constructible_v && std::is_constructible_v && !detail::reference_constructs_from_temporary_v) @@ -2572,7 +2554,6 @@ constexpr expected::expected(expected&& rhs) : has_val_(rhs.has_va } template - requires std::is_lvalue_reference_v template requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && std::is_convertible_v && !detail::reference_constructs_from_temporary_v) @@ -2586,7 +2567,6 @@ constexpr expected::expected(const expected& rhs) : has_val_(rhs.h } template - requires std::is_lvalue_reference_v template requires(std::is_reference_v && std::is_reference_v && std::is_constructible_v && std::is_convertible_v && !detail::reference_constructs_from_temporary_v) @@ -2600,7 +2580,6 @@ constexpr expected::expected(expected&& rhs) : has_val_(rhs.has_va } template - requires std::is_lvalue_reference_v template requires(!std::is_reference_v && std::is_constructible_v) constexpr expected::expected(const unexpected& e) : has_val_(false) { @@ -2608,7 +2587,6 @@ constexpr expected::expected(const unexpected& e) : has_val_(false) { } template - requires std::is_lvalue_reference_v template requires(!std::is_reference_v && std::is_constructible_v) constexpr expected::expected(unexpected&& e) : has_val_(false) { @@ -2616,7 +2594,6 @@ constexpr expected::expected(unexpected&& e) : has_val_(false) { } template - requires std::is_lvalue_reference_v template requires(std::is_constructible_v && !detail::unexpect_dangles_v) constexpr expected::expected(unexpect_t, Args&&... args) : has_val_(false) { @@ -2624,7 +2601,6 @@ constexpr expected::expected(unexpect_t, Args&&... args) : has_val_(false } template - requires std::is_lvalue_reference_v template requires std::is_constructible_v&, Args...> constexpr expected::expected(unexpect_t, std::initializer_list il, Args&&... args) : has_val_(false) { @@ -2636,7 +2612,6 @@ constexpr expected::expected(unexpect_t, std::initializer_list il, Arg // ============================================================================= template - requires std::is_lvalue_reference_v constexpr expected::~expected() requires(!std::is_trivially_destructible_v>) { @@ -2649,7 +2624,6 @@ constexpr expected::~expected() // ============================================================================= template - requires std::is_lvalue_reference_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> && @@ -2671,7 +2645,6 @@ constexpr expected& expected::operator=(const expected& rhs) } template - requires std::is_lvalue_reference_v constexpr expected& expected::operator=(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v> && std::is_nothrow_move_assignable_v>) @@ -2694,27 +2667,8 @@ expected::operator=(expected&& rhs) noexcept(std::is_nothrow_move_constru return *this; } -template - requires std::is_lvalue_reference_v -template - requires(!std::is_same_v, expected> && - !detail::is_unexpected_specialization>::value && std::is_constructible_v && - !detail::reference_constructs_from_temporary_v) -constexpr expected& expected::operator=(U&& u) { - if (has_val_) { - T& r = std::forward(u); - val_ = std::addressof(r); - } else { - std::destroy_at(std::addressof(unex_)); - T& r = std::forward(u); - val_ = std::addressof(r); - has_val_ = true; - } - return *this; -} template - requires std::is_lvalue_reference_v template requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) constexpr expected& expected::operator=(const unexpected& e) { @@ -2728,7 +2682,6 @@ constexpr expected& expected::operator=(const unexpected& e) { } template - requires std::is_lvalue_reference_v template requires(!std::is_reference_v && std::is_constructible_v && std::is_assignable_v) constexpr expected& expected::operator=(unexpected&& e) { @@ -2742,7 +2695,6 @@ constexpr expected& expected::operator=(unexpected&& e) { } template - requires std::is_lvalue_reference_v template requires(std::is_constructible_v && !detail::reference_constructs_from_temporary_v) constexpr T& expected::emplace(U&& u) noexcept { @@ -2760,7 +2712,6 @@ constexpr T& expected::emplace(U&& u) noexcept { // ============================================================================= template - requires std::is_lvalue_reference_v 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>) @@ -2788,7 +2739,6 @@ constexpr void expected::swap(expected& rhs) noexcept( // ============================================================================= template - requires std::is_lvalue_reference_v constexpr T* expected::operator->() const noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (!has_val_) @@ -2798,7 +2748,6 @@ constexpr T* expected::operator->() const noexcept { } template - requires std::is_lvalue_reference_v constexpr T& expected::operator*() const noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (!has_val_) @@ -2808,19 +2757,16 @@ constexpr T& expected::operator*() const noexcept { } template - requires std::is_lvalue_reference_v constexpr expected::operator bool() const noexcept { return has_val_; } template - requires std::is_lvalue_reference_v constexpr bool expected::has_value() const noexcept { return has_val_; } template - requires std::is_lvalue_reference_v constexpr T& expected::value() const& { static_assert(std::is_copy_constructible_v, "value() requires is_copy_constructible_v"); if (!has_val_) @@ -2829,7 +2775,6 @@ constexpr T& expected::value() const& { } template - requires std::is_lvalue_reference_v constexpr T& expected::value() && { if constexpr (std::is_reference_v) { static_assert(std::is_copy_constructible_v, "value() requires E to be copy constructible"); @@ -2843,7 +2788,6 @@ constexpr T& expected::value() && { } template - requires std::is_lvalue_reference_v constexpr const E& expected::error() const& noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (has_val_) @@ -2853,7 +2797,6 @@ constexpr const E& expected::error() const& noexcept { } template - requires std::is_lvalue_reference_v constexpr E& expected::error() & noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (has_val_) @@ -2863,7 +2806,6 @@ constexpr E& expected::error() & noexcept { } template - requires std::is_lvalue_reference_v constexpr const E&& expected::error() const&& noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (has_val_) @@ -2873,7 +2815,6 @@ constexpr const E&& expected::error() const&& noexcept { } template - requires std::is_lvalue_reference_v constexpr E&& expected::error() && noexcept { #if defined(BEMAN_EXPECTED_HARDENED) if (has_val_) @@ -2883,7 +2824,6 @@ constexpr E&& expected::error() && noexcept { } template - requires std::is_lvalue_reference_v template requires(std::is_object_v && !std::is_array_v) constexpr std::remove_cv_t expected::value_or(U&& def) const { @@ -2896,7 +2836,6 @@ constexpr std::remove_cv_t expected::value_or(U&& def) const { } template - requires std::is_lvalue_reference_v template constexpr typename expected::error_value_type expected::error_or(G&& def) const& { static_assert(std::is_copy_constructible_v, "error_or requires is_copy_constructible_v"); @@ -2907,7 +2846,6 @@ constexpr typename expected::error_value_type expected::error_or(G } template - requires std::is_lvalue_reference_v template constexpr typename expected::error_value_type expected::error_or(G&& def) && { static_assert(std::is_move_constructible_v, "error_or requires is_move_constructible_v"); @@ -2922,7 +2860,6 @@ constexpr typename expected::error_value_type expected::error_or(G // ============================================================================= template - requires std::is_lvalue_reference_v template requires std::is_constructible_v constexpr auto expected::and_then(F&& f) & { @@ -2937,7 +2874,6 @@ constexpr auto expected::and_then(F&& f) & { } template - requires std::is_lvalue_reference_v template requires std::is_constructible_v constexpr auto expected::and_then(F&& f) && { @@ -2952,7 +2888,6 @@ constexpr auto expected::and_then(F&& f) && { } template - requires std::is_lvalue_reference_v template requires std::is_constructible_v constexpr auto expected::and_then(F&& f) const& { @@ -2967,7 +2902,6 @@ constexpr auto expected::and_then(F&& f) const& { } template - requires std::is_lvalue_reference_v template requires std::is_constructible_v constexpr auto expected::and_then(F&& f) const&& { @@ -2982,7 +2916,6 @@ constexpr auto expected::and_then(F&& f) const&& { } template - requires std::is_lvalue_reference_v template constexpr auto expected::or_else(F&& f) & { using G = std::remove_cvref_t>; @@ -2995,7 +2928,6 @@ constexpr auto expected::or_else(F&& f) & { } template - requires std::is_lvalue_reference_v template constexpr auto expected::or_else(F&& f) && { using G = std::remove_cvref_t>; @@ -3008,7 +2940,6 @@ constexpr auto expected::or_else(F&& f) && { } template - requires std::is_lvalue_reference_v template constexpr auto expected::or_else(F&& f) const& { using G = std::remove_cvref_t>; @@ -3021,7 +2952,6 @@ constexpr auto expected::or_else(F&& f) const& { } template - requires std::is_lvalue_reference_v template constexpr auto expected::or_else(F&& f) const&& { using G = std::remove_cvref_t>; @@ -3034,7 +2964,6 @@ constexpr auto expected::or_else(F&& f) const&& { } template - requires std::is_lvalue_reference_v template requires std::is_constructible_v constexpr auto expected::transform(F&& f) & { @@ -3060,7 +2989,6 @@ constexpr auto expected::transform(F&& f) & { } template - requires std::is_lvalue_reference_v template requires std::is_constructible_v constexpr auto expected::transform(F&& f) && { @@ -3086,7 +3014,6 @@ constexpr auto expected::transform(F&& f) && { } template - requires std::is_lvalue_reference_v template requires std::is_constructible_v constexpr auto expected::transform(F&& f) const& { @@ -3112,7 +3039,6 @@ constexpr auto expected::transform(F&& f) const& { } template - requires std::is_lvalue_reference_v template requires std::is_constructible_v constexpr auto expected::transform(F&& f) const&& { @@ -3138,7 +3064,6 @@ constexpr auto expected::transform(F&& f) const&& { } template - requires std::is_lvalue_reference_v template constexpr auto expected::transform_error(F&& f) & { using G = std::remove_cv_t>; @@ -3153,7 +3078,6 @@ constexpr auto expected::transform_error(F&& f) & { } template - requires std::is_lvalue_reference_v template constexpr auto expected::transform_error(F&& f) && { using G = std::remove_cv_t>; @@ -3168,7 +3092,6 @@ constexpr auto expected::transform_error(F&& f) && { } template - requires std::is_lvalue_reference_v template constexpr auto expected::transform_error(F&& f) const& { using G = std::remove_cv_t>; @@ -3183,7 +3106,6 @@ constexpr auto expected::transform_error(F&& f) const& { } template - requires std::is_lvalue_reference_v template constexpr auto expected::transform_error(F&& f) const&& { using G = std::remove_cv_t>; diff --git a/include/beman/expected/unexpected.hpp b/include/beman/expected/unexpected.hpp index 2a01921..caf5b4f 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 diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 68b75f8..77c5dc0 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -2,6 +2,7 @@ # 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( @@ -296,7 +297,7 @@ add_fail_test(expected_void_ref_e_construct_from_unexpected_fail ) 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|selected deleted operator|attempting to reference" + "no assignment from unexpected|use of deleted function|call to deleted|invokes a deleted function|selected deleted operator|attempting to reference" ) # ============================================================================= From f7c44c7195cf93435790b64a22a295090345b5e7 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Thu, 2 Jul 2026 15:27:49 -0400 Subject: [PATCH 42/42] refactor: replace concept polyfill with compiler builtins for dangling traits Use __reference_constructs_from_temporary / __reference_converts_from_temporary builtins (available in GCC 13+ and Clang 16+) as the C++20-mode fallback instead of approximating the traits as concepts. This eliminates the concept-vs-variable naming mismatch and sets GCC 13 as the minimum. --- include/beman/expected/unexpected.hpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/include/beman/expected/unexpected.hpp b/include/beman/expected/unexpected.hpp index caf5b4f..4870520 100644 --- a/include/beman/expected/unexpected.hpp +++ b/include/beman/expected/unexpected.hpp @@ -33,17 +33,11 @@ struct is_unexpected_specialization> : std::true_type {}; #ifdef __cpp_lib_reference_from_temporary using std::reference_constructs_from_temporary_v; using std::reference_converts_from_temporary_v; -#else -template -concept reference_converts_from_temporary_v = - std::is_reference_v && - ((!std::is_reference_v && std::is_convertible_v*, std::remove_cvref_t*>) || - (std::is_lvalue_reference_v && std::is_const_v> && - std::is_convertible_v&&> && - !std::is_convertible_v&>)); - -template -concept reference_constructs_from_temporary_v = 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