diff --git a/CHANGELOG.md b/CHANGELOG.md index af871fe1..c3185197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ 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. +## A single `conjoin` argument is normalized, a single `disjoin` argument returned by value — 4 October 2026 + +`conjoin` is the fold of `&` from its unit, so a single argument meets the unit alone; previously the argument itself was returned, as a reference. Now `conjoin(x)` is `pack`, `conjoin(c)` for a `copack_for` is `copack_for, pack>`, and `conjoin(o)` for an `optional` is `optional>`; a `void` value stays `void`. The unit of a carrier is of its own kind: `just`, `expected`, or an engaged `optional>`. The result's shape no longer depends on the number of arguments. `disjoin` returns a single carrier by value. Both previously returned `decltype(arg)`, so `auto &&r = fn::conjoin(42);` dangled. + +## Deduction and `conjoin` hold values; `as_copack` and `as_choice` keep references — 4 October 2026 + +Class template argument deduction for `pack`, `copack`, `just` and `choice` removes only references and cv-qualifiers from each argument. For an lvalue `int x`, `pack{x}` is `pack`; previously it was `pack`, which `as_pack(x)` still yields. An array or a function is never turned into a pointer: `just{arr}` and `just{f}` fail to compile, where they deduced `just` and `just`, and `pack{f}` fails where it deduced `pack`. `pack{"abc"}` is `pack`. A function type is refused as a `pack` element and as a `copack` alternative, where it failed inside the library. `conjoin` holds a leading lvalue by value, as `&` holds every further operand: `conjoin(x, y)` is `pack`, where it was `pack`; `as_pack(x) & y` keeps the reference. + +`as_copack` and `as_choice` preserve the value category, as `as_pack` does: for an lvalue `x`, `as_copack(x)` is `copack` and `as_choice(x)` is `choice`; previously both copied. An lvalue the lift cannot refer to is refused rather than copied: `as_copack` of an lvalue `pack` or array, and `as_choice` of an lvalue copack, array or function. `as_choice("hi")`, now refused, was `choice`. Move the source, or use deduction, to copy. + +## `transform` over a copack keeps lvalue-reference results — 4 October 2026 + +`transform` and `transform_error` over a copack keep an lvalue-reference result as a reference alternative: an lvalue `copack_for` mapped through `[](auto &v) -> auto & { return v; }` yields `copack_for`; previously the referents were copied into `copack_for`. Rvalue-reference results, and references to a `pack` or a `copack`, still enter as values. A reference result that could refer into an argument expiring with the call, previously copied, now fails to compile. For example, `std::move(c).transform([](auto const &v) -> auto const & { return v; })` is refused; return by value instead. `expected`, `optional` and `choice` over a copack follow. + +## `|` 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. + +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..0978620c 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 @@ -297,7 +297,7 @@ Modeling complex algebraic structures—such as multi-field products or multi-al A `pack` stores multiple fields and supports the standard C++ tuple protocol (`get`, `tuple_size`, `tuple_element`, structured bindings) and an `append` mechanism. Unlike standard tuples, `libfn` packs are strictly flat: a `pack` cannot be an element of another `pack`. Appending a `pack` splices its fields into the outer pack rather than nesting it. -To explicitly lift values into a `pack` (which is useful when conjoining scalars with other packs or copacks), use `fn::as_pack(...)`. When called without template parameters, `as_pack` is deduction-only and preserves the value category of its arguments: `as_pack(42)` yields `pack`, whereas calling `as_pack(x)` on an lvalue `x` yields `pack` (a reference rather than a copy). +To explicitly lift values into a `pack` (which is useful when conjoining scalars with other packs or copacks), use `fn::as_pack(...)`. When called without template parameters, `as_pack` preserves the value category of its arguments: `as_pack(42)` yields `pack`, whereas calling `as_pack(x)` on an lvalue `x` yields `pack` (a reference rather than a copy). Class template argument deduction (CTAD) is the complement: it removes only references and cv-qualifiers, so `pack{x}` yields `pack`. Spelling the template parameters instead (e.g., `as_pack(x, d)`) takes deduction out of the picture: each argument is passed as the type you named, enabling implicit conversions to happen at the call boundary. A reference element becomes something you ask for explicitly — `as_pack(x)` yields `pack`. Note that partial template spelling is not supported; all element types must be spelled out explicitly if template parameters are specified. @@ -348,9 +348,15 @@ 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)`. 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`. -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. +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. + +> [!WARNING] +> +> The lifts `as_pack`, `as_copack` and `as_choice` (Section 11) keep an lvalue as a reference: `as_pack(x)` refers to `x`, which must outlive it. CTAD never does: `pack{x}`, `copack{x}`, `just{x}` and `choice{x}` hold copies. Rather than copy an lvalue it cannot refer to, a lift refuses it: an array, a function or a `pack` for `as_copack`, and an array, a function or a `copack` for `as_choice`. + +A `pack` can be a `copack` alternative (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. ```cpp @@ -408,6 +414,7 @@ Key principles of mapping: - Success and error states are preserved. - A bare `copack` has a member `transform` to map across alternatives, but takes no pipeline functor as it is data, not a carrier. - Over a `copack` side, `transform` and `transform_error` yield a normalized `copack`, with `void` results represented as `pack<>`. Over a plain side, only `expected` and `just` accept a `void` value result. +- Over a `copack` side, an lvalue-reference result enters as a reference alternative, as `optional`'s `transform` yields `optional`; any other reference result, including one to a `pack` or a `copack`, enters as its value. A reference result that could refer into an argument expiring with the call, such as an alternative of an rvalue `copack`, is ill-formed. - Applying `transform_error` to a carrier with no error side (such as `just` or `choice`) is ill-formed. - When a side is uninhabited (`copack<>`), transformation is well-formed but vacuous: neither the member nor the pipeline form is reachable, and the callback is not instantiated. This applies to `optional>` and `expected, E>` on the value side, and `expected>` on the error side. @@ -421,7 +428,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 @@ -462,14 +469,14 @@ auto test_cartesian_distribution(fn::copack_for ab, fn::copack_for c } ``` -A bare `scalar & scalar` is outside the algebra; it fails to compile for class types and resolves to the bitwise `AND` for built-in types like `int`. Conjunction dispatches on the left operand, so lifting one side—such as `fn::as_pack(a) & b`—enables the algebra. +A bare `scalar & scalar` is outside the algebra; it fails to compile for class types and resolves to the bitwise `AND` for built-in types like `int`. Conjunction dispatches on the left operand, so lifting the left operand—as in `fn::as_pack(a) & b`—enables the algebra. A scalar operand is held by value, as CTAD holds it: for lvalues `a` and `b`, `fn::as_pack(a) & b` is `pack`. The n-ary fold `fn::conjoin(...)` operates in two modes: - If all arguments are computation carriers, it folds them as a monadic conjunction, equivalent to cascading `operator&`. -- If no arguments are carriers, it conjoins them as a data-level product. +- If no arguments are carriers, it conjoins them as a data-level product, every scalar held by value: `fn::conjoin(a, b)` is `pack`. -Mixing carriers and data in a single call is ill-formed. +Mixing carriers and data in a single call is ill-formed. Being a fold from the unit, `fn::conjoin` with a single argument normalizes it into a sum of products: `fn::conjoin(a)` is `pack`, a `copack_for` becomes `copack_for, pack>`, and an `optional` becomes `optional>`; a `void` value stays `void`. ### Conjunction with the identity cluster @@ -512,7 +519,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. @@ -812,7 +819,7 @@ Monadic operations on the identity cluster: > ## 11. choice: identity over a coproduct -`choice` represents a computation that always succeeds by selecting one of several alternatives. It is `just>` (Section 3): the identity carrier over the coproduct payload, dispatching branch-wise where `just` maps the one value. `fn::as_choice(x)` lifts a value into the single-alternative choice over its decayed type, and wraps a copack in the choice over its alternatives. +`choice` represents a computation that always succeeds by selecting one of several alternatives. It is `just>` (Section 3): the identity carrier over the coproduct payload, dispatching branch-wise where `just` maps the one value. `fn::as_choice(x)` lifts a value into a single-alternative choice, as `as_copack` does, and wraps a copack rvalue in the choice over its alternatives. ### Mapping into a choice @@ -1040,14 +1047,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 +1066,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 +1132,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, lifts versus CTAD). **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/docs/reference/choice.md b/docs/reference/choice.md index 2efaa180..dadedb2d 100644 --- a/docs/reference/choice.md +++ b/docs/reference/choice.md @@ -50,9 +50,11 @@ From a value of one alternative, in place from arguments, widening from a `copac subset of the alternatives, or from a narrower `choice`. ```cpp {title: "fn::just< copack< Ts... > >::just"} +template +constexpr just(U &&v); // (1) +constexpr explicit just(U &&v); // (2) + template -constexpr just(T &&v); // (1) -constexpr explicit just(T &&v); // (2) constexpr explicit just(std::in_place_type_t d, auto &&...args); // (3) template @@ -70,9 +72,9 @@ constexpr just(just const &) = default; // (9) constexpr just(just &&) = default; // (10) ``` -:include-doxygen-doc: fn::just< copack< Ts... > >::just { args: "T &&" } +:include-doxygen-doc: fn::just< copack< Ts... > >::just { args: "U &&" } -:include-doxygen-doc-params: fn::just< copack< Ts... > >::just { args: "T &&", title: "parameters" } +:include-doxygen-doc-params: fn::just< copack< Ts... > >::just { args: "U &&", title: "parameters" } :include-doxygen-doc: fn::just< copack< Ts... > >::just { args: "::std::in_place_type_t< T >, auto &&..." } @@ -174,8 +176,8 @@ constexpr auto has_value(std::in_place_type_t d=std::in_place_type) const ```cpp {title: "fn::just< copack< Ts... > >::get_ptr"} template -constexpr auto get_ptr(std::in_place_type_t d=std::in_place_type) -> T *; // (1) -constexpr auto get_ptr(std::in_place_type_t d=std::in_place_type) const -> T const *; // (2) +constexpr auto get_ptr(std::in_place_type_t d=std::in_place_type) -> std::add_pointer_t; // (1) +constexpr auto get_ptr(std::in_place_type_t d=std::in_place_type) const -> std::add_pointer_t const>; // (2) ``` :include-doxygen-doc: fn::just< copack< Ts... > >::get_ptr { args: "::std::in_place_type_t< T >" } @@ -304,9 +306,11 @@ using choice_for = just>; // (1) ## as_choice {style: "api"} -`as_choice(x)` constructs a choice with one alternative of the decayed source type. A copack -instead becomes the payload of a choice over its alternatives. An existing choice becomes a -nested alternative; `choice{x}` preserves the existing choice type through copy deduction. +`as_choice(x)` constructs a choice with one alternative: a reference to an lvalue `x`, or the +value of an rvalue, whereas `choice{x}` always holds a value. A copack rvalue instead becomes the +payload of a choice over its alternatives. An array, a function or an lvalue copack is refused. An +existing choice becomes a nested alternative; `choice{x}` preserves the existing choice type +through copy deduction. ```cpp {title: "fn::as_choice"} constexpr auto as_choice(auto &&src) -> decltype(auto); // (1) diff --git a/docs/reference/conjoin.md b/docs/reference/conjoin.md index a7013f78..7ab474b1 100644 --- a/docs/reference/conjoin.md +++ b/docs/reference/conjoin.md @@ -82,8 +82,8 @@ constexpr auto operator&(Lh &&lh, Rh &&rh); // (13) ## Call signatures {style: "api"} ```cpp {title: "fn::conjoin_t::operator()"} -template -constexpr auto operator()(Arg &&arg) const -> decltype(arg); // (1) +template +constexpr auto operator()(Arg &&arg) const; // (1) template constexpr auto operator()(Arg &&arg, Args &&...args) const; // (2) diff --git a/docs/reference/copack.md b/docs/reference/copack.md index 98d3aa40..684cbbe0 100644 --- a/docs/reference/copack.md +++ b/docs/reference/copack.md @@ -84,9 +84,11 @@ copack(copack &&); // (3) :include-doxygen-doc: fn::copack<>::copack { args: "copack &&" } ```cpp {title: "fn::copack< Ts... >::copack"} +template +constexpr copack(U &&v); // (1) +constexpr explicit copack(U &&v); // (2) + template -constexpr copack(T &&v); // (1) -constexpr explicit copack(T &&v); // (2) constexpr explicit copack(std::in_place_type_t, auto &&...args); // (3) template @@ -100,9 +102,9 @@ constexpr copack(copack &&other) = default; // (9) constexpr copack(copack &&other); // (10) ``` -:include-doxygen-doc: fn::copack< Ts... >::copack { args: "T &&" } +:include-doxygen-doc: fn::copack< Ts... >::copack { args: "U &&" } -:include-doxygen-doc-params: fn::copack< Ts... >::copack { args: "T &&", title: "parameters" } +:include-doxygen-doc-params: fn::copack< Ts... >::copack { args: "U &&", title: "parameters" } :include-doxygen-doc: fn::copack< Ts... >::copack { args: "::std::in_place_type_t< T >, auto &&..." } @@ -284,8 +286,8 @@ constexpr auto apply_type_r(Fn &&fn, Args &&...args) const && -> Ret; // (4) ```cpp {title: "fn::copack< Ts... >::get_ptr"} template -constexpr auto get_ptr(std::in_place_type_t=std::in_place_type) -> T *; // (1) -constexpr auto get_ptr(std::in_place_type_t=std::in_place_type) const -> T const *; // (2) +constexpr auto get_ptr(std::in_place_type_t=std::in_place_type) -> std::add_pointer_t; // (1) +constexpr auto get_ptr(std::in_place_type_t=std::in_place_type) const -> std::add_pointer_t const>; // (2) ``` :include-doxygen-doc: fn::copack< Ts... >::get_ptr { args: "::std::in_place_type_t< T >" } diff --git a/docs/reference/disjoin.md b/docs/reference/disjoin.md index f57b868d..c48a1fe4 100644 --- a/docs/reference/disjoin.md +++ b/docs/reference/disjoin.md @@ -67,7 +67,7 @@ constexpr auto operator|(Lh &&lh, Rh &&rh); // (9) ```cpp {title: "fn::disjoin_t::operator()"} template -constexpr auto operator()(Arg &&arg) const -> decltype(arg); // (1) +constexpr auto operator()(Arg &&arg) const -> std::remove_cvref_t; // (1) template constexpr auto operator()(Arg &&arg, Args &&...args) const; // (2) 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/algebra.hpp b/include/fn/algebra.hpp index 5f559080..f2b6b5b6 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