Skip to content

D4280R0: Expected Over References — paper and wording#65

Open
steve-downey wants to merge 13 commits into
bemanproject:mainfrom
steve-downey:expected-over-references-paper
Open

D4280R0: Expected Over References — paper and wording#65
steve-downey wants to merge 13 commits into
bemanproject:mainfrom
steve-downey:expected-over-references-paper

Conversation

@steve-downey

Copy link
Copy Markdown
Member

The D4280R0 Expected Over References paper and its [expected] wording, split out from my fork's main (previously tangled into the lint PR #63). Touches only papers/ and docs/.

Paper

  • Rewrite of Expected Over References on the P2988 abstract / before-after / decision-log outline, plus a correction so the structure matches the code: one value-reference specialization expected<T&, E> (not six), with reference errors admitted by relaxing the E constraint rather than by new specializations.
  • Render the T&, E synopsis 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/\changed marks, 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:

  • New [expected.ref] subclause: expected<T&, E> synopsis and full member descriptions (rebind-on-assign, temporary-binding overloads deleted via reference_constructs_from_temporary, shallow-const observers, no-steal converting construction, value_or by value, uniform monadic ops passing T&).
  • New [expected.un.ref] subclause: unexpected<E&> (pointer-backed), and a carve-out letting a reference E be a valid unexpected argument.
  • Relax the E-Destructible constraint for reference errors in [expected.object.general] / [expected.void.general].
  • Store the error as unexpected<E> (not a bare union member E) in the primary, void, and ref specs, since E& can't be a union member; make the swap noexcept specs precise for reference E.

Each wording commit records its verified stripped-diff-vs-baseline line count; build is clean at 93 pages.

…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.
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.

1 participant