Skip to content

Support lvalue-reference payloads in just - #437

Open
Bronek wants to merge 7 commits into
mainfrom
bronek/just_for_reference
Open

Bronek wants to merge 7 commits into
mainfrom
bronek/just_for_reference

Conversation

@Bronek

@Bronek Bronek commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

just<T&> now supports references to object types other than arrays, std::in_place_type tags and copacks, providing an always-engaged counterpart to C++26 optional<T&>. Rvalue-reference payloads remain unsupported, as in optional and pack.

Semantics

  • Copy assignment and emplace rebind the reference; they do not assign to the referent.
  • value() returns T&, and callables receive T&, regardless of the carrier's value category or constness.
  • Comparisons compare the referents. value_type is T, as for optional<T&>.
  • There is no default constructor. Deduction from a value still produces an owning carrier: for an int x, just{x} deduces just<int>. An explicit type tag permits reference deduction: just(std::in_place_type<int&>, x) deduces just<int&>.
  • apply_type tags the payload with std::in_place_type<T&>.
  • The carrier stores a pointer and is trivially copyable and a structural type.

Effects on composition

  • A transform callback returning U& produces just<U&>, which refers to the returned object. Value results still produce owning carriers.
  • An owning just used as an rvalue rejects a transform callback returning U&: the reference may refer into the payload, which expires with the carrier. For example, auto r = make_cfg_just() | fn::transform(&Cfg::get); with int const &Cfg::get() const would otherwise leave r dangling, and neither GCC nor Clang warns about it at -O0. The member rejects the result through a static_assert, and the pipeline functor through its constraints. optional<T> accepts such a result, as C++26 specifies; just deliberately diverges here. Lvalue carriers and just<T&> of any value category still produce just<U&>.
  • If every branch of a choice's and_then returns the same just<U&> type, the result retains that type. A join of different result types is rejected if any is a reference just, because references cannot be alternatives of the resulting choice. This matches the existing restriction on reference results in optional joins. A constrained partial specialization of _joining_superset_type lets both the member and pipeline functor reject these joins through constraints rather than hard errors.
  • Products and sums built by & and | copy the referent, as they do for optional<T&>. The unit just<void> is an exception: & returns the other operand, so just<void>{} & just<T&>{x} still produces just<T&>.

Copack references remain unsupported

A static_assert rejects just<copack<Ts...> cv &> for every Ts.... A transform callback returning a copack reference also remains unsupported. Such a carrier would dispatch on the referent's active alternative, introducing control flow based on state it does not own. #434 discusses the tradeoffs and open questions. The empty case, just<copack<> &>, is uninhabited and remains unsupported regardless of that decision.

Known limitations and follow-up work

  • As with optional<T&>, binding to a temporary is not rejected: just<int const&> j{42}; compiles but leaves a dangling reference. The C++26 guard requires the C++23 trait reference_constructs_from_temporary; the doc comment records this limitation.
  • | does not handle const referents correctly: just<int const&> | just<int const&> and just<int const&> | just<int> produce hard errors. optional<int const&> | just<int> has the same problem on main. & works; fixing | is shared follow-up work.
  • just<T&>::and_then shares and_then overlooks the copy when a callback returns a carrier by reference #436 with the other carriers' and_then: when a callback returns a carrier by lvalue reference, the member copies it but neither its noexcept nor its constraints account for that copy. and_then overlooks the copy when a callback returns a carrier by reference #436 tracks the fix across all carriers.
  • Section 3 of TYPE_ALGEBRA.md still says that carriers other than optional reject references. That sentence is updated on bronek/type_algebra_article to avoid a merge conflict. Section 15 is updated here.

Tests

The new just of reference test case covers construction, deduction, special members, use as a structural template argument, accepted and rejected referent types, rebinding, access, comparisons, and composition. Callable tests verify that even an rvalue carrier passes T&, with owning just cases for comparison.

and_then.cpp covers joins with matching and differing result types through the member, pipeline functor, and identity expected crossing, plus a bridge to optional<int&>. transform.cpp now checks that lvalue-reference results are accepted from lvalue carriers and from a temporary just<T const&>, rejected from rvalue owning carriers, and that rvalue-reference results remain rejected.

Local validation passed with g++ 16.2.1 and clang++ 22.1.8 in Debug, Release, and C++23-validate configurations, plus GCC's LIBFN_CXX26 configuration and Clang with libc++ and sanitizers. Documentation signature and coverage checks using the host Doxygen found no new mismatches against a HEAD baseline. Znai and MSVC validation remain for CI.

Closes #417

Add `just<T&>` with rebinding assignment and emplace. Access preserves
the lvalue reference regardless of the carrier's constness or value
category. Transform can preserve supported reference results.

Reject copack referents and joins of differing result types that
include reference payloads.

Document the lifetime limitations and cover reference construction,
rebinding, callable operations, and joins in tests.

Assisted-by: Claude:claude-opus-5-5
@augmentcode

augmentcode Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: This PR adds non-owning lvalue-reference payload support to fn::just.


Changes:

  • Introduces just<T&> for non-array object referents, excluding copacks and in-place tags.
  • Stores reference payloads as pointers, making the carrier always engaged, trivially copyable, and structural.
  • Adds rebind semantics for copy assignment and emplace, while value() always returns T&.
  • Extends transform, bind, apply, typed-apply, comparisons, deduction, and composition for reference carriers.
  • Rejects reference results from rvalue owning just transforms to avoid self-dangling views.
  • Prevents divergent choice joins involving reference just results, since references cannot be copack alternatives.
  • Adds extensive tests for construction, safety constraints, rebinding, constexpr use, composition, and callable behavior.
  • Updates API documentation, the type-algebra reference-payload model, and the design changelog.
Technical Notes: Temporary binding and const-reference sum composition remain documented follow-up limitations; rvalue-reference payloads remain unsupported.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread docs/reference/just.md
@codecov

codecov Bot commented Sep 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Bronek

Bronek commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread include/fn/just.hpp
Assisted-by: Claude:claude-opus-5-5
An rvalue owning `just` passes its payload to the callback as an
rvalue, so a reference result may refer into a payload that expires
with the carrier. `optional<T>` accepts such results as C++26
specifies; `just` diverges deliberately.

Assisted-by: Claude:claude-opus-5-5
@Bronek

Bronek commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. No suggestions at this time.

Comment augment review to trigger a new review at any time.

Bronek added 3 commits October 3, 2026 21:45
How a reference to a product or a sum takes part in conjunction,
disjunction and grading is an open question, so `just<T&>` and
`fn::optional<T&>` refuse both until the type algebra answers it.

Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
@sonarqubecloud

sonarqubecloud Bot commented Oct 3, 2026

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Admit lvalue-reference payloads in just

1 participant