Repository navigation
Allow lvalue-reference alternatives in copack; keep references through |, &, transform and lifts (breaking) - #451
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
🤖 Augment PR SummarySummary: This PR lets Changes:
Technical Notes: Whether a reference would bind to a temporary is decided by 🤖 Was this summary useful? React with 👍 or 👎 |
4f12d91 to
0c71a59
Compare
Assisted-by: Claude:claude-opus-5-5
Closes: #449 Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
Closes: #448 Assisted-by: Claude:claude-opus-5-5
…ment by value A single argument was forwarded as a reference, which dangled when bound to a temporary and gave a result whose shape depended on the number of arguments. Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
MSVC instantiates the deduced-return body of a non-viable rvalue transform overload while forming its declaration, which fired the reference-result guard for a const lvalue call. Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
…the headers Declare get_ptr with a trailing return, which doxygen reads as constexpr, and list the single-carrier conjoin overload first, so that each listing has the shape the signature check derives from the headers. Assisted-by: Claude:claude-opus-5-5
…erence Assisted-by: Claude:claude-opus-5-5
0c71a59 to
b4d75d5
Compare
Move the refusing callback to tests/util and define add_reject_tests ahead of both suites, so fn tests can declare compile failures as pfn tests do. Assisted-by: Claude:claude-opus-5-5
The gates probed the member transform of a copack payload or a choice. When the rvalue overload dropped out, the const & overload answered instead: a temporary operand passed for a callback refusing an rvalue, which then received an lvalue into the temporary, and a reference result into a temporary passed only to fail on instantiation. The gates now ask what copack::transform requires, in the operand's own cv/ref category. examples/simple relied on the fallback for its auto & arms. Assisted-by: Claude:claude-opus-5-5
fn::optional::transform and fn::expected's transform and transform_error lose their trailing decltype, which removed an overload whose callback did not accept the payload in that category and let const & take a temporary operand. expected keeps the draft's constructibility constraints as requires-clauses, so a copy-only error still falls through to const && as in pfn. Checked on MSVC 19.44, 19.51 and latest through the amalgamated header on Compiler Explorer. Assisted-by: Claude:claude-opus-5-5
copack::transform and choice's transform and and_then take the same shape, so a callback refusing rvalues can no longer receive an lvalue into a temporary copack or choice and return a reference into it. This comes after the optional and expected change: with their trailing decltype gone, no declaration names these deduced bodies, which MSVC would instantiate for a non-viable overload. Assisted-by: Claude:claude-opus-5-5
…pected The disjunction tests threw only from optional's left value and expected's error fold, so the exception edge of copying optional's right value, and either of expected's values, into the sum was never taken; codecov/patch counted the lines missed. Assisted-by: Claude:claude-opus-5-5
A reference alternative bound to a temporary dangled at once:
copack<int const&>{42} compiled. As optional<T&> does (#454, the C++26
rule), the converting constructors of copack and choice are deleted where
the alternative the value selects would bind to a temporary, so overload
resolution never routes such a value to another overload. The holder of a
reference alternative refuses the binding itself, which constrains away the
in_place_type constructors, assignment from a value, emplace and the lifts.
Assisted-by: Claude:claude-opus-5-5
|
augment review |
|
This pull request is abnormally large and would use a significant amount of tokens to review. If you still wish to review it, comment "augment review" and we will review it. |
|
augment review |
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
A lift picks a reference or a copy from the argument's value category, which the call site does not show; the named type shows it, and its constructors refuse a temporary for a reference alternative. `as_pack` stays until `pack`'s braces refuse a temporary too (#463). Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
The referents sit at namespace scope: MSVC 19.44, 19.51 and latest hit an internal compiler error reading through a class-type template argument which points to a block-scope static, with or without libfn. Assisted-by: Claude:claude-opus-5-5
|
augment review |
Keying `|` on the payload type made `copack<A, B>&` or `pack<A>&` an alternative, which no copack admits: `optional<copack_for<A, B>&> | optional<C>` became a hard error where main flattened it into `optional<copack_for<A, B, C>>`. The sum now takes a payload as it takes a transform result, so such a reference enters as its value, while its reference elements and alternatives stay references. Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
|
augment review |
Assisted-by: Claude:claude-opus-5-5
|
augment review |
|



Add lvalue-reference alternatives to
copackandchoice, preserve them through|,&,transformand the lifts, and reject bindings that could dangle.Allow lvalue-reference alternatives in
copack. An alternative may beT&, held as a pointer to its referent;T,T&andT const&are distinct alternatives. As withoptional<T&>, assignment rebinds and comparison compares referents. Likepack<T&>, a copack returnsT&, orT const&when the copack isconst, regardless of its value category. A value selects among the alternatives of its own decayed type by overload resolution, socopack_for<int, int&>{x}is ambiguous;std::in_place_type<int&>names the alternative.choice<T&>andexpected<copack<T&>, E>now work, too.Reject temporaries for reference alternatives. Like
optional<T&>(#454), reference alternatives reject bindings to temporaries:copack<int const&>{42}andchoice<int const&>{std::move(x)}do not compile. The converting constructors are deleted when the selected alternative would bind to a temporary, so the call fails rather than falling through to another overload. Thein_place_typeconstructors, assignment from a value,emplaceand theas_copack/as_choicelifts are constrained. When the compiler lacks the builtin (GCC 12, MSVC before Visual Studio 2026 18.6), the check relies on the emulation inpfn::reference_converts_from_temporary.Use the payload type for
|and&. Anoptional<T&>operand contributesT&rather thanT:optional<int&> | optional<long&>isoptional<copack_for<int&, long&>>, andoptional<int&> & optional<long>isoptional<pack<int&, long>>. References topackandcopackare not valid alternatives, so they still contribute values, as they do intransformresults. This also fixes&and|forconstreferents and includes the|fix from #437, which rebases onto this PR.Breaking: result types change when an
optional<T&>operand is used with|or&. For example,optional<int&> & just<void>now yieldsoptional<pack<int&>>, as required by the type algebra.Keep lvalue-reference results of
transformover acopack. A callback returningT&yields a reference alternative, asoptional'stransformyieldsoptional<T&>; other reference results are still stored as values.Breaking: reference results that could refer into an argument whose lifetime ends with the call, such as an alternative of an rvalue copack, are now rejected instead of copied. Return by value instead.
Never fall back to another cv/ref overload.
copack::transform,choice'stransformandand_then, andtransformandtransform_erroronfn::optionalandfn::expectedfollow the approach introduced forpfnin #459: callback requirements are checked by assertions rather than constraints.Previously, a callback that rejected rvalues removed the rvalue overload from consideration. The
const &overload could then pass an lvalue referring into the temporary operand, allowing a reference result to dangle. As in the standard,expectedremains constrained on constructing the unchanged side.fn::applicable_transformandfn::applicable_transform_errornow check the payload directly, likefn::applicable_and_then, instead of probing the members. This keeps pipe operations SFINAE-friendly.Breaking:
const &overload;requires-expression causes a hard error; usefn::applicable_transformand its siblings instead;|, a callback arm takingauto &no longer accepts the alternatives of a temporary operand; takeauto const &, asexamples/simplenow does.Deduce values and lift references. CTAD for
pack,copack,justandchoiceremoves only references and cv-qualifiers: for an lvaluex,pack{x}ispack<int>, andpack{"abc"}ispack<char[4]>. The liftsas_pack,as_copackandas_choicepreserve lvalues as references and reject those they cannot reference instead of copying them. Pass an rvalue, e.g.std::move(x), to lift a value instead; an array or a function cannot be lifted by value.conjoinstores a leading scalar by value, just as&stores subsequent scalars.Breaking:
pack{x}now copies instead of referring tox;as_pack(x)still refers to it.as_copack(x)andas_choice(x)now refer toxinstead of copying it; move the source to store a value, or use CTAD to copy it.arrand a functionf,just{arr},just{f}andpack{f}fail to compile, as doas_copackof an lvaluepackandas_choiceof an lvalue copack, array or function, such asas_choice("hi").int x, y;,conjoin(x, y)ispack<int, int>, where it waspack<int&, int>.Deprecate
as_copackandas_choice. Name the type instead:copack<int&>{x}andchoice<int&>{x}refer tox,copack{x}andchoice{x}copy it, andjust{c}deduces the choice over the alternatives of a copackc. A lift chooses between a reference and a value based on the argument's value category, which may not be apparent at the call site. Naming the type makes that choice explicit.as_packremains until brace initialization ofpackrejects temporaries for reference elements (#463).Normalize a single
conjoinargument; return a singledisjoinargument by value.conjoinfolds&starting from its unit. With one argument, it applies&to the unit and that argument:conjoin(x)ispack<int>, acopack_for<A, B>becomescopack_for<pack<A>, pack<B>>, and anoptional<int>becomesoptional<pack<int>>, while avoidvalue staysvoid.disjoin(m)returnsmby value.This fixes a dangling reference. With one argument, both returned the argument itself as a reference, so binding the result of a call on a temporary left a reference to a destroyed object. Both now return a value, which the binding keeps alive:
Breaking: the result of a single-argument
conjoinchanges type, e.g.conjoin(o).value()is apack<int>where it was anint; callbacks still receive the unpacked elements.Other commits fix MSVC spellings of copack result types, equality's
noexceptspecification for reference alternatives, and relocation of reference alternatives byemplace. They also update the reference listings to match the headers. Compile-failure tests forfnlive intests/fn/reject/and share the machinery introduced forpfnin #461.TYPE_ALGEBRA.md and CHANGELOG.md document each change.
The final tree passes locally with gcc 16 and clang 21 under sanitizers, and in CI. Each commit in the fallback fix was also built and tested with gcc 16. Compiler Explorer checks using the amalgamated header covered the fallback fix, rejection of temporaries, and
copackandchoiceas template arguments on MSVC 19.44, 19.51 and latest. Deprecation warnings were checked on MSVC 19.44 and latest. Other MSVC checks and AppleClang checks are covered by CI.Related issue found during this work: #450,
&on aconst pack<int&>fails to compile.Closes #448
Closes #449
Part of #447: the reference alternatives of
copackandchoice.Assisted-by: Claude:claude-opus-5-5