From 7adf3d7a4b9648fcf504d14283752d2fc03a52c6 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Sun, 4 Oct 2026 21:51:53 +0100 Subject: [PATCH 01/12] Allow lvalue-reference alternatives in `copack` Assisted-by: Claude:claude-opus-5-5 --- CHANGELOG.md | 8 + TYPE_ALGEBRA.md | 21 +- examples/type_algebra/main.cpp | 7 +- include/fn/copack.hpp | 181 ++++++++---- include/fn/detail/functional.hpp | 35 ++- include/fn/detail/variadic_union.hpp | 340 ++++++++++++---------- include/fn/just.hpp | 37 +-- tests/fn/algebra.cpp | 24 ++ tests/fn/and_then.cpp | 24 +- tests/fn/copack.cpp | 417 ++++++++++++++++++++++++++- tests/fn/detail/variadic_union.cpp | 37 +++ tests/fn/just_choice.cpp | 41 ++- 12 files changed, 918 insertions(+), 254 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af871fe1..24d04cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ Design history of libfn, newest first. The living documents — [README.md](README.md), [CONTRIBUTING.md](CONTRIBUTING.md), [docs/](docs/) — describe only the present state of the design; when a decision makes an earlier idea obsolete, this file is where the transition is recorded and explained. +## `copack` admits lvalue-reference alternatives — 4 October 2026 + +A `copack` alternative may be an lvalue reference `T&`, held as a pointer to its referent; previously an alternative could not be a reference, and a reference had to be wrapped in a `pack`. `T`, `T&` and `T const&` are distinct alternatives. As with `optional`, assignment and `emplace` rebind, and comparison compares referents. As from a `pack`, callables, `get` and `get_ptr` reach the referent as `T&`, or as `T const&` through a `const` copack, whatever its value category. A reference to a `pack` or a `copack` remains refused, as do rvalue references and references to arrays or functions. `choice` and `expected, E>` follow. + +The converting constructors of `copack` and `choice` and their value assignments consider only the alternatives whose decayed type is the value's own, and select among them as overload resolution selects among functions taking each. For an lvalue `int x`, `copack_for{x}` is ambiguous and rejected; `std::in_place_type` names the alternative. A `copack_for` can be built only through `std::in_place_type` or widening, since `int` and `int const&` are ambiguous for every `int` value. `copack{std::in_place_type, x}` deduces `copack`. + +`and_then` over a copack whose branches return `optional` and other optionals joins the references into the result's copack, e.g. `optional>`; previously such a branch set was refused. + ## `` becomes ``; `monadic_invocable` rejects an incomplete functor — 27 September 2026 Replace includes of `` with ``; no compatibility header is provided. `some_in_place_type` moves to `` and remains available through ``. diff --git a/TYPE_ALGEBRA.md b/TYPE_ALGEBRA.md index 854a0fd3..1d65d758 100644 --- a/TYPE_ALGEBRA.md +++ b/TYPE_ALGEBRA.md @@ -258,7 +258,7 @@ Together, `just`, `choice` and `expected>` form the **identity clust > > To carry several alternatives that cannot fail, use `choice`: a computation that always succeeds, with a result that is one of `Ts...`. It is an alias of `just>`, the identity carrier specialized for a coproduct payload, so generic code constrained on `some_just` also accepts a `choice`. Prefer `choice_for` when listing alternatives, since it normalizes their order. `just>` is left incomplete, as `choice<>` is; the unit carrier is `just`. -These carriers constrain their payloads. While `optional` is supported as a standard-conforming exception, other carriers reject raw reference types outright; references must be wrapped inside a `pack` (detailed in Section 4). +These carriers constrain their payloads. While `optional` is supported as a standard-conforming exception, other carriers reject raw reference types outright; references travel inside a `pack` or as `copack` alternatives (detailed in Section 4). ### Carriers have control flow; raw data does not @@ -348,7 +348,9 @@ As a payload, a `copack` models a discriminated union of types. Evaluating a `copack` via `.apply()` passes the active alternative to the callback. Because `copack` is self-flattening, nested `copack`s do not occur. A selected alternative that is itself tuple-like—such as `pack`, `std::tuple`, or `std::array`—is unpacked one level, passing its immediate constituents as separate arguments. Since normalized shapes are sums of products, one level of unpacking is sufficient to supply the product's fields as function arguments. -To explicitly lift a scalar value into a single-alternative coproduct (i.e. *singular* `copack`), use `fn::as_copack(value)`. Unlike `as_pack`, it always decays: a `copack` alternative can never be a reference. A singular `copack` supports the standard C++ tuple protocol (`get<0>`, `tuple_size` returning 1, `tuple_element<0, copack<...>>`, structured bindings) and additionally provides index-less `get`. +To explicitly lift a scalar value into a single-alternative coproduct (i.e. *singular* `copack`), use `fn::as_copack(value)`. Unlike `as_pack`, it always decays. A singular `copack` supports the standard C++ tuple protocol (`get<0>`, `tuple_size` returning 1, `tuple_element<0, copack<...>>`, structured bindings) and additionally provides index-less `get`. + +An alternative may be an lvalue reference, so `copack_for` is a sum of borrowed locations. `T`, `T&` and `T const&` are distinct alternatives. A value selects among the alternatives of its own decayed type as overload resolution selects among functions taking each: for an lvalue `int x`, `copack_for{x}` binds `int&`, while `copack_for{x}` is ambiguous and rejected — `std::in_place_type` names the alternative instead. As with `optional`, assignment rebinds. The callback receives `T&`, or `T const&` from a `const` copack, whatever the copack's value category, as from a `pack`. A reference to a `pack` or a `copack` is not an alternative. A `pack` can be lifted into a `copack` (including packs holding references), but a `pack` cannot contain a `copack`. There is algebraic equivalence between a hypothetical `pack` containing a `copack` (which is disallowed) and a specific shape of `copack` containing a `pack` — see Section 6 for details. @@ -1040,14 +1042,14 @@ The library respects C++ value mechanics: - `noexcept` is conditionally computed. - Value categories (lvalue/rvalue) propagate strictly to callbacks, avoiding copies. - Immovable and move-only payloads are supported in place. -- Reference-bearing `pack` and `optional` are supported. Lifetime management of non-owning references remains with the caller. -- `pack` compares element-wise, supporting equality and three-way comparison. For reference-bearing `pack`, comparison applies to the referents rather than the references themselves. +- Reference-bearing `pack`, `copack` and `optional` are supported. Lifetime management of non-owning references remains with the caller. +- `pack` compares element-wise, supporting equality and three-way comparison. For references in a `pack` or a `copack`, comparison applies to the referents rather than the references themselves. > [!NOTE] > > ### Note — reference payloads > -> Raw reference payloads are disallowed on the carriers `expected`, `just` and `choice`, and as `copack` alternatives. `expected` stores its payload in a union, and C++ forbids a union member of reference type; the algebra's own types refuse them so that every alternative is dispatched the same way, whatever it holds. `optional` is the deliberate exception — the standard specifies it, and `libfn` polyfills it. If you want to propagate references inside the other carriers, wrap them in a `pack` (e.g. `expected, E>`). +> Raw reference payloads are disallowed on the carriers `expected` and `just`: `expected` stores its payload in a union, and C++ forbids a union member of reference type. `optional` is the deliberate exception — the standard specifies it, and `libfn` polyfills it. To propagate references inside the other carriers, wrap them in a `pack` (e.g. `expected, E>`) or make them `copack` alternatives (e.g. `expected, E>` or `choice`), which hold a pointer to the referent. ```cpp @@ -1059,9 +1061,14 @@ auto test_references() -> void fn::optional opt{x}; static_assert(std::same_as); - // expected must wrap references inside a pack + // expected holds references inside a pack ... fn::expected, Error> ex{fn::as_pack(x)}; static_assert(std::same_as &>); + + // ... or hold them as copack alternatives + fn::expected, Error> ec{x}; + using std::get; + static_assert(std::same_as); } ``` @@ -1120,7 +1127,7 @@ Index: **2. Core Algebraic Foundations (Sections 2, 3, & 4)** * **Section 2** establishes the core algebraic identities of types (**0**, **1**, **A + B**, **A × B**), separates Zero (`copack<>`) from Unit (`pack<>`), and defines `copack` set semantics (deduplication, flattening, sorting), why the algebra is strictly opt-in, and how `void` enters it. * **Section 3** establishes the carriers, the fact that raw data lacks control flow while carriers have it, and the basics of cross-carrier recovery-path bridging. - * **Section 4** expands on the concrete C++ payload behaviors of `pack` and `copack` (ADL `get`, structured bindings, `.append()`, singular copacks). + * **Section 4** expands on the concrete C++ payload behaviors of `pack` and `copack` (ADL `get`, structured bindings, `.append()`, singular copacks, reference alternatives). **3. Progressive Functional Composition (Sections 5–9)** * Once the data layers (payloads) and computational contexts (carriers) are defined, the document advances into composition: diff --git a/examples/type_algebra/main.cpp b/examples/type_algebra/main.cpp index f3a60229..af93cb65 100644 --- a/examples/type_algebra/main.cpp +++ b/examples/type_algebra/main.cpp @@ -427,9 +427,14 @@ auto test_references() -> void fn::optional opt{x}; static_assert(std::same_as); - // expected must wrap references inside a pack + // expected holds references inside a pack ... fn::expected, Error> ex{fn::as_pack(x)}; static_assert(std::same_as &>); + + // ... or hold them as copack alternatives + fn::expected, Error> ec{x}; + using std::get; + static_assert(std::same_as); } // sync-example-test-references diff --git a/include/fn/copack.hpp b/include/fn/copack.hpp index 2721112b..97834636 100644 --- a/include/fn/copack.hpp +++ b/include/fn/copack.hpp @@ -45,13 +45,47 @@ template concept empty_copack = some_copack && (::std::remove_cvref_t::size == 0); namespace detail { +// How a reference to a product or a sum takes part in conjunction, disjunction and grading is an open +// question, so neither is admitted as a referent. template static constexpr bool _is_valid_copack_subtype // - = (not ::std::is_same_v) // - &&(not ::std::is_reference_v) // - &&(not some_copack) // - &&(not some_in_place_type) // - &&::std::is_same_v>; + = (not some_copack) + && (not some_in_place_type) // + &&(::std::is_lvalue_reference_v // + ? ::std::is_object_v<::std::remove_reference_t> // + && (not ::std::is_array_v<::std::remove_reference_t>) && (not some_pack) + : (not ::std::is_void_v) && (not ::std::is_reference_v) + && ::std::is_same_v>); + +// The alternative a value selects: overload resolution among functions taking each alternative of the +// value's decayed type, so that a merely convertible value selects nothing; void when nothing is selected. +// A by-value parameter ties with every viable reference binding, so the owning alternative is selected +// exactly where no reference alternative binds - decided here without asking it to copy the value. +template +constexpr inline bool _binds_alternative = ::std::is_reference_v && ::std::is_convertible_v + && ::std::is_same_v<::std::remove_cvref_t, ::std::remove_cvref_t>; +template > struct _select_arm { + struct _never final {}; + static void _select(_never); +}; +template struct _select_arm { + static auto _select(A) -> ::std::type_identity; +}; +template struct _select_arms : _select_arm... { + using _select_arm::_select...; +}; +template struct _selected_alternative { + using type + = ::std::conditional_t<(not(... || _binds_alternative)) && type_one_of<::std::remove_cvref_t, As...>, + ::std::remove_cvref_t, void>; +}; +template + requires(not type_one_of<::std::remove_cvref_t, As...>) + && requires { _select_arms::_select(::std::declval()); } +struct _selected_alternative { + using type = decltype(_select_arms::_select(::std::declval()))::type; +}; +template using _selected_alternative_t = _selected_alternative::type; struct _apply_autodetect_tag final {}; @@ -393,13 +427,16 @@ struct copack { // hand. static constexpr bool _copy_assignable = (... - && (::std::is_copy_assignable_v && detail::_makeable + && (::std::is_copy_assignable_v> && detail::_makeable && (detail::_nothrow_makeable || detail::_nothrow_makeable))); - static constexpr bool _nothrow_copy_assignable - = (... && (::std::is_nothrow_copy_assignable_v && detail::_nothrow_makeable)); + static constexpr bool _nothrow_copy_assignable = (... + && (::std::is_nothrow_copy_assignable_v> + && detail::_nothrow_makeable)); static constexpr bool _move_assignable - = (... && (::std::is_move_assignable_v && detail::_nothrow_makeable)); - static constexpr bool _nothrow_move_assignable = (... && ::std::is_nothrow_move_assignable_v); + = (... + && (::std::is_move_assignable_v> && detail::_nothrow_makeable)); + static constexpr bool _nothrow_move_assignable + = (... && ::std::is_nothrow_move_assignable_v>); // Each special member is trivial exactly when every alternative permits it, with the gates // `std::variant` uses. The trivial arm IS the compiler's defaulted member - for the assignments, @@ -410,11 +447,13 @@ struct copack { static constexpr bool _trivially_move_constructible = (... && ::std::is_trivially_move_constructible_v); static constexpr bool _trivially_copy_assignable = (... - && (::std::is_trivially_copy_constructible_v && ::std::is_trivially_copy_assignable_v + && (::std::is_trivially_copy_constructible_v + && ::std::is_trivially_copy_assignable_v> && ::std::is_trivially_destructible_v)); static constexpr bool _trivially_move_assignable = (... - && (::std::is_trivially_move_constructible_v && ::std::is_trivially_move_assignable_v + && (::std::is_trivially_move_constructible_v + && ::std::is_trivially_move_assignable_v> && ::std::is_trivially_destructible_v)); /** @@ -470,31 +509,29 @@ struct copack { } /** - * @brief Constructs the alternative matching the value's decayed type + * @brief Constructs the alternative the value selects * - * Takes a value of exactly one alternative: a merely convertible non-alternative is rejected, - * so interconvertible alternatives never make a resolution puzzle. Explicit exactly where the - * conversion to that alternative is. + * The value selects among the alternatives of its own decayed type - such as `T`, `T&` and + * `T const&` - as overload resolution selects among functions taking each; an ambiguous selection + * is rejected. A merely convertible non-alternative is rejected too, so interconvertible + * alternatives never make a resolution puzzle. Explicit exactly where the conversion to that + * alternative is. * * @param v Value of one alternative */ - template - constexpr copack(T &&v) // NOSONAR cpp:S1709,S6458 implicit arm of the explicit pair; has_type excludes self - noexcept(detail::_nothrow_makeable, decltype(v)>) - requires has_type<::std::remove_cvref_t> && (detail::_makeable, decltype(v)>) - && (::std::is_convertible_v>) - : data(detail::make_variadic_union<::std::remove_cvref_t, data_t>(FWD(v))), - index(detail::type_index<::std::remove_cvref_t, Ts...>) + template > + constexpr copack(U &&v) // NOSONAR cpp:S1709,S6458 implicit arm of the explicit pair; has_type excludes self + noexcept(detail::_nothrow_makeable) + requires has_type && (detail::_makeable) && (::std::is_convertible_v) + : data(detail::make_variadic_union(FWD(v))), index(detail::type_index) { } - template - constexpr explicit copack(T &&v) // NOSONAR cpp:S6458 has_type excludes self - noexcept(detail::_nothrow_makeable, decltype(v)>) - requires has_type<::std::remove_cvref_t> && (detail::_makeable, decltype(v)>) - && (not ::std::is_convertible_v>) - : data(detail::make_variadic_union<::std::remove_cvref_t, data_t>(FWD(v))), - index(detail::type_index<::std::remove_cvref_t, Ts...>) + template > + constexpr explicit copack(U &&v) // NOSONAR cpp:S6458 has_type excludes self + noexcept(detail::_nothrow_makeable) + requires has_type && (detail::_makeable) && (not ::std::is_convertible_v) + : data(detail::make_variadic_union(FWD(v))), index(detail::type_index) { } @@ -526,7 +563,7 @@ struct copack { requires detail::is_superset_of> && (not ::std::is_same_v>) && (... && detail::_makeable) && (sizeof...(Tx) > 0) : data(FWD(arg).template _invoke([](::std::in_place_type_t, auto &&v) -> data_t { - return detail::make_variadic_union(FWD(v)); + return detail::make_variadic_union(detail::_rebound(FWD(v))); })), index(FWD(arg).template _invoke<::std::size_t>([](::std::in_place_type_t, auto &&) { // return detail::type_index; @@ -563,7 +600,7 @@ struct copack { && detail::is_superset_of> && (sizeof...(Tx) > 0) && (... && detail::_makeable>) : data(FWD(arg).template _invoke([](::std::in_place_type_t, auto &&v) -> data_t { - return detail::make_variadic_union(FWD(v)); + return detail::make_variadic_union(detail::_rebound(FWD(v))); })), index(FWD(arg).template _invoke<::std::size_t>([](::std::in_place_type_t, auto &&) { // return detail::type_index; @@ -581,10 +618,10 @@ struct copack { = default; constexpr copack(copack const &other) noexcept(_nothrow_copyable) requires(not _trivially_copy_constructible) && _copyable - : data(detail::invoke_type_variadic_union( // - other.data, other.index, // - [](::std::in_place_type_t, auto const &v) -> data_t { // - return detail::make_variadic_union(v); + : data(detail::invoke_type_variadic_union( // + other.data, other.index, // + [](::std::in_place_type_t, auto &&v) -> data_t { // + return detail::make_variadic_union(detail::_rebound(v)); })), index(other.index) { @@ -660,10 +697,11 @@ struct copack { // arms mirror `_reinit`'s shape, and the third is the standard's snapshot-and-restore - live here, // where old and new are the same type, so the constraint that admits the alternative also // guarantees its snapshot. - template constexpr void _reassign(V &&v) noexcept(::std::is_nothrow_assignable_v) + template + constexpr void _reassign(V &&v) noexcept(::std::is_nothrow_assignable_v &, V>) { - T *held = detail::ptr_variadic_union(this->data); - if constexpr (::std::is_nothrow_assignable_v) { + auto *held = detail::ptr_variadic_union(this->data); + if constexpr (::std::is_nothrow_assignable_v &, V>) { *held = FWD(v); } else if constexpr (::std::is_nothrow_assignable_v) { T tmp{FWD(v)}; // may throw, and the value in hand is untouched until it cannot @@ -709,12 +747,14 @@ struct copack { if (this != &other) { if (index == other.index) { detail::invoke_type_variadic_union( // - other.data, other.index, - [this](::std::in_place_type_t, auto const &v) { this->template _reassign(v); }); + other.data, other.index, [this](::std::in_place_type_t, auto &&v) { + this->template _reassign(detail::_rebound(v)); + }); } else { detail::invoke_type_variadic_union( // - other.data, other.index, - [this](::std::in_place_type_t, auto const &v) { this->template _reinit(v); }); + other.data, other.index, [this](::std::in_place_type_t, auto &&v) { + this->template _reinit(detail::_rebound(v)); + }); } } return *this; @@ -763,19 +803,22 @@ struct copack { // construction otherwise, exactly as the same-type operator= does. template constexpr copack &operator=(copack const &arg) // - noexcept((... && (::std::is_nothrow_copy_assignable_v && detail::_nothrow_makeable))) + noexcept((... + && (::std::is_nothrow_copy_assignable_v> + && detail::_nothrow_makeable))) requires detail::is_superset_of> && (not ::std::is_same_v>) && (... - && (::std::is_copy_assignable_v && detail::_makeable + && (::std::is_copy_assignable_v> + && detail::_makeable && (detail::_nothrow_makeable || detail::_nothrow_makeable))) && (sizeof...(Tx) > 0) { - arg.template _invoke([this](::std::in_place_type_t, auto const &v) { + arg.template _invoke([this](::std::in_place_type_t, auto &&v) { if (this->index == detail::type_index) - this->template _reassign(v); + this->template _reassign(detail::_rebound(v)); else - this->template _reinit(v); + this->template _reinit(detail::_rebound(v)); }); return *this; } @@ -788,9 +831,11 @@ struct copack { */ template constexpr copack &operator=(copack &&arg) // - noexcept((... && ::std::is_nothrow_move_assignable_v)) + noexcept((... && ::std::is_nothrow_move_assignable_v>)) requires detail::is_superset_of> && (not ::std::is_same_v>) - && (... && (::std::is_move_assignable_v && detail::_nothrow_makeable)) + && (... + && (::std::is_move_assignable_v> + && detail::_nothrow_makeable)) && (sizeof...(Tx) > 0) { ::std::move(arg).template _invoke([this](::std::in_place_type_t, auto &&v) { @@ -815,10 +860,12 @@ struct copack { // lets an uninvolved alternative forbid the assignment; this overload consults only the // alternative involved, assigning in place when it is the one held and replacing by // construction otherwise. - template > + template > constexpr copack &operator=(U &&v) // - noexcept(::std::is_nothrow_assignable_v && detail::_nothrow_makeable) - requires has_type && ::std::is_assignable_v && detail::_makeable + noexcept(::std::is_nothrow_assignable_v &, decltype(v)> + && detail::_nothrow_makeable) + requires has_type && ::std::is_assignable_v &, decltype(v)> + && detail::_makeable && (detail::_nothrow_makeable || detail::_nothrow_makeable) { if (index == detail::type_index) @@ -850,7 +897,7 @@ struct copack { && (detail::_nothrow_makeable || detail::_nothrow_makeable) { this->template _reinit(FWD(args)...); - return *detail::ptr_variadic_union(this->data); + return detail::_held(*detail::ptr_variadic_union(this->data)); } /** @@ -869,7 +916,8 @@ struct copack { } /** - * @brief Pointer to the alternative `T`, or `nullptr` where it is not the one held + * @brief Pointer to the alternative `T` - to the referent, for a reference alternative - or `nullptr` + * where it is not the one held * * The escape hatch for direct access: unlike `apply`, no dispatch and no exhaustiveness - the * caller names one alternative and tests the result. @@ -879,16 +927,21 @@ struct copack { */ template requires has_type - [[nodiscard]] constexpr T *get_ptr(::std::in_place_type_t = ::std::in_place_type) noexcept + [[nodiscard]] constexpr ::std::add_pointer_t get_ptr(::std::in_place_type_t = ::std::in_place_type) noexcept { - return has_value(::std::in_place_type) ? detail::ptr_variadic_union(data) : nullptr; + return has_value(::std::in_place_type) + ? ::std::addressof(detail::_held(*detail::ptr_variadic_union(data))) + : nullptr; } template requires has_type - [[nodiscard]] constexpr T const *get_ptr(::std::in_place_type_t = ::std::in_place_type) const noexcept + [[nodiscard]] constexpr ::std::add_pointer_t<::std::remove_reference_t const> + get_ptr(::std::in_place_type_t = ::std::in_place_type) const noexcept { - return has_value(::std::in_place_type) ? detail::ptr_variadic_union(data) : nullptr; + return has_value(::std::in_place_type) + ? ::std::addressof(detail::_held(*detail::ptr_variadic_union(data))) + : nullptr; } /** @@ -1223,7 +1276,7 @@ constexpr inline bool _nothrow_copack_lift /** * @brief Lifts a value into a singular copack, decaying * - * Unlike `as_pack`, always by value: a copack alternative can never be a reference. + * Unlike `as_pack`, always by value; the in-place overload below can name a reference alternative. * * @param src Value to lift * @return A `copack` over the decayed type of `src`, holding it @@ -1315,7 +1368,7 @@ template [[nodiscard]] constexpr decltype(auto) get(Cp &&c) noexcept { using type = detail::_sole_alternative<::std::remove_cvref_t>::type; - if constexpr (::std::is_lvalue_reference_v) + if constexpr (::std::is_lvalue_reference_v || ::std::is_reference_v) return (*c.get_ptr(::std::in_place_type)); else return ::std::move(*c.get_ptr(::std::in_place_type)); @@ -1599,6 +1652,12 @@ template struct tuple_size<::fn::copack> : ::std::integral_const template struct tuple_element<0, ::fn::copack> { using type = T; }; + +// As a const pack's, a const copack's reference alternative is const, so `tuple_element<0, copack +// const>` must match `get` and cannot defer to the generic `tuple_element<0, const T>`. +template struct tuple_element<0, ::fn::copack const> { + using type = decltype(::fn::detail::_apply_const<::fn::copack const &, T>); +}; } // namespace std #include diff --git a/include/fn/detail/functional.hpp b/include/fn/detail/functional.hpp index cc423761..d114a201 100644 --- a/include/fn/detail/functional.hpp +++ b/include/fn/detail/functional.hpp @@ -21,6 +21,17 @@ namespace fn::inline LIBFN_VERSION::detail { +// A reference alternative T of a const copack is handed out const, as a pack's reference element is. +// Relocating it binds a new T to the same referent - bound as T to begin with - as relocating a pack's +// reference element does, so only the const the view added is removed. +template [[nodiscard]] constexpr auto _rebound(auto &&v) noexcept -> decltype(auto) +{ + if constexpr (::std::is_lvalue_reference_v) + return const_cast(v); // NOSONAR cpp:S859 removes only the const its own view added + else + return FWD(v); +} + namespace _fold_detail { // pack forbids rvalue references. template @@ -64,9 +75,9 @@ template struct _fold_rh final { template [[nodiscard]] constexpr auto operator()(::std::in_place_type_t, auto &&l) const - noexcept(noexcept(_fold(FWD(l), ::std::declval()))) + noexcept(noexcept(_fold(_rebound(FWD(l)), ::std::declval()))) { - return _fold(FWD(l), static_cast(rv)); + return _fold(_rebound(FWD(l)), static_cast(rv)); } }; @@ -75,9 +86,9 @@ template struct _fold_lh final { template [[nodiscard]] constexpr auto operator()(::std::in_place_type_t, auto &&r) const - noexcept(noexcept(_fold(::std::declval(), FWD(r)))) + noexcept(noexcept(_fold(_rebound(::std::declval()), _rebound(FWD(r))))) { - return _fold(static_cast(lv), FWD(r)); + return _fold(_rebound(static_cast(lv)), _rebound(FWD(r))); } }; @@ -534,12 +545,12 @@ template typename Tpl, typename... Ts, type constexpr inline bool _is_ts_applicable &, Tx...> = (... && _is_applicable::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_ts_applicable const &, Tx...> - = (... && _is_applicable::value); + = (... && _is_applicable const &, Ts>, Tx...>::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_ts_applicable &&, Tx...> = (... && _is_applicable::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_ts_applicable const &&, Tx...> - = (... && _is_applicable::value); + = (... && _is_applicable const &&, Ts>, Tx...>::value); template concept _typelist_applicable = _is_ts_applicable; @@ -549,13 +560,13 @@ constexpr inline bool _is_rts_applicable &, Tx...> = (... && _is_applicable_r::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_rts_applicable const &, Tx...> - = (... && _is_applicable_r::value); + = (... && _is_applicable_r const &, Ts>, Tx...>::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_rts_applicable &&, Tx...> = (... && _is_applicable_r::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_rts_applicable const &&, Tx...> - = (... && _is_applicable_r::value); + = (... && _is_applicable_r const &&, Ts>, Tx...>::value); template concept _typelist_applicable_r = _is_rts_applicable; @@ -567,13 +578,13 @@ constexpr inline bool _is_nothrow_ts_applicable &, Tx...> = (... && _is_nothrow_applicable::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_ts_applicable const &, Tx...> - = (... && _is_nothrow_applicable::value); + = (... && _is_nothrow_applicable const &, Ts>, Tx...>::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_ts_applicable &&, Tx...> = (... && _is_nothrow_applicable::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_ts_applicable const &&, Tx...> - = (... && _is_nothrow_applicable::value); + = (... && _is_nothrow_applicable const &&, Ts>, Tx...>::value); template concept _typelist_nothrow_applicable = _is_nothrow_ts_applicable; @@ -583,13 +594,13 @@ constexpr inline bool _is_nothrow_rts_applicable &, Tx...> = (... && _is_nothrow_applicable_r::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_rts_applicable const &, Tx...> - = (... && _is_nothrow_applicable_r::value); + = (... && _is_nothrow_applicable_r const &, Ts>, Tx...>::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_rts_applicable &&, Tx...> = (... && _is_nothrow_applicable_r::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_rts_applicable const &&, Tx...> - = (... && _is_nothrow_applicable_r::value); + = (... && _is_nothrow_applicable_r const &&, Ts>, Tx...>::value); template concept _typelist_nothrow_applicable_r = _is_nothrow_rts_applicable; diff --git a/include/fn/detail/variadic_union.hpp b/include/fn/detail/variadic_union.hpp index cbee56ec..2fcf735f 100644 --- a/include/fn/detail/variadic_union.hpp +++ b/include/fn/detail/variadic_union.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -58,13 +59,13 @@ constexpr inline bool _is_tst_invocable &, Tx...> = (... && _is_type_invocable::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_tst_invocable const &, Tx...> - = (... && _is_type_invocable::value); + = (... && _is_type_invocable const &, Ts>, Tx...>::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_tst_invocable &&, Tx...> = (... && _is_type_invocable::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_tst_invocable const &&, Tx...> - = (... && _is_type_invocable::value); + = (... && _is_type_invocable const &&, Ts>, Tx...>::value); template concept _typelist_type_invocable = _is_tst_invocable; @@ -94,13 +95,13 @@ constexpr inline bool _is_rtst_invocable &, Tx...> = (... && _is_type_invocable_r::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_rtst_invocable const &, Tx...> - = (... && _is_type_invocable_r::value); + = (... && _is_type_invocable_r const &, Ts>, Tx...>::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_rtst_invocable &&, Tx...> = (... && _is_type_invocable_r::value); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_rtst_invocable const &&, Tx...> - = (... && _is_type_invocable_r::value); + = (... && _is_type_invocable_r const &&, Ts>, Tx...>::value); template concept _typelist_type_invocable_r = _is_rtst_invocable; @@ -113,13 +114,17 @@ constexpr inline bool _is_nothrow_tst_invocable &, Tx...> = (... && ::std::is_nothrow_invocable_v, Ts &, Tx...>); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_tst_invocable const &, Tx...> - = (... && ::std::is_nothrow_invocable_v, Ts const &, Tx...>); + = (... + && ::std::is_nothrow_invocable_v, apply_const_lvalue_t const &, Ts>, + Tx...>); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_tst_invocable &&, Tx...> = (... && ::std::is_nothrow_invocable_v, Ts &&, Tx...>); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_tst_invocable const &&, Tx...> - = (... && ::std::is_nothrow_invocable_v, Ts const &&, Tx...>); + = (... + && ::std::is_nothrow_invocable_v, apply_const_lvalue_t const &&, Ts>, + Tx...>); template concept _typelist_type_nothrow_invocable = _is_nothrow_tst_invocable; @@ -129,16 +134,41 @@ constexpr inline bool _is_nothrow_rtst_invocable &, Tx...> = (... && ::std::is_nothrow_invocable_r_v, Ts &, Tx...>); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_rtst_invocable const &, Tx...> - = (... && ::std::is_nothrow_invocable_r_v, Ts const &, Tx...>); + = (... + && ::std::is_nothrow_invocable_r_v, + apply_const_lvalue_t const &, Ts>, Tx...>); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_rtst_invocable &&, Tx...> = (... && ::std::is_nothrow_invocable_r_v, Ts &&, Tx...>); template typename Tpl, typename... Ts, typename... Tx> constexpr inline bool _is_nothrow_rtst_invocable const &&, Tx...> - = (... && ::std::is_nothrow_invocable_r_v, Ts const &&, Tx...>); + = (... + && ::std::is_nothrow_invocable_r_v, + apply_const_lvalue_t const &&, Ts>, Tx...>); template concept _typelist_type_nothrow_invocable_r = _is_nothrow_rtst_invocable; +// A union member cannot be a reference: an lvalue-reference alternative is held as a pointer to its +// referent. The constructor binds as a reference initialization does, and copying or assigning the +// holder rebinds. +template struct _ref_holder final { + T *p; + constexpr _ref_holder(T &r) noexcept : p(::std::addressof(r)) {} // NOSONAR cpp:S1709 binds as a reference does +}; + +template +using _union_member_t = ::std::conditional_t<::std::is_reference_v, _ref_holder<::std::remove_reference_t>, T>; + +// The alternative T held in a union member: the member itself, or the referent of a reference, const +// where the member is, as a pack hands out a reference element +template [[nodiscard]] constexpr auto _held(auto &&m) noexcept -> decltype(auto) +{ + if constexpr (::std::is_reference_v) + return static_cast>(*m.p); + else + return FWD(m); +} + template union variadic_union; template <> union variadic_union<>; // Intentionally incomplete @@ -152,7 +182,8 @@ concept some_variadic_union = _is_variadic_union; template union variadic_union { static_assert(not _some_in_place_type); using t0 = T0; - T0 v0; + using m0 = _union_member_t; + m0 v0; template static constexpr bool has_type // @@ -160,8 +191,8 @@ template union variadic_union { static constexpr ::std::size_t size = 1; template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m0{FWD(args)...})) + requires ::std::is_same_v && requires { m0{FWD(args)...}; } : v0{FWD(args)...} { } @@ -171,19 +202,19 @@ template union variadic_union { // cases need none of them - and the trivial ones make the union exactly as trivial as its // members, for the copack to propagate. constexpr variadic_union(variadic_union const &) - requires(::std::is_trivially_copy_constructible_v) + requires(::std::is_trivially_copy_constructible_v) = default; constexpr variadic_union(variadic_union &&) - requires(::std::is_trivially_move_constructible_v) + requires(::std::is_trivially_move_constructible_v) = default; constexpr variadic_union &operator=(variadic_union const &) - requires(::std::is_trivially_copy_assignable_v) + requires(::std::is_trivially_copy_assignable_v) = default; constexpr variadic_union &operator=(variadic_union &&) - requires(::std::is_trivially_move_assignable_v) + requires(::std::is_trivially_move_assignable_v) = default; constexpr ~variadic_union() - requires(::std::is_trivially_destructible_v) + requires(::std::is_trivially_destructible_v) = default; // A union destroys nothing; this exists only to not be deleted when a member's destructor is // non-trivial, and its constrained pair above keeps the trivial case trivial. @@ -197,8 +228,10 @@ template union variadic_union { using t0 = T0; using t1 = T1; - T0 v0; - T1 v1; + using m0 = _union_member_t; + using m1 = _union_member_t; + m0 v0; + m1 v1; template static constexpr bool has_type // @@ -206,32 +239,32 @@ template union variadic_union { static constexpr ::std::size_t size = 2; template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m0{FWD(args)...})) + requires ::std::is_same_v && requires { m0{FWD(args)...}; } : v0{FWD(args)...} { } template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m1{FWD(args)...})) + requires ::std::is_same_v && requires { m1{FWD(args)...}; } : v1{FWD(args)...} { } constexpr variadic_union(variadic_union const &) - 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; constexpr variadic_union(variadic_union &&) - 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; constexpr variadic_union &operator=(variadic_union const &) - requires(::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v) + requires(::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v) = default; constexpr variadic_union &operator=(variadic_union &&) - requires(::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v) + requires(::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v) = default; constexpr ~variadic_union() - requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v) + requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v) = default; constexpr ~variadic_union() {} // NOSONAR cpp:S3490 the non-trivial pair of the constrained default }; @@ -247,9 +280,12 @@ template union variadic_union; + using m1 = _union_member_t; + using m2 = _union_member_t; + m0 v0; + m1 v1; + m2 v2; template static constexpr bool has_type // @@ -257,43 +293,43 @@ template union variadic_union - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m0{FWD(args)...})) + requires ::std::is_same_v && requires { m0{FWD(args)...}; } : v0{FWD(args)...} { } template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m1{FWD(args)...})) + requires ::std::is_same_v && requires { m1{FWD(args)...}; } : v1{FWD(args)...} { } template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m2{FWD(args)...})) + requires ::std::is_same_v && requires { m2{FWD(args)...}; } : v2{FWD(args)...} { } constexpr variadic_union(variadic_union const &) - requires(::std::is_trivially_copy_constructible_v && ::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 + && ::std::is_trivially_copy_constructible_v) = default; constexpr variadic_union(variadic_union &&) - requires(::std::is_trivially_move_constructible_v && ::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 + && ::std::is_trivially_move_constructible_v) = default; constexpr variadic_union &operator=(variadic_union const &) - requires(::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v - && ::std::is_trivially_copy_assignable_v) + requires(::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v + && ::std::is_trivially_copy_assignable_v) = default; constexpr variadic_union &operator=(variadic_union &&) - requires(::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v - && ::std::is_trivially_move_assignable_v) + requires(::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v + && ::std::is_trivially_move_assignable_v) = default; constexpr ~variadic_union() - requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v - && ::std::is_trivially_destructible_v) + requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v + && ::std::is_trivially_destructible_v) = default; constexpr ~variadic_union() {} // NOSONAR cpp:S3490 the non-trivial pair of the constrained default }; @@ -314,10 +350,14 @@ template union variadic_uni using t1 = T1; using t2 = T2; using t3 = T3; - T0 v0; - T1 v1; - T2 v2; - T3 v3; + using m0 = _union_member_t; + using m1 = _union_member_t; + using m2 = _union_member_t; + using m3 = _union_member_t; + m0 v0; + m1 v1; + m2 v2; + m3 v3; template static constexpr bool has_type // @@ -325,49 +365,49 @@ template union variadic_uni static constexpr ::std::size_t size = 4; template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m0{FWD(args)...})) + requires ::std::is_same_v && requires { m0{FWD(args)...}; } : v0{FWD(args)...} { } template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m1{FWD(args)...})) + requires ::std::is_same_v && requires { m1{FWD(args)...}; } : v1{FWD(args)...} { } template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m2{FWD(args)...})) + requires ::std::is_same_v && requires { m2{FWD(args)...}; } : v2{FWD(args)...} { } template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m3{FWD(args)...})) + requires ::std::is_same_v && requires { m3{FWD(args)...}; } : v3{FWD(args)...} { } constexpr variadic_union(variadic_union const &) - requires(::std::is_trivially_copy_constructible_v && ::std::is_trivially_copy_constructible_v - && ::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 + && ::std::is_trivially_copy_constructible_v && ::std::is_trivially_copy_constructible_v) = default; constexpr variadic_union(variadic_union &&) - requires(::std::is_trivially_move_constructible_v && ::std::is_trivially_move_constructible_v - && ::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 + && ::std::is_trivially_move_constructible_v && ::std::is_trivially_move_constructible_v) = default; constexpr variadic_union &operator=(variadic_union const &) - requires(::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v - && ::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v) + requires(::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v + && ::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v) = default; constexpr variadic_union &operator=(variadic_union &&) - requires(::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v - && ::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v) + requires(::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v + && ::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v) = default; constexpr ~variadic_union() - requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v - && ::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v) + requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v + && ::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v) = default; constexpr ~variadic_union() {} // NOSONAR cpp:S3490 the non-trivial pair of the constrained default }; @@ -395,11 +435,15 @@ union variadic_union { using t1 = T1; using t2 = T2; using t3 = T3; + using m0 = _union_member_t; + using m1 = _union_member_t; + using m2 = _union_member_t; + using m3 = _union_member_t; using more_t = variadic_union; - T0 v0; - T1 v1; - T2 v2; - T3 v3; + m0 v0; + m1 v1; + m2 v2; + m3 v3; more_t more; template @@ -409,26 +453,26 @@ union variadic_union { static constexpr ::std::size_t size = 4 + more_t::size; template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m0{FWD(args)...})) + requires ::std::is_same_v && requires { m0{FWD(args)...}; } : v0{FWD(args)...} { } template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m1{FWD(args)...})) + requires ::std::is_same_v && requires { m1{FWD(args)...}; } : v1{FWD(args)...} { } template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m2{FWD(args)...})) + requires ::std::is_same_v && requires { m2{FWD(args)...}; } : v2{FWD(args)...} { } template - constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(T{FWD(args)...})) - requires ::std::is_same_v && requires { T{FWD(args)...}; } + constexpr variadic_union(::std::in_place_type_t, Args &&...args) noexcept(noexcept(m3{FWD(args)...})) + requires ::std::is_same_v && requires { m3{FWD(args)...}; } : v3{FWD(args)...} { } @@ -441,28 +485,28 @@ union variadic_union { } constexpr variadic_union(variadic_union const &) - requires(::std::is_trivially_copy_constructible_v && ::std::is_trivially_copy_constructible_v - && ::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 + && ::std::is_trivially_copy_constructible_v && ::std::is_trivially_copy_constructible_v && ::std::is_trivially_copy_constructible_v) = default; constexpr variadic_union(variadic_union &&) - requires(::std::is_trivially_move_constructible_v && ::std::is_trivially_move_constructible_v - && ::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 + && ::std::is_trivially_move_constructible_v && ::std::is_trivially_move_constructible_v && ::std::is_trivially_move_constructible_v) = default; constexpr variadic_union &operator=(variadic_union const &) - requires(::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v - && ::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v + requires(::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v + && ::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v && ::std::is_trivially_copy_assignable_v) = default; constexpr variadic_union &operator=(variadic_union &&) - requires(::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v - && ::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v + requires(::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v + && ::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v && ::std::is_trivially_move_assignable_v) = default; constexpr ~variadic_union() - requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v - && ::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v + requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v + && ::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v) = default; constexpr ~variadic_union() {} // NOSONAR cpp:S3490 the non-trivial pair of the constrained default @@ -516,8 +560,8 @@ template #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif template -[[nodiscard]] constexpr U make_variadic_union(auto &&...args) noexcept(noexcept(T{FWD(args)...})) - requires(U::template has_type) && requires { T{FWD(args)...}; } +[[nodiscard]] constexpr U make_variadic_union(auto &&...args) noexcept(noexcept(_union_member_t{FWD(args)...})) + requires(U::template has_type) && requires { _union_member_t{FWD(args)...}; } { return U(::std::in_place_type, FWD(args)...); } @@ -546,7 +590,7 @@ template && _typelist_applicable_r // { if (index == 0) - return static_cast(_apply(FWD(fn), FWD(v).v0, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v0), FWD(args)...)); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -562,7 +606,7 @@ template && _typelist_type_invocable_r // { if (index == 0) - return _invoke_type(FWD(fn), FWD(v).v0, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v0), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -573,7 +617,7 @@ constexpr void apply_variadic_union(some_variadic_union auto &&v, ::std::size_t && _typelist_applicable // { if (index == 0) - return (void)_apply(FWD(fn), FWD(v).v0, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v0), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -584,7 +628,7 @@ constexpr void invoke_type_variadic_union(some_variadic_union auto &&v, ::std::s && _typelist_type_invocable // { if (index == 0) - return (void)_invoke_type(FWD(fn), FWD(v).v0, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v0), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -596,9 +640,9 @@ template && _typelist_applicable_r // { if (index == 0) - return static_cast(_apply(FWD(fn), FWD(v).v0, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v0), FWD(args)...)); else if (index == 1) - return static_cast(_apply(FWD(fn), FWD(v).v1, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v1), FWD(args)...)); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -610,9 +654,9 @@ template && _typelist_type_invocable_r // { if (index == 0) - return _invoke_type(FWD(fn), FWD(v).v0, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return _invoke_type(FWD(fn), FWD(v).v1, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v1), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -623,9 +667,9 @@ constexpr void apply_variadic_union(some_variadic_union auto &&v, ::std::size_t && _typelist_applicable // { if (index == 0) - return (void)_apply(FWD(fn), FWD(v).v0, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return (void)_apply(FWD(fn), FWD(v).v1, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v1), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -636,9 +680,9 @@ constexpr void invoke_type_variadic_union(some_variadic_union auto &&v, ::std::s && _typelist_type_invocable // { if (index == 0) - return (void)_invoke_type(FWD(fn), FWD(v).v0, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return (void)_invoke_type(FWD(fn), FWD(v).v1, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v1), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -650,11 +694,11 @@ template && _typelist_applicable_r // { if (index == 0) - return static_cast(_apply(FWD(fn), FWD(v).v0, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v0), FWD(args)...)); else if (index == 1) - return static_cast(_apply(FWD(fn), FWD(v).v1, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v1), FWD(args)...)); else if (index == 2) - return static_cast(_apply(FWD(fn), FWD(v).v2, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v2), FWD(args)...)); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -666,11 +710,11 @@ template && _typelist_type_invocable_r // { if (index == 0) - return _invoke_type(FWD(fn), FWD(v).v0, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return _invoke_type(FWD(fn), FWD(v).v1, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v1), FWD(args)...); else if (index == 2) - return _invoke_type(FWD(fn), FWD(v).v2, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v2), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -681,11 +725,11 @@ constexpr void apply_variadic_union(some_variadic_union auto &&v, ::std::size_t && _typelist_applicable // { if (index == 0) - return (void)_apply(FWD(fn), FWD(v).v0, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return (void)_apply(FWD(fn), FWD(v).v1, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v1), FWD(args)...); else if (index == 2) - return (void)_apply(FWD(fn), FWD(v).v2, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v2), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -696,11 +740,11 @@ constexpr void invoke_type_variadic_union(some_variadic_union auto &&v, ::std::s && _typelist_type_invocable // { if (index == 0) - return (void)_invoke_type(FWD(fn), FWD(v).v0, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return (void)_invoke_type(FWD(fn), FWD(v).v1, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v1), FWD(args)...); else if (index == 2) - return (void)_invoke_type(FWD(fn), FWD(v).v2, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v2), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -712,13 +756,13 @@ template && _typelist_applicable_r // { if (index == 0) - return static_cast(_apply(FWD(fn), FWD(v).v0, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v0), FWD(args)...)); else if (index == 1) - return static_cast(_apply(FWD(fn), FWD(v).v1, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v1), FWD(args)...)); else if (index == 2) - return static_cast(_apply(FWD(fn), FWD(v).v2, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v2), FWD(args)...)); else if (index == 3) - return static_cast(_apply(FWD(fn), FWD(v).v3, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v3), FWD(args)...)); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -730,13 +774,13 @@ template && _typelist_type_invocable_r // { if (index == 0) - return _invoke_type(FWD(fn), FWD(v).v0, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return _invoke_type(FWD(fn), FWD(v).v1, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v1), FWD(args)...); else if (index == 2) - return _invoke_type(FWD(fn), FWD(v).v2, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v2), FWD(args)...); else if (index == 3) - return _invoke_type(FWD(fn), FWD(v).v3, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v3), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -747,13 +791,13 @@ constexpr void apply_variadic_union(some_variadic_union auto &&v, ::std::size_t && _typelist_applicable // { if (index == 0) - return (void)_apply(FWD(fn), FWD(v).v0, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return (void)_apply(FWD(fn), FWD(v).v1, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v1), FWD(args)...); else if (index == 2) - return (void)_apply(FWD(fn), FWD(v).v2, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v2), FWD(args)...); else if (index == 3) - return (void)_apply(FWD(fn), FWD(v).v3, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v3), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -764,13 +808,13 @@ constexpr void invoke_type_variadic_union(some_variadic_union auto &&v, ::std::s && _typelist_type_invocable // { if (index == 0) - return (void)_invoke_type(FWD(fn), FWD(v).v0, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return (void)_invoke_type(FWD(fn), FWD(v).v1, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v1), FWD(args)...); else if (index == 2) - return (void)_invoke_type(FWD(fn), FWD(v).v2, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v2), FWD(args)...); else if (index == 3) - return (void)_invoke_type(FWD(fn), FWD(v).v3, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v3), FWD(args)...); ::pfn::unreachable(); // LCOV_EXCL_LINE } @@ -782,13 +826,13 @@ template && _typelist_applicable_r // { if (index == 0) - return static_cast(_apply(FWD(fn), FWD(v).v0, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v0), FWD(args)...)); else if (index == 1) - return static_cast(_apply(FWD(fn), FWD(v).v1, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v1), FWD(args)...)); else if (index == 2) - return static_cast(_apply(FWD(fn), FWD(v).v2, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v2), FWD(args)...)); else if (index == 3) - return static_cast(_apply(FWD(fn), FWD(v).v3, FWD(args)...)); + return static_cast(_apply(FWD(fn), _held(FWD(v).v3), FWD(args)...)); else return apply_variadic_union(FWD(v).more, index - 4, FWD(fn), FWD(args)...); } @@ -801,13 +845,13 @@ template && _typelist_type_invocable_r // { if (index == 0) - return _invoke_type(FWD(fn), FWD(v).v0, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return _invoke_type(FWD(fn), FWD(v).v1, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v1), FWD(args)...); else if (index == 2) - return _invoke_type(FWD(fn), FWD(v).v2, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v2), FWD(args)...); else if (index == 3) - return _invoke_type(FWD(fn), FWD(v).v3, FWD(args)...); + return _invoke_type(FWD(fn), _held(FWD(v).v3), FWD(args)...); else return invoke_type_variadic_union(FWD(v).more, index - 4, FWD(fn), FWD(args)...); } @@ -819,13 +863,13 @@ constexpr void apply_variadic_union(some_variadic_union auto &&v, ::std::size_t && _typelist_applicable // { if (index == 0) - return (void)_apply(FWD(fn), FWD(v).v0, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return (void)_apply(FWD(fn), FWD(v).v1, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v1), FWD(args)...); else if (index == 2) - return (void)_apply(FWD(fn), FWD(v).v2, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v2), FWD(args)...); else if (index == 3) - return (void)_apply(FWD(fn), FWD(v).v3, FWD(args)...); + return (void)_apply(FWD(fn), _held(FWD(v).v3), FWD(args)...); else return apply_variadic_union(FWD(v).more, index - 4, FWD(fn), FWD(args)...); } @@ -837,13 +881,13 @@ constexpr void invoke_type_variadic_union(some_variadic_union auto &&v, ::std::s && _typelist_type_invocable // { if (index == 0) - return (void)_invoke_type(FWD(fn), FWD(v).v0, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v0), FWD(args)...); else if (index == 1) - return (void)_invoke_type(FWD(fn), FWD(v).v1, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v1), FWD(args)...); else if (index == 2) - return (void)_invoke_type(FWD(fn), FWD(v).v2, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v2), FWD(args)...); else if (index == 3) - return (void)_invoke_type(FWD(fn), FWD(v).v3, FWD(args)...); + return (void)_invoke_type(FWD(fn), _held(FWD(v).v3), FWD(args)...); else return invoke_type_variadic_union(FWD(v).more, index - 4, FWD(fn), FWD(args)...); } diff --git a/include/fn/just.hpp b/include/fn/just.hpp index e979ccd5..e5a5dc4b 100644 --- a/include/fn/just.hpp +++ b/include/fn/just.hpp @@ -791,29 +791,27 @@ template struct just> { template static constexpr bool has_type = value_type::template has_type; /** - * @brief Constructs the alternative matching the value's type after removing cv/ref qualifiers + * @brief Constructs the alternative the value selects, as `copack`'s converting constructor does * * Explicit exactly where the conversion to that alternative is. * * @param v Value of one alternative */ - template - constexpr just(T &&v) // NOSONAR cpp:S1709,S6458 implicit arm of the explicit pair; has_type excludes self - noexcept(::std::is_nothrow_constructible_v>, T &&>) - requires has_type<::std::remove_cvref_t> - && ::std::is_constructible_v>, T &&> - && ::std::is_convertible_v> - : v_(::std::in_place_type<::std::remove_cvref_t>, FWD(v)) + template > + constexpr just(U &&v) // NOSONAR cpp:S1709,S6458 implicit arm of the explicit pair; has_type excludes self + noexcept(::std::is_nothrow_constructible_v, U &&>) + requires has_type && ::std::is_constructible_v, U &&> + && ::std::is_convertible_v + : v_(::std::in_place_type, FWD(v)) { } - template - constexpr explicit just(T &&v) // NOSONAR cpp:S6458 has_type excludes self - noexcept(::std::is_nothrow_constructible_v>, T &&>) - requires has_type<::std::remove_cvref_t> - && ::std::is_constructible_v>, T &&> - && (not ::std::is_convertible_v>) - : v_(::std::in_place_type<::std::remove_cvref_t>, FWD(v)) + template > + constexpr explicit just(U &&v) // NOSONAR cpp:S6458 has_type excludes self + noexcept(::std::is_nothrow_constructible_v, U &&>) + requires has_type && ::std::is_constructible_v, U &&> + && (not ::std::is_convertible_v) + : v_(::std::in_place_type, FWD(v)) { } @@ -1026,21 +1024,24 @@ template struct just> { } /** - * @brief Pointer to the alternative `T`, or `nullptr` where it is not the one held + * @brief Pointer to the alternative `T` - to the referent, for a reference alternative - or `nullptr` + * where it is not the one held * * @tparam T The alternative to access * @return Pointer to the alternative, or `nullptr` */ template requires has_type - [[nodiscard]] constexpr T *get_ptr(::std::in_place_type_t d = ::std::in_place_type) noexcept + [[nodiscard]] constexpr ::std::add_pointer_t get_ptr(::std::in_place_type_t d + = ::std::in_place_type) noexcept { return v_.get_ptr(d); } template requires has_type - [[nodiscard]] constexpr T const *get_ptr(::std::in_place_type_t d = ::std::in_place_type) const noexcept + [[nodiscard]] constexpr ::std::add_pointer_t<::std::remove_reference_t const> + get_ptr(::std::in_place_type_t d = ::std::in_place_type) const noexcept { return v_.get_ptr(d); } diff --git a/tests/fn/algebra.cpp b/tests/fn/algebra.cpp index 1e8d705e..b03dfc5f 100644 --- a/tests/fn/algebra.cpp +++ b/tests/fn/algebra.cpp @@ -266,6 +266,30 @@ TEST_CASE("operator &", "[pack][copack][operator_and]") CHECK(held() == 7); } + SECTION("a reference alternative stays a reference element") + { + // the product holds what the alternative holds, as a lifted lvalue does in as_pack; the factor is + // the alternative's type, so a const copack contributes the same reference + constexpr auto battery = [] { + int x = 1; + auto r = fn::copack{x} & fn::pack{2}; + static_assert(std::is_same_v>>); + auto l = fn::pack{2} & fn::copack{x}; + static_assert(std::is_same_v>>); + fn::copack const c{x}; + auto rc = c & fn::pack{2}; + auto lc = fn::pack{2} & c; + auto cc = c & c; + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>>); + return &fn::get<0>(fn::get(r)) == &x && &fn::get<1>(fn::get(l)) == &x && &fn::get<0>(fn::get(rc)) == &x + && &fn::get<1>(fn::get(lc)) == &x && &fn::get<1>(fn::get(cc)) == &x; + }; + static_assert(battery()); + CHECK(battery()); + } + SECTION("the data fold takes data, never a carrier") { constexpr auto can = [](auto &&...args) { return requires { fn::conjoin(FWD(args)...); }; }; diff --git a/tests/fn/and_then.cpp b/tests/fn/and_then.cpp index f55b8d3e..350bc930 100644 --- a/tests/fn/and_then.cpp +++ b/tests/fn/and_then.cpp @@ -1720,6 +1720,25 @@ TEST_CASE("and_then joins heterogeneous optional branches", "[and_then][optional CHECK(rr.value() == X{}); } + SECTION("a reference branch joins as a reference alternative") + { + constexpr auto fnRef = [](X &x) { + return fn::overload{[&x](A) { return fn::optional{x}; }, [](B) { return fn::optional{Y{}}; }}; + }; + X x{}; + auto r = In{fn::copack_for{A{}}}.and_then(fnRef(x)); + static_assert(std::is_same_v>>); + CHECK(r.value().get_ptr() == &x); + auto p = In{fn::copack_for{A{}}} | fn::and_then(fnRef(x)); + static_assert(std::is_same_v>>); + CHECK(p.value().get_ptr() == &x); + static_assert([fnRef] { + X x{}; + In const in{fn::copack_for{A{}}}; + return in.and_then(fnRef(x)).value().get_ptr() == &x; + }()); + } + SECTION("constraints and noexcept") { // a mixed optional-and-expected set answers, not errors @@ -1729,11 +1748,6 @@ TEST_CASE("and_then joins heterogeneous optional branches", "[and_then][optional static_assert(canM(In{fn::copack_for{A{}}}, fnJoin)); // converse static_assert(fn::applicable_and_then); static_assert(not fn::applicable_and_then); - // a reference-carrying optional leaves the join unformable rather than copying the referent - constexpr auto fnRef = fn::overload{[](A) -> fn::optional { throw 0; }, // - [](B) { return fn::optional{Y{}}; }}; - static_assert(not canM(In{fn::copack_for{A{}}}, fnRef)); - In v{fn::copack_for{A{}}}; constexpr auto fnNothrow = fn::overload{[](A) noexcept { return fn::optional{X{}}; }, [](B) noexcept { return fn::optional{Y{}}; }}; diff --git a/tests/fn/copack.cpp b/tests/fn/copack.cpp index 9e19523c..7a3b5ebd 100644 --- a/tests/fn/copack.cpp +++ b/tests/fn/copack.cpp @@ -119,6 +119,9 @@ concept can_transform = requires(S s, Fn fn) { FWD(s).transform(fn); }; template concept can_apply_type = requires(S s, Fn fn, Args... args) { FWD(s).apply_type(FWD(fn), FWD(args)...); }; +template +concept can_apply = requires(S s, Fn fn) { FWD(s).apply(FWD(fn)); }; + template concept can_apply_type_r = requires(S s, Fn fn, Args... args) { FWD(s).template apply_type_r(FWD(fn), FWD(args)...); }; @@ -131,6 +134,9 @@ concept can_get_at = requires(S s) { fn::get(FWD(s)); }; template concept has_tuple_size = requires { std::tuple_size::value; }; + +constexpr int nttp_referent = 3; +constexpr int nttp_referent_twin = 3; } // anonymous namespace // A copack brace-initializes the alternative it stores. That is a DESIGN DIRECTION, not an @@ -366,6 +372,32 @@ TEST_CASE("copack basic functionality tests", "[copack]") static_assert(std::same_as, fn::copack>, fn::copack>); static_assert( std::same_as, fn::copack>, fn::copack>); + + SECTION("reference alternatives") + { + // T, T& and T const& are distinct alternatives, ordered and deduplicated like any other + static_assert(fn::copack_for::size == 3); + static_assert(std::same_as, fn::copack>); + static_assert(std::same_as, fn::copack_for>); + static_assert(std::same_as, int &>, fn::copack_for>); + + // The admission is a static_assert in the copack's body, which no probe can observe failing, + // so the trait behind it is asked directly + using fn::detail::_is_valid_copack_subtype; + static_assert(_is_valid_copack_subtype); + static_assert(_is_valid_copack_subtype); + static_assert(_is_valid_copack_subtype); + static_assert(_is_valid_copack_subtype); + static_assert(_is_valid_copack_subtype>); + static_assert(not _is_valid_copack_subtype); + static_assert(not _is_valid_copack_subtype); + static_assert(not _is_valid_copack_subtype); + static_assert(not _is_valid_copack_subtype &>); + static_assert(not _is_valid_copack_subtype const &>); + static_assert(not _is_valid_copack_subtype &>); + static_assert(not _is_valid_copack_subtype &>); + SUCCEED(); + } } SECTION("applicable") @@ -515,6 +547,75 @@ TEST_CASE("copack basic functionality tests", "[copack]") static_assert(std::is_same_v); static_assert(b.has_value()); } + + SECTION("reference alternative") + { + int x = 1; + copack a{x}; + CHECK(a.get_ptr() == &x); + copack b = x; // implicit, as binding a reference is + CHECK(b.get_ptr() == &x); + static_assert([] { + int x = 1; + copack const a{x}; + return a.get_ptr() == &x; + }()); + + // deduction decays: a reference alternative is always named + copack c{x}; + static_assert(std::is_same_v>); + + SECTION("selection") + { + // the value selects as overload resolution among f(int), f(int&) and f(int const&) would + using RC = fn::copack_for; + int const z = 3; + CHECK(RC{x}.has_value(std::in_place_type)); // the less cv-qualified binding wins + CHECK(RC{z}.get_ptr() == &z); + CHECK(RC{std::as_const(x)}.get_ptr() == &x); + + using IR = fn::copack_for; + static_assert(not std::is_constructible_v); // ambiguous, as f(int) and f(int&) are + CHECK(IR{std::move(x)}.has_value(std::in_place_type)); // int& binds no rvalue + CHECK(IR{std::as_const(x)}.has_value(std::in_place_type)); + CHECK(IR{std::in_place_type, x}.get_ptr() == &x); + + // f(int) and f(int const&) are ambiguous for every int, so only a tag or widening builds this one + using ICR = fn::copack_for; + static_assert(not std::is_constructible_v); + static_assert(not std::is_constructible_v); + static_assert(not std::is_constructible_v); + CHECK(ICR{std::in_place_type, x}.get_ptr() == &x); + CHECK(ICR{copack{x}}.get_ptr() == &x); + + static_assert([] { + int x = 1; + return RC{x}.has_value(std::in_place_type) && IR{std::move(x)}.has_value(std::in_place_type) + && ICR{std::in_place_type, x}.get_ptr() == &x; + }()); + } + + SECTION("constraints") + { + // a merely convertible value selects nothing, as for an owning alternative + static_assert(not std::is_constructible_v, int &>); + static_assert(not std::is_constructible_v, int &>); + static_assert(not std::is_constructible_v, int>); + static_assert(not std::is_constructible_v, int const &>); + struct Base { + int v; + }; + struct Derived : Base {}; + static_assert(not std::is_constructible_v, Derived &>); + static_assert(std::is_constructible_v, Base &>); // converse + + // ... while the tag names the alternative, binding as a reference initialization does + Derived d{}; + copack c{std::in_place_type, d}; + CHECK(c.get_ptr() == static_cast(&d)); + static_assert(not std::is_constructible_v, std::in_place_type_t, Base>); + } + } } SECTION("forwarding constructors (immovable)") @@ -532,7 +633,9 @@ TEST_CASE("copack basic functionality tests", "[copack]") // Invalid alternative types make the deduction probe false without a hard instantiation error. static_assert(can_deduce_in_place); - static_assert(not can_deduce_in_place); + static_assert(can_deduce_in_place); + static_assert(not can_deduce_in_place); + static_assert(not can_deduce_in_place &, fn::pack &>); static_assert(not can_deduce_in_place); static_assert(not can_deduce_in_place); static_assert(not can_deduce_in_place, int>); @@ -698,6 +801,27 @@ TEST_CASE("copack basic functionality tests", "[copack]") CHECK_THROWS_AS(Z(tag{}, std::move(std::as_const(src))), int); CHECK_NOTHROW(Z(tag{}, std::move(src))); } + + SECTION("reference alternatives") + { + int x = 1; + copack const a{x}; + fn::copack_for b{a}; + CHECK(b.get_ptr() == &x); + fn::copack_for c = copack{x}; + CHECK(c.get_ptr() == &x); + fn::copack_for d{std::in_place_type>, a}; + CHECK(d.get_ptr() == &x); + static_assert([] { + int x = 1; + fn::copack_for const b = copack{x}; + return b.get_ptr() == &x; + }()); + + // the alternative is kept: a reference never decays into the owning one + static_assert(not std::is_constructible_v, copack>); + static_assert(std::is_constructible_v, copack>); // converse + } } SECTION("has_type type mismatch") @@ -770,6 +894,28 @@ TEST_CASE("copack basic functionality tests", "[copack]") constexpr auto d = fn::copack{std::in_place_type, 12}; static_assert(d.get_ptr(std::in_place_type) == nullptr); static_assert(*d.get_ptr(std::in_place_type) == 12); + + SECTION("reference alternative") + { + // the pointer is to the referent, const through a const copack, as through a const pack + int x = 1; + using R = fn::copack_for; + R r{x}; + static_assert(std::is_same_v()), int *>); + static_assert(std::is_same_v()), int const *>); + static_assert(std::is_same_v()), int const *>); + CHECK(r.get_ptr() == &x); + CHECK(std::as_const(r).get_ptr() == &x); + CHECK(r.get_ptr() == nullptr); + *r.get_ptr() = 2; + CHECK(x == 2); + static_assert([] { + int x = 1; + R r{x}; + *r.get_ptr() = 2; + return x == 2 && std::as_const(r).get_ptr() == &x; + }()); + } } SECTION("equality comparison") @@ -862,6 +1008,25 @@ TEST_CASE("copack basic functionality tests", "[copack]") SUCCEED(); } + + SECTION("reference alternatives") + { + int x = 1; + int y = 1; + int z = 2; + using R = copack; + using IR = fn::copack_for; + CHECK(R{x} == R{y}); // the referents compare + CHECK(R{x} != R{z}); + CHECK(R{x} == IR{std::in_place_type, y}); + CHECK(IR{std::in_place_type, x} != IR{1}); // distinct alternatives never compare equal + CHECK(R{x} != copack{1}); + static_assert([] { + int x = 1; + int y = 1; + return R{x} == R{y} && IR{std::in_place_type, x} != IR{1}; + }()); + } } SECTION("apply") @@ -981,6 +1146,33 @@ TEST_CASE("copack basic functionality tests", "[copack]") static_assert(copack{2}.apply(fn, 3) == 5); } } + + SECTION("reference alternative") + { + // the callable receives T&, or T const& from a const copack, as from a pack - whatever the value + // category + struct Other final {}; + int x = 1; + using R = fn::copack_for; + R r{x}; + constexpr auto addr = fn::overload{[](int &i) { return &i; }, [](Other) -> int * { return nullptr; }}; + constexpr auto caddr + = fn::overload{[](int const &i) { return &i; }, [](Other) -> int const * { return nullptr; }}; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(not can_apply); + static_assert(not can_apply); + CHECK(r.apply(addr) == &x); + CHECK(R{x}.apply(addr) == &x); + CHECK(std::as_const(r).apply(caddr) == &x); + CHECK(std::move(std::as_const(r)).apply(caddr) == &x); + CHECK(std::as_const(r).template apply_r(caddr) == &x); + static_assert([addr, caddr] { + int x = 1; + R r{x}; + return R{x}.apply(addr) == &x && std::move(std::as_const(r)).apply(caddr) == &x; + }()); + } } SECTION("apply_r") @@ -1299,6 +1491,15 @@ TEST_CASE("copack basic functionality tests", "[copack]") CHECK(read_nttp() == 42.0); CHECK(read_nttp() == 43.0); // the pack alternative spreads: 42 + true CHECK(read_nttp() == 42.0); + + // a reference alternative is a template argument as a reference template parameter is: by the + // identity of its referent, never by its value + constexpr copack j{nttp_referent}; + constexpr copack k{nttp_referent}; + constexpr copack l{nttp_referent_twin}; + static_assert(std::is_same_v, some_copack_nttp>); + static_assert(not std::is_same_v, some_copack_nttp>); + CHECK(read_nttp() == 3.0); } } @@ -1489,6 +1690,32 @@ TEST_CASE("copack apply_type", "[copack][apply_type]") } } + SECTION("reference alternative") + { + // the tag names the reference alternative, and the arm receives T&, or T const& from a const + // copack, whatever the value category + int x = 1; + using R = fn::copack_for; + R r{std::in_place_type, x}; + constexpr auto arms = fn::overload{[](std::in_place_type_t, auto &&) -> int { return 0; }, + [](std::in_place_type_t, int &) { return 1; }, + [](std::in_place_type_t, int const &) { return 2; }}; + CHECK(r.apply_type(arms) == 1); + CHECK(std::as_const(r).apply_type(arms) == 2); + CHECK(std::move(r).apply_type(arms) == 1); + CHECK(std::move(std::as_const(r)).apply_type(arms) == 2); + CHECK(R{1}.apply_type(arms) == 0); // the rows stay apart where the values interconvert + + constexpr auto owning = [](std::in_place_type_t, auto &&) { return 0; }; + static_assert(not can_apply_type); // a missing row is rejected + static_assert(can_apply_type &, decltype(owning)>); + static_assert([arms] { + int x = 1; + R const r{std::in_place_type, x}; + return std::move(r).apply_type(arms) == 2 && R{std::in_place_type, x}.apply_type(arms) == 1; + }()); + } + SECTION("apply_type_r") { static_assert(std::is_same_v(arms))>); @@ -1617,6 +1844,21 @@ TEST_CASE("copack noexcept", "[copack][noexcept]") static_assert(not noexcept(std::declval &>() = std::declval())); SUCCEED(); } + + SECTION("reference alternatives") + { + // binding and rebinding a reference cannot throw, whatever a sibling alternative does + using R = fn::copack_for; + static_assert(noexcept(R{std::declval()})); + static_assert(noexcept(R{std::in_place_type, std::declval()})); + static_assert(not noexcept(R{std::declval()})); // converse + static_assert(noexcept(std::declval() = std::declval())); + static_assert( + not noexcept(std::declval &>() = std::declval())); + static_assert(noexcept(std::declval().template emplace(std::declval()))); + static_assert(std::is_nothrow_copy_assignable_v>); + SUCCEED(); + } } TEST_CASE("copack triviality", "[copack][triviality]") @@ -1649,6 +1891,21 @@ TEST_CASE("copack triviality", "[copack][triviality]") SUCCEED(); } + SECTION("of references: a pointer to the referent") + { + using R = fn::copack_for; + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_destructible_v); + 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); + // assignment rebinds, so a const referent, which cannot be assigned through, does not matter + static_assert(std::is_trivially_copy_assignable_v>); + static_assert(std::is_trivially_move_assignable_v>); + SUCCEED(); + } + SECTION("each operation follows its own gate") { // a pack holding a reference is trivially copyable and refuses assignment: the operations @@ -2211,6 +2468,29 @@ TEST_CASE("copack move and copy", "[copack][has_value][get_ptr]") static_assert(std::is_nothrow_move_constructible_v>); SUCCEED(); } + + SECTION("reference alternative") + { + // copies refer to the same referent, and destroying them leaves it alive + int const live = Counted::live; + Counted c{1}; + { + fn::copack_for a{c}; + auto b = a; + auto m = std::move(a); + CHECK(b.get_ptr() == &c); + CHECK(m.get_ptr() == &c); + CHECK(Counted::live == live + 1); + } + CHECK(Counted::live == live + 1); + static_assert([] { + int x = 1; + fn::copack_for const a{x}; + auto b = a; + auto m = std::move(b); + return m.get_ptr() == &x; + }()); + } } TEST_CASE("copack assignment", "[copack][assignment]") @@ -2624,6 +2904,89 @@ TEST_CASE("copack assignment", "[copack][assignment]") SUCCEED(); } + SECTION("rebinds a reference") + { + // never assigns through: the old referent keeps its value + int x = 1; + int y = 2; + using R = fn::copack_for; + + SECTION("same alternative") + { + R a{x}; + R const b{y}; + a = b; + CHECK(a.get_ptr() == &y); + a = R{x}; + CHECK(a.get_ptr() == &x); + copack t{x}; + t = copack{y}; // the trivial arm + CHECK(t.get_ptr() == &y); + CHECK(x == 1); + CHECK(y == 2); + } + + SECTION("the alternative changes") + { + R a{std::string{"abc"}}; + a = R{x}; + CHECK(a.get_ptr() == &x); + a = R{std::string{"def"}}; + CHECK(*a.get_ptr() == "def"); + CHECK(x == 1); + } + + SECTION("from a value") + { + R a{x}; + a = y; + CHECK(a.get_ptr() == &y); + CHECK(x == 1); + + // the value selects as it does in construction + using IR = fn::copack_for; + static_assert(not std::is_assignable_v); + IR b{std::in_place_type, x}; + b = 5; + CHECK(b.has_value(std::in_place_type)); + CHECK(x == 1); + } + + SECTION("widening") + { + fn::copack_for a{5}; + a = copack{x}; + CHECK(a.get_ptr() == &x); + copack const c{y}; + a = c; + CHECK(a.get_ptr() == &y); + CHECK(x == 1); + } + + SECTION("const referent") + { + fn::copack_for a{std::as_const(x)}; + a = fn::copack_for{std::as_const(y)}; + CHECK(a.get_ptr() == &y); + a = std::as_const(x); + CHECK(a.get_ptr() == &x); + } + + SECTION("constexpr") + { + static_assert([] { + int x = 1; + int y = 2; + fn::copack_for a{x}; + a = fn::copack_for{y}; + a = 3L; + a = x; + return a.get_ptr() == &x && x == 1 && y == 2; + }()); + SUCCEED(); + } + } + SECTION("strong exception guarantee") { SECTION("a throwing assignment is rolled back") @@ -2967,6 +3330,26 @@ TEST_CASE("copack emplace", "[copack][emplace]") static_assert(repoint()); } + SECTION("binds a reference") + { + using R = fn::copack_for; + int x = 1; + int y = 2; + R a{x}; + int &r = a.emplace(y); + CHECK(&r == &y); + CHECK(a.get_ptr() == &y); + CHECK(x == 1); + static_assert(not can_emplace); // a temporary does not bind + static_assert(can_emplace); + static_assert([] { + int x = 1; + int y = 2; + R a{3L}; + return &a.emplace(x) == &x && &a.emplace(y) == &y && x == 1; + }()); + } + SECTION("constraints") { static_assert(can_emplace); @@ -3125,6 +3508,38 @@ TEST_CASE("copack get and tuple protocol", "[copack][get][tuple]") }()); } + SECTION("reference alternative") + { + // every value category yields the reference itself, as apply passes it + int x = 1; + using R = fn::copack; + R r{x}; + // const reaches the referent, as through a const pack + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v(std::move(r))), int &>); + static_assert(std::is_same_v, int &>); + static_assert(std::is_same_v, int const &>); + static_assert(std::is_same_v const>, int const>); + CHECK(&fn::get(R{x}) == &x); + CHECK(&fn::get<0>(std::as_const(r)) == &x); + + auto [b] = r; + b = 2; + CHECK(x == 2); + auto const &[c] = r; + static_assert(std::is_same_v); + CHECK(&c == &x); + static_assert([] { + int x = 1; + auto [b] = R{x}; + b = 2; + return x == 2; + }()); + } + SECTION("one-element pack") { // These tuple traits and access types match those of pack. diff --git a/tests/fn/detail/variadic_union.cpp b/tests/fn/detail/variadic_union.cpp index b47d7019..bdd4e777 100644 --- a/tests/fn/detail/variadic_union.cpp +++ b/tests/fn/detail/variadic_union.cpp @@ -197,3 +197,40 @@ TEST_CASE("variadic_union with a non-copyable alternative", "[variadic_union][ma CHECK(ptr_variadic_union(a4)->v == 42); } + +TEST_CASE("variadic_union with a reference alternative", "[variadic_union][make_variadic_union][apply_variadic_union]") +{ + // A union member cannot be a reference: the alternative is held as a pointer, and dispatch hands + // out the referent as an lvalue in every value category of the union, const where the union is + using T = variadic_union; + static_assert(T::has_type); + static_assert(T::has_type); + static_assert(not T::has_type); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copy_assignable_v); // assigning the holder of int const& rebinds it + + constexpr auto addr = [](auto &&i) -> void const * { return &i; }; + constexpr auto tagged = [](std::in_place_type_t, auto &&i) { + if constexpr (not std::is_same_v) + return 0; + else if constexpr (std::is_same_v) + return 1; + else + return std::is_same_v ? 2 : 0; + }; + constexpr auto battery = [addr, tagged] { + int x = 1; + T const t = make_variadic_union(x); + T u = t; + return apply_variadic_union(t, 0, addr) == &x + && apply_variadic_union(std::move(t), 0, addr) == &x + && apply_variadic_union(u, 0, addr) == &x + && apply_variadic_union(std::move(u), 0, addr) == &x + && invoke_type_variadic_union(t, 0, tagged) == 1 + && invoke_type_variadic_union(std::move(t), 0, tagged) == 1 + && invoke_type_variadic_union(u, 0, tagged) == 2 + && invoke_type_variadic_union(std::move(u), 0, tagged) == 2; + }; + CHECK(battery()); + static_assert(battery()); +} diff --git a/tests/fn/just_choice.cpp b/tests/fn/just_choice.cpp index 9e087502..a0a2693d 100644 --- a/tests/fn/just_choice.cpp +++ b/tests/fn/just_choice.cpp @@ -396,6 +396,25 @@ TEST_CASE("choice non-monadic functionality", "[choice]") CHECK(b.has_value()); CHECK(b.value().get_ptr()->v == 29 * from_rval); } + + SECTION("reference alternative") + { + // the value selects as copack's does + int x = 1; + choice a{x}; + CHECK(a.get_ptr() == &x); + static_assert(std::is_same_v()), int *>); + static_assert(std::is_same_v()), int const *>); + using IR = fn::choice_for; + static_assert(not std::is_constructible_v); + CHECK(IR{std::in_place_type, x}.get_ptr() == &x); + CHECK(IR{std::move(x)}.has_value()); + static_assert([] { + int x = 1; + choice const a{x}; + return a.get_ptr() == &x; + }()); + } } SECTION("constructor from copack") @@ -495,6 +514,24 @@ TEST_CASE("choice non-monadic functionality", "[choice]") static_assert(std::is_assignable_v); SUCCEED(); } + + SECTION("reference alternative") + { + // rebinds, never assigns through + int x = 1; + int y = 2; + choice a{x}; + a = y; + CHECK(a.get_ptr() == &y); + CHECK(x == 1); + static_assert([] { + int x = 1; + int y = 2; + choice a{x}; + a = y; + return a.get_ptr() == &y && x == 1; + }()); + } } SECTION("forwarding constructors (immovable)") @@ -512,7 +549,9 @@ TEST_CASE("choice non-monadic functionality", "[choice]") // Invalid alternative types make the deduction probe false without a hard instantiation error. static_assert(can_deduce_in_place); - static_assert(not can_deduce_in_place); + static_assert(can_deduce_in_place); + static_assert(not can_deduce_in_place); + static_assert(not can_deduce_in_place &, fn::pack &>); static_assert(not can_deduce_in_place); static_assert(not can_deduce_in_place); SUCCEED(); From 3c5e15cdc6fe7d159338a23a3e3a80819a5c2226 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Sun, 4 Oct 2026 21:51:55 +0100 Subject: [PATCH 02/12] Key `|` and `&` on the payload type, keeping lvalue-reference payloads Closes: #449 Assisted-by: Claude:claude-opus-5-5 --- CHANGELOG.md | 6 ++++ TYPE_ALGEBRA.md | 4 +-- include/fn/algebra.hpp | 25 ++++++++++---- include/fn/expected.hpp | 20 +++++++----- include/fn/just.hpp | 34 +++++++++---------- include/fn/optional.hpp | 20 ++++++------ tests/fn/expected.cpp | 16 +++++++++ tests/fn/just_choice.cpp | 18 +++++++++++ tests/fn/optional.cpp | 70 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 169 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24d04cd7..bff515b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Design history of libfn, newest first. The living documents — [README.md](README.md), [CONTRIBUTING.md](CONTRIBUTING.md), [docs/](docs/) — describe only the present state of the design; when a decision makes an earlier idea obsolete, this file is where the transition is recorded and explained. +## `|` and `&` keep lvalue-reference payloads — 4 October 2026 + +The disjunction sums, and the conjunction multiplies, `T&` for an `optional` operand, rather than its value type `T`. `optional | optional` is `optional>`; previously the referents were copied into `optional>`. `optional | optional` is `optional>`; previously it was `optional`, bound to the right operand's value, and failed to compile for an rvalue right operand. `optional | optional` compiles, where it failed inside the operator. Identity-cluster operands follow: `just | optional` is `choice_for`, where it was `just`. `|` places each value directly into the alternative its payload names, so an lvalue `int` is never ambiguous between alternatives `int` and `int&`. + +`optional & optional` is `optional>`; previously the referent was copied into `optional>`. `optional & optional` is `optional>`, where the referent's `const` leaked into `optional>`. `just & optional` is `optional>`, where it was `optional>`. `optional & just` is `optional>`, no longer the owning `optional>` of the 27 September entry on `&` taking a `void` side. + ## `copack` admits lvalue-reference alternatives — 4 October 2026 A `copack` alternative may be an lvalue reference `T&`, held as a pointer to its referent; previously an alternative could not be a reference, and a reference had to be wrapped in a `pack`. `T`, `T&` and `T const&` are distinct alternatives. As with `optional`, assignment and `emplace` rebind, and comparison compares referents. As from a `pack`, callables, `get` and `get_ptr` reach the referent as `T&`, or as `T const&` through a `const` copack, whatever its value category. A reference to a `pack` or a `copack` remains refused, as do rvalue references and references to arrays or functions. `choice` and `expected, E>` follow. diff --git a/TYPE_ALGEBRA.md b/TYPE_ALGEBRA.md index 1d65d758..8fab7977 100644 --- a/TYPE_ALGEBRA.md +++ b/TYPE_ALGEBRA.md @@ -423,7 +423,7 @@ Key principles of mapping: > ## 6. Product composition with operator& (conjunction) -Conjunction evaluates independent computations. `a & b` succeeds only if both operands succeed: values combine into a `pack`, and errors union into a `copack`. If both operands share the same error type, the error side remains ungraded. +Conjunction evaluates independent computations. `a & b` succeeds only if both operands succeed: values combine into a `pack`, and errors union into a `copack`. If both operands share the same error type, the error side remains ungraded. The value of an `optional` enters the product as the factor `T&`: `optional & optional` is `optional>`. ```cpp @@ -514,7 +514,7 @@ auto test_conjunction_with_identity_cluster(fn::expected ex, fn::jus > ## 7. Sum composition with operator| (disjunction) -Disjunction evaluates alternative computations, keeping the first successful result. `a | b` fails only if both operands fail: dual to conjunction, their values union into a `copack`, and their errors combine into a `pack`. If both operands share the same value type, the value side remains ungraded, so two `void` operands stay `void`. Otherwise a `void` operand enters the sum as `pack<>`. +Disjunction evaluates alternative computations, keeping the first successful result. `a | b` fails only if both operands fail: dual to conjunction, their values union into a `copack`, and their errors combine into a `pack`. If both operands share the same payload, the value side remains ungraded, so two `void` operands stay `void`. Otherwise a `void` operand enters the sum as `pack<>`. The value of an `optional` enters the sum as the alternative `T&`: `optional | optional` is `optional>`. If either error side is graded, the product distributes over it: $(E_1 + E_2) \times F \to (E_1 \times F) + (E_2 \times F)$ (the full Cartesian product when both are graded), yielding a canonical `copack` of `pack`s. diff --git a/include/fn/algebra.hpp b/include/fn/algebra.hpp index 5f559080..8c849ee9 100644 --- a/include/fn/algebra.hpp +++ b/include/fn/algebra.hpp @@ -27,9 +27,19 @@ namespace detail { // from noexcept calculations for folding and result construction. template using _value_of_t = decltype(::std::declval().value()); +// What a carrier's value side contributes to conjunction and disjunction: its value type, or the +// reference itself for a carrier of an lvalue reference +template struct _payload { + using type = typename C::value_type; +}; +template