Skip to content

Commit 2d7b010

Browse files
authored
D4280R0: Expected Over References — paper and wording (#65)
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.
2 parents 67235be + 2ace9a4 commit 2d7b010

10 files changed

Lines changed: 7062 additions & 206 deletions

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ repos:
1818
hooks:
1919
- id: clang-format
2020
types_or: [c++, c]
21+
# Hand-aligned illustrative listing rendered verbatim in the paper.
22+
exclude: '^papers/wg21-latex/implementation\.hpp$'
2123

2224
# CMake linting and formatting
2325
- repo: https://github.com/BlankSpruce/gersemi-pre-commit

docs/human-design-review-guide.md

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,55 @@
44

55
## What This Is
66

7-
A complete implementation of `std::expected` (C++26) extended with six template specializations that allow `T` and/or `E` to be reference types, proposed for C++29. The reference semantics follow P2988 (`optional<T&>`): rebind on assignment, shallow const, dangling prevention via deleted constructors.
7+
A complete implementation of `std::expected` (C++26) extended to allow `T` and/or `E` to be reference types, proposed for C++29. This is a new `expected<T&, E>` specialization for a reference value, a relaxation of the primary and `void` templates to admit a reference error `E`, and a new `unexpected<E&>` — three `expected` class templates in all, each accepting an object or reference error. The reference semantics follow P2988 (`optional<T&>`): rebind on assignment, shallow const, dangling prevention via deleted constructors.
88

99
**File count:** 3 headers, ~4400 lines of implementation, 15 positive test files (469 tests), 54 negative compile tests.
1010

1111
**This guide is not a checklist.** It's a map of the decisions that shaped this code. Some are obviously correct. Some are defensible but debatable. Some might be wrong. Your job is to decide which is which.
1212

1313
---
1414

15-
## Decision 1: Six Separate Specializations, Zero Abstraction
15+
## Decision 1: Three Class Templates, Reference Errors Folded In, Zero Abstraction
1616

1717
### What was done
1818

19-
The implementation provides six complete, independent partial specializations:
19+
The implementation provides three complete, independent `expected` class
20+
templates, plus two `unexpected` templates. Reference *error* types are not
21+
separate specializations: each `expected` template permits `E` to be an object
22+
type *or* an lvalue reference, and stores the error as an `unexpected<E>` — which
23+
is the pointer-holding `unexpected<E&>` when `E` is a reference. So a single
24+
`expected<T&, E>` covers both `expected<T&, E>` and `expected<T&, E&>`, and the
25+
primary covers `expected<T, E>` and `expected<T, E&>`.
2026

21-
| Specialization | Lines | Storage |
27+
| Template | Value storage | Error storage |
2228
|----------------|-------|---------|
23-
| `expected<T, E>` | ~1100 | `union { T val_; E unex_; }` + bool |
24-
| `expected<void, E>` | ~800 | `union { E unex_; }` + bool |
25-
| `expected<T&, E>` | ~700 | `T*` + `union { E unex_; }` + bool |
26-
| `expected<T, E&>` | ~700 | `union { T val_; }` + `E*` + bool |
27-
| `expected<T&, E&>` | ~550 | `T*` + `E*` + bool |
28-
| `expected<void, E&>` | ~450 | `E*` + bool |
29-
30-
Each specialization is a self-contained class with its own constructors, assignment operators, observers, swap, equality, and monadic operations. There is no shared base class, no CRTP, no mixin, no macro that generates them.
29+
| `expected<T, E>` (primary) | `union { T val_; unexpected<E> unex_; }` + bool | `unexpected<E>` (pointer when `E` is `E&`) |
30+
| `expected<void, E>` | `union { unexpected<E> unex_; }` + bool | `unexpected<E>` |
31+
| `expected<T&, E>` | `union { T* val_; unexpected<E> unex_; }` + bool | `unexpected<E>` |
32+
| `unexpected<E>` || `E` by value |
33+
| `unexpected<E&>` || `E*` |
34+
35+
The static assertion that used to read "`E` must not be a reference" now reads
36+
"`E` must not be an *rvalue* reference," which is the whole of what admits
37+
reference errors. Each template is a self-contained class with its own
38+
constructors, assignment operators, observers, swap, equality, and monadic
39+
operations. There is no shared base class, no CRTP, no mixin, no macro that
40+
generates them.
3141

3242
### Why this matters
3343

34-
The total is ~4300 lines of template code in a single header. A factored implementation using a common base or policy template could plausibly cut this by 40-60%. The monadic operations alone account for 4 operations x 4 ref-qualified overloads x 6 specializations = 96 method definitions that share structural similarity.
44+
The total is ~4300 lines of template code in a single header. A factored implementation using a common base or policy template could plausibly cut this by 40-60%. The monadic operations alone account for 4 operations x 4 ref-qualified overloads across the three templates — dozens of method definitions that share structural similarity.
3545

3646
### The argument for the current approach
3747

3848
- Standard library implementations (libstdc++, libc++, MSVC STL) use flat specializations for `optional` and `expected`. Inheritance-based factoring produces incomprehensible error messages.
3949
- Each specialization can be read and audited independently against whatever future standard wording emerges.
40-
- When constraints differ subtly between specializations (and they do — `T&` doesn't need `is_nothrow_move_constructible` checks, `E&` doesn't support `unexpected<G>` construction), a shared base must either disable features via SFINAE or push complexity into the base, which hides it rather than removing it.
50+
- When constraints differ subtly between templates (and they do — `T&` doesn't need `is_nothrow_move_constructible` checks, and a reference `E` accepts `unexpected<G>` construction only when `G` is itself a reference), a shared base must either disable features via SFINAE or push complexity into the base, which hides it rather than removing it.
4151

4252
### The argument against
4353

44-
- **Maintenance burden.** A bug in `and_then` must be fixed in 6 places. A new monadic operation (hypothetical `or_transform`) must be added 24 times (4 overloads x 6 specializations).
45-
- **Consistency risk.** Are all 6 specializations actually consistent? Small divergences creep in when hand-copying constraint clauses. The only way to verify is exhaustive side-by-side comparison.
54+
- **Maintenance burden.** A bug in `and_then` must be fixed in three templates. A new monadic operation (hypothetical `or_transform`) must be added 12 times (4 overloads x 3 templates).
55+
- **Consistency risk.** Are all three templates actually consistent, across both object and reference `E`? Small divergences creep in when hand-copying constraint clauses. The only way to verify is exhaustive side-by-side comparison.
4656
- **Review fatigue.** A reviewer looking at 4300 lines of structurally similar code will inevitably skim. The 95th `requires` clause gets less scrutiny than the 5th.
4757

4858
### What to decide
@@ -172,7 +182,7 @@ Every deleted operation carries a C++26 `= delete("message")` string explaining
172182

173183
### What was done
174184

175-
All four monadic operations (`and_then`, `or_else`, `transform`, `transform_error`) are provided for all six specializations, with four ref-qualified overloads each (`&`, `const&`, `&&`, `const&&`).
185+
All four monadic operations (`and_then`, `or_else`, `transform`, `transform_error`) are provided for all three `expected` templates, with four ref-qualified overloads each (`&`, `const&`, `&&`, `const&&`), for both object and reference `E`.
176186

177187
For reference specializations, the callable always receives `T&` (dereferenced pointer), regardless of the expected object's own value category. This means:
178188

@@ -208,21 +218,21 @@ std::move(e).and_then(f); // f still gets int&, not int&&
208218
209219
### What to decide
210220
211-
Is the test suite sufficient for standardization review? The positive coverage is good — every operation has basic tests. But the systematic constraint/SFINAE testing that exists for the primary template is absent for three of the four reference specializations. This gap should be closed before submitting to WG21.
221+
Is the test suite sufficient for standardization review? The positive coverage is good — every operation has basic tests. But the systematic constraint/SFINAE testing that exists for the primary template and `expected<T&, E>` is absent for the reference-error (`E&`) configurations of the primary and `void` templates. This gap should be closed before submitting to WG21.
212222
213223
---
214224
215225
## Decision 10: Single Header vs Multi-Header
216226
217227
### What exists
218228
219-
Everything is in `expected.hpp`. The `unexpected.hpp` and `bad_expected_access.hpp` are separate (matching the standard's `[expected.syn]` structure), but all six specializations live in one file.
229+
Everything is in `expected.hpp`. The `unexpected.hpp` and `bad_expected_access.hpp` are separate (matching the standard's `[expected.syn]` structure), but all three `expected` templates live in one file.
220230
221231
### What to discuss
222232
223233
- **4400 lines in one file.** This is at the boundary of manageable. A reviewer can `grep` and navigate, but side-by-side comparison of specializations requires tooling.
224234
- **The standard doesn't mandate file structure.** But for a reference implementation intended to demonstrate proposed wording, would splitting `expected_ref.hpp` (or similar) improve reviewability?
225-
- **Compile times.** Every translation unit that includes `expected.hpp` parses all six specializations. For users who only need `expected<T, E>`, this is wasted work. The module build path (`expected.cppm`) mitigates this but is opt-in and not the default.
235+
- **Compile times.** Every translation unit that includes `expected.hpp` parses all three templates. For users who only need `expected<T, E>`, this is wasted work. The module build path (`expected.cppm`) mitigates this but is opt-in and not the default.
226236
227237
---
228238

papers/.clang-format

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
AccessModifierOffset: -2
2+
AlignAfterOpenBracket: Align
3+
AlignConsecutiveAssignments: true
4+
AlignConsecutiveDeclarations: true
5+
AlignEscapedNewlines: Left
6+
AlignOperands: true
7+
AlignTrailingComments: true
8+
AllowAllParametersOfDeclarationOnNextLine: true
9+
AllowShortBlocksOnASingleLine: false
10+
AllowShortCaseLabelsOnASingleLine: false
11+
AllowShortFunctionsOnASingleLine: All
12+
AllowShortIfStatementsOnASingleLine: false
13+
AllowShortLoopsOnASingleLine: false
14+
AlwaysBreakAfterDefinitionReturnType: None
15+
AlwaysBreakAfterReturnType: None
16+
AlwaysBreakBeforeMultilineStrings: false
17+
AlwaysBreakTemplateDeclarations: Yes
18+
BinPackArguments: false
19+
BinPackParameters: false
20+
BraceWrapping:
21+
AfterClass: false
22+
AfterControlStatement: false
23+
AfterEnum: false
24+
AfterFunction: false
25+
AfterNamespace: false
26+
AfterObjCDeclaration: false
27+
AfterStruct: false
28+
AfterUnion: false
29+
AfterExternBlock: false
30+
BeforeCatch: false
31+
BeforeElse: false
32+
IndentBraces: false
33+
SplitEmptyFunction: true
34+
SplitEmptyRecord: true
35+
SplitEmptyNamespace: true
36+
BreakBeforeBinaryOperators: None
37+
BreakBeforeBraces: Custom
38+
BreakBeforeInheritanceComma: false
39+
BreakBeforeTernaryOperators: true
40+
BreakConstructorInitializersBeforeComma: false
41+
BreakConstructorInitializers: BeforeColon
42+
BreakAfterJavaFieldAnnotations: false
43+
BreakStringLiterals: true
44+
ColumnLimit: 79
45+
CommentPragmas: '^ IWYU pragma:'
46+
CompactNamespaces: false
47+
ConstructorInitializerAllOnOneLineOrOnePerLine: true
48+
ConstructorInitializerIndentWidth: 4
49+
ContinuationIndentWidth: 4
50+
Cpp11BracedListStyle: true
51+
DerivePointerAlignment: false
52+
DisableFormat: false
53+
ExperimentalAutoDetectBinPacking: false
54+
FixNamespaceComments: true
55+
ForEachMacros:
56+
- foreach
57+
- Q_FOREACH
58+
- BOOST_FOREACH
59+
IncludeBlocks: Preserve
60+
IncludeCategories:
61+
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
62+
Priority: 2
63+
- Regex: '^(<|"(gtest|isl|json)/)'
64+
Priority: 3
65+
- Regex: '.*'
66+
Priority: 1
67+
IncludeIsMainRegex: '$'
68+
IndentCaseLabels: false
69+
IndentPPDirectives: None
70+
IndentWidth: 4
71+
IndentWrappedFunctionNames: false
72+
JavaScriptQuotes: Leave
73+
JavaScriptWrapImports: true
74+
KeepEmptyLinesAtTheStartOfBlocks: true
75+
MacroBlockBegin: ''
76+
MacroBlockEnd: ''
77+
MaxEmptyLinesToKeep: 1
78+
NamespaceIndentation: None
79+
ObjCBinPackProtocolList: Auto
80+
ObjCBlockIndentWidth: 4
81+
ObjCSpaceAfterProperty: false
82+
ObjCSpaceBeforeProtocolList: true
83+
PenaltyBreakAssignment: 2
84+
PenaltyBreakBeforeFirstCallParameter: 19
85+
PenaltyBreakComment: 300
86+
PenaltyBreakFirstLessLess: 120
87+
PenaltyBreakString: 1000
88+
PenaltyBreakTemplateDeclaration: 10
89+
PenaltyExcessCharacter: 1000000
90+
PenaltyReturnTypeOnItsOwnLine: 60
91+
PointerAlignment: Left
92+
ReflowComments: true
93+
SortIncludes: false
94+
SortUsingDeclarations: true
95+
SpaceAfterCStyleCast: false
96+
SpaceAfterTemplateKeyword: false
97+
SpaceBeforeAssignmentOperators: true
98+
SpaceBeforeCtorInitializerColon: true
99+
SpaceBeforeInheritanceColon: true
100+
SpaceBeforeParens: ControlStatements
101+
SpaceBeforeRangeBasedForLoopColon: true
102+
SpaceInEmptyParentheses: false
103+
SpacesBeforeTrailingComments: 1
104+
SpacesInAngles: false
105+
SpacesInContainerLiterals: true
106+
SpacesInCStyleCastParentheses: false
107+
SpacesInParentheses: false
108+
SpacesInSquareBrackets: false
109+
Standard: Auto
110+
TabWidth: 8
111+
UseTab: Never

papers/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ _minted/
2020
# Downloaded bibliography (too large to track; regenerate with make curl-bib)
2121
wg21.bib
2222

23+
# Generated clean wording (regenerate with make strip-wording)
24+
expected-clean.tex
25+
2326
# Emacs
2427
*~
2528
\#*\#

0 commit comments

Comments
 (0)