D4280R0: Expected Over References — paper and wording#65
Open
steve-downey wants to merge 13 commits into
Open
Conversation
…baseline Pristine [expected] wording extracted from the C++ working draft (cplusplus/draft source/utilities.tex, at "Merge 2026-06 CWG Motion 1"). It is the baseline the D4280R0 wording diffs against. Do not edit this file. Update it only by re-extracting the [expected] subclause from a newer working draft; proposed changes belong in the paper's own wording, not here.
A verbatim copy of expected-base.tex, wired into D4280R0's Wording chapter via \include. Proposed changes are made here as marked edits (\added / \removed / \changed, addedblock / removedblock), so a diff against expected-base.tex is exactly the wording delta. The markup is stripped to produce the final text for the working draft.
Restructure to the abstract / before-after / decision-log outline, in the
P2988 pattern, covering the current design: six specializations for
references in the value and/or error position (expected<T&,E>, <T,E&>,
<T&,E&>, <void,E&>) plus unexpected<E&>.
Document the standing decisions as a numbered log LEWG can poll against:
rebind on assignment, shallow const, dangling constructors deleted via
reference_constructs_from_temporary_v, the unexpected<E&> reference-only
construction rule, value_or/error_or by value, uniform monadic ops, the
shallow-conversions-must-not-steal guarantee, and = delete("...") messages.
Wire the wording chapter to \include{expected-new}.
Removes \added / \removed / \changed (and the nb variants, \remitem, and the addedblock/removedblock/addedcode/removedcode environments) from the marked-up wording copy, nesting-aware, to produce the text for a working- draft pull request. Wired as `make strip-wording` -> expected-clean.tex (generated, gitignored). D4280R0.pdf now depends on expected-new.tex.
Set SpaceAfterTemplateKeyword:false so clang-format emits `template<` to match the checked-in [expected] baseline. Reformat the illustrative expected<T&,E> synopsis in D4280R0 with it: a codeblock (not minted), names unqualified (inside namespace std), see-below as @\seebelow@, and the private members exposition-only (@\exposid@ // \expos).
The header has three expected class templates (primary, void, T&), each accepting an object or lvalue-reference error E stored through unexpected<E> (pointer-based as unexpected<E&>). Reference errors are admitted by relaxing the E constraint from "not a reference" to "not an rvalue reference", not by new specializations. Only the reference *value* needs one: expected<T&, E>. Reframe the abstract, proposal, D4, impact, wording plan, and implementation note accordingly. Update the stale Decision 1 (and related counts) in docs/human-design-review-guide.md, whose six-specialization table no longer matched the implementation.
First marked wording edit in expected-new.tex: a new [expected.ref]
subclause with the class synopsis, hand-matched to the [expected.void]
sibling house style (no namespace wrapper, 2-space members, short
templates inline, const & ref-qualifier spacing, exposition-only members).
Wrapped in \begin{addedblock}; section comments are plain for now and
become \ref cross-refs when the member-description subclauses land.
Verified: make strip-wording then diff against expected-base.tex shows a
single contiguous insertion (0 lines removed, 127 added) — the markup
strips to exactly the proposed change and nothing else.
In [expected.object.general] and [expected.void.general], condition "E shall meet the Destructible requirements" on E not being an lvalue reference, as marked \added edits in place. A reference error has no destructor to require. (This is the expected-side half; unexpected<E&> and the [expected.un.general] non-object carve-out follow next, and are what actually make a reference E a valid unexpected argument.) Verified: stripped diff vs expected-base.tex is two changed lines (501, 1903) plus the [expected.ref] block — no other churn.
Carve lvalue references out of [expected.un.general]'s "non-object type is ill-formed" prohibition (marked \added), and add a new [expected.un.ref] subclause specifying the unexpected<E&> partial specialization: it holds a pointer to an external object, binds E& in its constructors (deleting the temporary-binding overloads via reference_constructs_from_temporary), returns E& from a shallow-const error(), and swaps pointers. This is what makes a reference E a valid unexpected argument, so expected<T, E&> and friends become well-formed. Verified: stripped diff vs expected-base.tex changes exactly 3 lines (the carve-out plus the two Destructible constraints) and otherwise only inserts the two new subclauses.
Add the constructor, destructor, assignment, swap, observer, monadic, and
equality subclauses for expected<T&, E>, and turn the synopsis section
comments into \ref cross-references to them.
Spells out the reference-distinctive semantics: value construction and
emplace via addressof, deletion of temporary-binding overloads
(reference_constructs_from_temporary), rebind assignment that never
assigns through, the reference-only unexpected<G> rule (D5), shallow-const
operator*/value/error, no-steal converting construction, value_or by
value, and monadic operations that always pass T&. Groups identical to the
primary cross-reference [expected.object.*] rather than restating them.
Replaced a malformed effects table with prose.
Verified: build clean at 93 pages; all \ref{expected.ref.*} resolve; the
only undefined ref is the pre-existing baseline optional.ctor; stripped
diff vs expected-base.tex still removes exactly 3 lines and otherwise only
inserts the new subclauses.
An "as-if" exposition of the error as a union member E unex cannot represent the documented behavior: when E is an lvalue reference, E& is not a permissible union member. Specify the error member as unexpected<E> instead, which holds a pointer via unexpected<E&> for a reference E, and route error access through unex.error(). Update the dependent member descriptions (copy ctor, unexpect_t effects, error(), rebinding assignment) to match, and add a General paragraph stating why. Verified: build clean at 93 pages; stripped diff vs expected-base.tex still removes exactly 3 lines (all in the relaxation edits, none here).
The step-1 relaxation lets expected<T, E> and expected<void, E> take an lvalue-reference E, but their ratified exposition stores the error as a bare union member E unex, which is ill-formed for E& (same as-if defect just fixed in [expected.ref]). Change both union members to unexpected<E> and route error access through unex.error(). The edit is surgical: construct_at(addressof(unex), ...), reinit-expected, swap(unex, rhs.unex), and the "direct-non-list-initializes unex with the error" construction prose all operate correctly on unexpected<E> unchanged. Only the four error() returns (-> unex.error()), the same-state assignment lines (unex = rhs.unex; and unex = unexpected<E>(...e.error())), and the primary swap's temporary (E tmp -> unexpected<E> tmp) change. unexpected<E> assignment gives rebind for reference E and assign-through for object E, which is exactly the intended semantics. Left as a follow-up (equivalent, not incorrect): the swap noexcept specification still tests is_nothrow_swappable_v<E>, which is over-conservative for a reference E (the pointer swap is always noexcept). Verified: build clean at 93 pages; stripped diff vs expected-base.tex changes exactly the 16 intended lines (13 storage + 3 relaxation), no churn; bad_expected_access and unexpected keep E unex (a reference class member, not a union member, is legal).
…torage Close the two loose ends from the storage fix: 1. The primary and void swap exception specifications tested is_nothrow_swappable_v<E>, which for a reference E asks whether the *referents* are nothrow-swappable -- over-conservative, since swap exchanges the errors as unexpected<E> (a pointer swap for E&, always noexcept). Respell the E terms as unexpected<E> in both swap noexcept specs and the primary swap's branch guard. Equivalent for object E; correctly noexcept for reference E. 2. Add a marked General paragraph to expected<T, E> and expected<void, E> stating the error is unex.error(), held as unexpected<E> so E may be an lvalue reference (E& cannot be a union member; unexpected<E&> holds a pointer). Pure additions. Verified: build clean at 93 pages; stripped diff vs expected-base.tex changes exactly 19 lines (3 relaxation + 13 storage + 3 swap), the two notes being additions; no churn.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The D4280R0 Expected Over References paper and its
[expected]wording, split out from my fork'smain(previously tangled into the lint PR #63). Touches onlypapers/anddocs/.Paper
expected<T&, E>(not six), with reference errors admitted by relaxing theEconstraint rather than by new specializations.T&, Esynopsis in Standard house style; track.clang-format(SpaceAfterTemplateKeyword: false).Wording (
[expected])Change-marked against a checked-in draft-standard baseline:
expected-base.tex— pristine[expected]extracted from the working draft (the diff baseline; do not edit).expected-new.tex— the editable copy; proposed changes as\added/\removed/\changedmarks, so a diff vs the baseline is exactly the wording delta.strip-wording.py(make strip-wording) — strips the change markup to produce clean text for a working-draft PR.Wording content:
[expected.ref]subclause:expected<T&, E>synopsis and full member descriptions (rebind-on-assign, temporary-binding overloads deleted viareference_constructs_from_temporary, shallow-const observers, no-steal converting construction,value_orby value, uniform monadic ops passingT&).[expected.un.ref]subclause:unexpected<E&>(pointer-backed), and a carve-out letting a referenceEbe a validunexpectedargument.E-Destructible constraint for reference errors in[expected.object.general]/[expected.void.general].unexpected<E>(not a bare union memberE) in the primary, void, and ref specs, sinceE&can't be a union member; make theswapnoexceptspecs precise for referenceE.Each wording commit records its verified stripped-diff-vs-baseline line count; build is clean at 93 pages.