diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index dfb30a7..22a708c 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -23,11 +23,6 @@ updates:
schedule:
interval: "weekly"
- - package-ecosystem: pip
- directory: /papers/P2988
- schedule:
- interval: daily
-
- package-ecosystem: pip
directory: /
schedule:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 3d96a8b..ddd7b95 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -50,6 +50,8 @@ jobs:
include:
- language: actions
build-mode: none
+ - language: c-cpp
+ build-mode: manual
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
@@ -95,15 +97,12 @@ jobs:
# to build your code.
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- - if: matrix.build-mode == 'manual'
+ - name: Build C++ with the GCC debug preset
+ if: matrix.build-mode == 'manual'
shell: bash
run: |
- echo 'If you are using a "manual" build mode for one or more of the' \
- 'languages you are analyzing, replace this with the commands to build' \
- 'your code, for example:'
- echo ' make bootstrap'
- echo ' make release'
- exit 1
+ cmake --preset gcc-debug
+ cmake --build --preset gcc-debug
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8742438..b745b87 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -37,7 +37,7 @@ If the presets are not suitable for your use case, a traditional CMake invocatio
provide more configurability.
To configure, build and test the project manually, you can run this set of commands. Note
-that this requires GoogleTest to be installed.
+that this requires Catch2 to be installed.
```bash
cmake \
@@ -65,7 +65,7 @@ The best way to install the project's dependencies is to use the vcpkg workflow.
To do so, make sure vcpkg is installed and `VCPKG_ROOT` is defined in your environment,
then specify
`-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"`. Vcpkg will handle
-the project's dependencies, including GoogleTest.
+the project's dependencies, including Catch2.
Example commands:
@@ -73,7 +73,7 @@ Example commands:
cmake \
-B build \
-S . \
- -DCMAKE_CXX_STANDARD=17 \
+ -DCMAKE_CXX_STANDARD=20 \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
cmake --build build
ctest --test-dir build
@@ -89,7 +89,7 @@ configure beman.expected to fetch them automatically via CMake FetchContent.
To do so, specify
`-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake`. This will
-bring in GoogleTest automatically along with any other dependency the project may require.
+bring in Catch2 automatically along with any other dependency the project may require.
Example commands:
diff --git a/Makefile b/Makefile
index a75cb86..eb40c92 100755
--- a/Makefile
+++ b/Makefile
@@ -55,7 +55,7 @@ VCPKG ?= $(shell command -v vcpkg 2> /dev/null)
ifeq ($(VCPKG),)
_cmake_top_level?="./cmake/use-fetch-content.cmake"
_toolchain:=$(_local_toolchain)
- _args=-DBEMANINFRA_googletest_REPO=file:///home/sdowney/bld/googletest/googletest.git
+ _args=
else
_vcpkg_toolchain:=$(VCPKG_ROOT)/scripts/buildsystems/vcpkg.cmake
_cmake_top_level?=$(_vcpkg_toolchain)
@@ -117,7 +117,7 @@ compile-headers: $(_build_path)/CMakeCache.txt ## Compile the headers
.PHONY: install
install: $(_build_path)/CMakeCache.txt compile ## Install the project
- $(CMAKE) --install $(_build_path) --config $(CONFIG) --component beman.expected --verbose
+ $(CMAKE) --install $(_build_path) --config $(CONFIG) --verbose
.PHONY: clean-install
clean-install:
@@ -155,7 +155,7 @@ env:
.PHONY: papers
papers:
- $(MAKE) -C papers/P2988 papers
+ $(MAKE) -C papers papers
.DEFAULT: $(_build_path)/CMakeCache.txt ## Other targets passed through to cmake
$(CMAKE) --build $(_build_path) --config $(CONFIG) --target $@ -- -k 0
diff --git a/README.md b/README.md
index cb669f3..069b08c 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,10 @@ The fix is to make each such member's definition token-identical to its
declaration; that work is not done here simply because we do not have direct
MSVC access to develop and verify the workarounds. Contributions with MSVC
access are welcome.
+
+The Windows setup below is retained for contributors who want to investigate
+MSVC support. It is currently unsupported and unverified; the documented build
+and test commands are not expected to pass with MSVC yet.
> These development environments are verified using our CI configuration.
## Development
@@ -138,15 +142,18 @@ brew install llvm
For Windows
-To build Beman libraries, you will need the MSVC compiler. MSVC can be obtained
-by installing Visual Studio; the free Visual Studio 2022 Community Edition can
-be downloaded from
+> [!WARNING]
+>
+> MSVC builds are currently unsupported and unverified. These instructions only
+> establish a Windows development environment for work on that support.
+
+The MSVC compiler can be obtained by installing Visual Studio; the free Visual
+Studio 2022 Community Edition can be downloaded from
[Microsoft](https://visualstudio.microsoft.com/vs/community/).
After Visual Studio has been installed, you can launch "Developer PowerShell for
VS 2022" by typing it into Windows search bar. This shell environment will
-provide CMake, Ninja, and MSVC, allowing you to build the library and run the
-tests.
+provide CMake, Ninja, and MSVC for investigating the current build failures.
@@ -259,6 +266,11 @@ include an appropriate `beman.expected` header from your source code.
> Altering include search paths to spell the include target another way (e.g.
> `#include `) is unsupported.
+Textual-header consumers can detect the reference extension with the public
+configuration macro `BEMAN_EXPECTED_HAS_REFERENCES`, whose value is `1`.
+Importing the `beman.expected` module does not import preprocessor macros;
+module support itself is unconditional when that build mode is selected.
+
The process for incorporating `beman.expected` into your project depends on the
build system being used. Instructions for CMake are provided in following sections.
@@ -296,6 +308,8 @@ This will generate the following directory structure at `/opt/beman`.
│ └── beman
│ └── expected
│ ├── bad_expected_access.hpp
+│ ├── config.hpp
+│ ├── config_generated.hpp
│ ├── expected.hpp
│ └── unexpected.hpp
└── lib
@@ -303,4 +317,5 @@ This will generate the following directory structure at `/opt/beman`.
└── beman.expected
├── beman.expected-config.cmake
├── beman.expected-config-version.cmake
- └── beman.expected-targets.cmake```
+ └── beman.expected-targets.cmake
+```
diff --git a/docs/human-design-review-guide.md b/docs/human-design-review-guide.md
index a2dbac0..410276f 100644
--- a/docs/human-design-review-guide.md
+++ b/docs/human-design-review-guide.md
@@ -6,7 +6,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` specialization for a reference value, a relaxation of the primary and `void` templates to admit a reference error `E`, and a new `unexpected` — three `expected` class templates in all, each accepting an object or reference error. The reference semantics follow P2988 (`optional`): rebind on assignment, shallow const, dangling prevention via deleted constructors.
-**File count:** 3 headers, ~4400 lines of implementation, 15 positive test files (469 tests), 54 negative compile tests.
+**File count:** 3 headers, 3,413 lines in `expected.hpp`, 19 positive test files with 688 Catch2 `TEST_CASE`s, and 59 negative compile tests.
**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.
@@ -41,7 +41,7 @@ generates them.
### Why this matters
-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.
+The total is 3,413 lines of template code in a single header. A factored implementation using a common base or policy template could plausibly cut this substantially. 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.
### The argument for the current approach
@@ -53,7 +53,7 @@ The total is ~4300 lines of template code in a single header. A factored impleme
- **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).
- **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.
-- **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.
+- **Review fatigue.** A reviewer looking at 3,413 lines of structurally similar code will inevitably skim. The 95th `requires` clause gets less scrutiny than the 5th.
### What to decide
@@ -79,7 +79,7 @@ P2988 settled this for `optional` after years of debate. JeanHeyd Meneide's
### What to discuss anyway
-- **User expectation.** C++ programmers coming from `std::reference_wrapper` expect assign-through (that's what `reference_wrapper::operator=` does). This will surprise some users.
+- **Wrapper precedent.** `std::reference_wrapper::operator=` also rebinds. The remaining wrapper drawbacks are `.get()` friction, wrapper identity in generic interfaces, and adaptation before monadic callables receive `T&` directly.
- **`emplace` also rebinds.** `e.emplace(new_ref)` rebinds, which is the only sensible behavior, but it differs from `expected::emplace(args...)` which constructs in-place. The name `emplace` is arguably misleading for reference types since nothing is being "emplaced" — a pointer is being reassigned.
- **Swap rebinds both sides.** Two `expected` objects swap their pointers, not the values they point to. This is consistent with rebind semantics but will surprise users who think of references as aliases.
@@ -159,7 +159,7 @@ Every deleted operation carries a C++26 `= delete("message")` string explaining
### What to discuss
-- **C++26 feature.** `= delete("message")` is not available in C++23 or earlier. The README claims C++17+ support. This feature silently degrades — compilers that don't support it treat it as plain `= delete` — but the claim of C++17+ compatibility deserves scrutiny against the actual minimum language version needed for the rest of the code (`requires` clauses, concepts, `constexpr` union, etc.).
+- **C++26 feature.** `= delete("message")` is not available in C++23 or earlier. The project targets C++20 and uses a macro that emits a diagnostic message only when `__cpp_deleted_function` advertises support, otherwise spelling a plain deleted function.
- **Message quality.** Are the messages actionable? Do they guide the user to the correct alternative? Review each message for clarity and accuracy.
- **Is this the right mechanism?** An alternative is `static_assert(false, "message")` inside a constrained-away-but-still-instantiable template. That would work in C++23 but is arguably worse. The `= delete("message")` approach is cleaner and should be preferred if C++26 is truly the floor.
@@ -204,16 +204,16 @@ std::move(e).and_then(f); // f still gets int&, not int&&
### What exists
-- 15 positive test files with 469 Catch2 `TEST_CASE` entries
-- 54 negative compile tests (`_fail.cpp`) each with a `PASS_REGULAR_EXPRESSION` regex
+- 19 positive test files with 688 Catch2 `TEST_CASE` entries
+- 59 negative compile tests (`_fail.test.cpp`) each with a `PASS_REGULAR_EXPRESSION` regex
- Separate constraint test files for primary and `T&` specializations
- Hardened precondition tests under `BEMAN_EXPECTED_HARDENED`
### Gaps to discuss
-- **No constraint test files for `expected`, `expected`, or `expected`.** The primary template has `expected_constraints.test.cpp` and `expected` has `expected_ref_constraints.test.cpp`. The other three reference specializations have no dedicated constraint test file. Their SFINAE behavior is untested under `static_assert(!is_constructible_v<...>)` patterns.
-- **No monadic constraint tests for reference specializations.** `expected_monadic_constraints.test.cpp` covers only the primary and void specializations with move-only error types. The same constraints apply to reference specializations but are untested.
-- **No triviality tests for reference specializations.** `expected_trivial.test.cpp` covers only primary and void. Reference specializations (especially `T&, E&` and `void, E&`, which are pointer-only) should be trivially copyable, movable, and destructible unconditionally. This is not verified.
+- **Reference-error constraints are distributed.** `expected`, `expected`, and `expected` assertions live in their behavioral files and `expected_review_corrections.test.cpp`, rather than dedicated constraint files. Review both locations together.
+- **Monadic constraints remain concentrated.** `expected_monadic_constraints.test.cpp` is the systematic matrix; the reference behavioral files add specialization-specific checks. Confirm any new monadic constraint is represented in both relevant layers.
+- **Triviality is configuration-dependent.** Pointer-only `T&`/`E&` combinations have direct trait assertions. `expected` still inherits object-`E` properties, so it is not unconditionally trivial.
- **Monadic tests are embedded in general test files** rather than having dedicated `_monadic.test.cpp` files for reference specializations. This makes it harder to verify completeness.
### What to decide
@@ -230,7 +230,7 @@ Everything is in `expected.hpp`. The `unexpected.hpp` and `bad_expected_access.h
### What to discuss
-- **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.
+- **3,413 lines in one file.** This is at the boundary of manageable. A reviewer can `grep` and navigate, but side-by-side comparison of class-template forms requires tooling.
- **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?
- **Compile times.** Every translation unit that includes `expected.hpp` parses all three templates. For users who only need `expected`, this is wasted work. The module build path (`expected.cppm`) mitigates this but is opt-in and not the default.
diff --git a/docs/llm-code-review-guide.md b/docs/llm-code-review-guide.md
index 527f394..dd1456a 100644
--- a/docs/llm-code-review-guide.md
+++ b/docs/llm-code-review-guide.md
@@ -1,7 +1,7 @@
# LLM Code Review Guide: `beman::expected`
**Target Audience:** High-context Large Language Models acting as rigorous C++ code reviewers.
-**Scope:** The entire `include/beman/expected/` header set — one 4400-line header (`expected.hpp`) plus two small companions (`unexpected.hpp`, `bad_expected_access.hpp`).
+**Scope:** The entire `include/beman/expected/` header set — one 3,413-line header (`expected.hpp`) plus two small companions (`unexpected.hpp`, `bad_expected_access.hpp`). The suite has 19 positive test files with 688 Catch2 `TEST_CASE`s and 59 negative compile tests.
## Mission
@@ -17,7 +17,7 @@ You are reviewing a proposed **C++29** reference implementation of `std::expecte
| Standard (org-mode) | `docs/standard/expected.org` | Same content, machine-parseable |
| Standard (plain text) | `docs/standard/expected.txt` | Same content, grep-friendly |
| Conformance audit | `docs/conformance-audit.md` | Every clause checked with PASS/EXT/FIXED status |
-| P2988 (optional\) | External: open-std.org/P2988 | Rebind semantics design source for reference specializations |
+| P2988R12 (optional\) | [wg21.link/P2988R12](https://wg21.link/P2988R12) | Rebind semantics design source for reference specializations |
| beman/optional reference impl | `~/src/steve-downey/optional/main/` | Pattern source for T& storage and dangling prevention |
### What You Have No Normative Source For
@@ -38,8 +38,8 @@ This means: for the primary template and void specialization, you can check clau
Before reading line-by-line, establish the shape:
-1. **Count specializations.** There should be exactly 6: ``, ``, ``, ``, ``, ``. Each should have complete API surface (constructors, assignment, observers, swap, equality, monadic ops).
-2. **Identify the storage model** for each specialization. Primary uses union of T and E. Void uses union of E only. Reference specializations store pointers. Verify no specialization accidentally uses a union member for a reference type.
+1. **Count class-template forms.** There should be exactly 3: primary ``, partial specialization ``, and partial specialization ``. Allowing `E` to be an lvalue reference yields the six value/error configurations without three additional class definitions.
+2. **Identify the storage model** for each form. Every error arm is `unexpected`; it owns an object for object `E` and stores a pointer for reference `E`. The `T&` form stores its value as a pointer. Verify no union directly contains a reference.
3. **Map the helper utilities.** `reinit_expected` (lines 65-84) handles destroy-and-reconstruct for value-type transitions. `reference_constructs_from_temporary_v` (lines 88-100) is the dangling-prevention concept. Verify these are used correctly and only where applicable.
### Phase 2: Clause-by-Clause for Primary and Void
@@ -86,7 +86,7 @@ Each reference specialization should offer the **same user-facing operations** a
| unexpect_t ctor | yes | yes | yes | yes | yes |
| in_place_t ctor | yes | **deleted** | yes | **deleted** | N/A (void) |
| Value assignment | yes | rebind | yes | yes | N/A |
-| unexpected assignment | yes | yes | **deleted** | **deleted** | N/A |
+| unexpected assignment | yes | yes | from `` only | from `` only | from `` only |
| emplace | yes | rebind | yes | rebind | emplace() (void) |
| operator* | T& | T& | T& | T& | void |
| operator-> | T* | T* | T* | T* | N/A |
@@ -99,7 +99,7 @@ Each reference specialization should offer the **same user-facing operations** a
Verify each cell. Pay special attention to the **deleted** entries — each should have a `= delete("message")` with a clear diagnostic and a corresponding negative compile test.
-For the `from only` cells: construction from `unexpected` is permitted for reference `E` **only when `G` is itself a reference** (`unexpected`, which holds a pointer to an external object). Construction from a value-typed `unexpected` stays `= delete`d (it would dangle), and rebinding *assignment* from `unexpected` is not offered for reference `E`. Verify: `is_constructible_v, unexpected>` is true, `is_constructible_v, unexpected>` is false, and `unexpected` → `int&` is rejected (const drop).
+For the `from only` cells: construction and rebinding assignment from `unexpected` are permitted for reference `E` **only when `G` is itself a reference** (`unexpected`, which holds a pointer to an external object). The value-typed form stays deleted because it would dangle. Verify construction and assignment traits for the accepted reference case, rejected value case, and rejected const-dropping case.
#### 2. Dangling Prevention
@@ -112,10 +112,10 @@ For every constructor or assignment that accepts a forwarding reference where th
#### 3. Storage Layout
-- `expected`: pointer `T*` plus union `{ E unex_; }` plus `bool has_val_`
-- `expected`: value `T` in union plus `E*` pointer plus `bool has_val_`
-- `expected`: two pointers `T*`, `E*` plus `bool has_val_`
-- `expected`: pointer `E*` plus `bool has_val_`
+- `expected`: union of `T*` and `unexpected` plus `bool has_val_`
+- `expected`: union of `T` and pointer-holding `unexpected` plus `bool has_val_`
+- `expected`: union of `T*` and pointer-holding `unexpected` plus `bool has_val_`
+- `expected`: pointer-holding `unexpected` plus `bool has_val_`
Verify:
- No union contains a reference or pointer where the active member tracking could be wrong
@@ -141,7 +141,7 @@ Every function in this implementation should be `constexpr`. Verify:
#### Trivial Special Member Functions
-The primary and void specializations must be trivially copyable/movable/destructible when T and E are. Reference specializations (pointer-based) should be trivially everything unconditionally. Verify:
+The primary and void forms must be trivially copyable/movable/destructible when their stored alternatives permit it. Pointer-only `T&`/`E&` configurations should be trivial, while `expected` still follows object-`E` properties. Verify:
- `= default` paths exist for trivial cases
- The conditional dispatch between trivial and non-trivial paths is correct
- Reference specializations don't accidentally have non-trivial destructors
diff --git a/docs/optional_references.md b/docs/optional_references.md
deleted file mode 100644
index ab780e0..0000000
--- a/docs/optional_references.md
+++ /dev/null
@@ -1,638 +0,0 @@
-# Changes Since Last Version
-
-- **Changes since R10**
-
- - Wording changes from LWG review.
-
-- **Changes since R9**
-
- - Fix cast in wording removing base/derived UB
-
-# Comparison table
-
-## Using a raw pointer result for an element search function
-
-This is the convention the C++ core guidelines suggest, to use a raw pointer for representing optional non-owning references. However, there is a user-required check against , no type safety meaning no safety against mis-interpreting such a raw pointer, for example by using pointer arithmetic on it.
-
-## returning result of an element search function via a (smart) pointer
-
-The disadvantage here is that is both non-standard and not well named, therefore this example uses that would have the advantage of avoiding dangling through potential lifetime extension. However, on the downside is still the explicit checks against the on the client side, failing so risks undefined behavior.
-
-## returning result of an element search function via an iterator
-
-This might be the obvious choice, for example, for associative containers, especially since their iterator stability guarantees. However, returning such an iterator will leak the underlying container type as well necessarily requires one to know the sentinel of the container to check for the not-found case.
-
-## Using an optional\ as a substitute for optional\
-
-This approach adds another level of indirection and requires two checks to take a definite action.
-
-# Motivation
-
-Other than the standard library’s implementation of optional, optionals holding references are common. The desire for such a feature is well understood, and many optional types in commonly used libraries provide it, with the semantics proposed here. One standard library implementation already provides an implementation of but disables its use, because the standard forbids it.
-
-The research in JeanHeyd Meneide’s \_References for Standard Library Vocabulary Types - an optional case study.\_ shows conclusively that rebind semantics are the only safe semantic as assign through on engaged is too bug-prone. Implementations that attempt assign-through are abandoned. The standard library should follow existing practice and supply an that rebinds on assignment.
-
-Additional background reading on can be found in JeanHeyd Meneide’s article \_To Bind and Loose a Reference\_ .
-
-In freestanding environments or for safety-critical libraries, an optional type over references is important to implement containers, that otherwise as the standard library either would cause undefined behavior when accessing an non-available element, throw an exception, or silently create the element. Returning a plain pointer for such an optional reference, as the core guidelines suggest, is a non-type-safe solution and doesn’t protect in any way from accessing an non-existing element by a de-reference. In addition, the monadic APIs of makes is especially attractive by streamlining client code receiving such an optional reference, in contrast to a pointer that requires an explicit nullptr check and de-reference.
-
-There is a principled reason not to provide a partial specialization over as the semantics are in some ways subtly different than the primary template. Assignment may have side-effects not present in the primary, which has pure value semantics. However, I argue this is misleading, as reference semantics often has side-effects. The proposed semantic is similar to what an provides, with much greater usability.
-
-There are well motivated suggestions that perhaps instead of an there should be an that is an independent primary template. This proposal rejects that, because we need a policy over all sum types as to how reference semantics should work, as optional is a variant over T and monostate. That the library sum type can not express the same range of types as the product type, tuple, is an increasing problem as we add more types logically equivalent to a variant. The template types and should behave as extensions of and , or we lose the ability to reason about generic types.
-
-That we can’t guarantee from (product type) that (sum type) is valid, is a problem, and one that reflection can’t solve. A language sum type could, but we need agreement on the semantics.
-
-The semantics of a variant with a reference are as if it holds the address of the referent when referring to that referent. All other semantics are worse. Not being able to express a variant\ is inconsistent, hostile, and strictly worse than disallowing it.
-
-Thus, we expect future papers to propose and with the ability to hold references. The latter can be used as an iteration type over elements.
-
-# Design
-
-The design is straightforward. The holds a pointer to the underlying object of type , or if the optional is disengaged. The implementation is simple, especially with C++20 and up techniques, using concept constraints. As the held pointer is a primitive regular type with reference semantics, many operations can be defaulted and are by nature. See and . The implementation is less than 200 lines of code, much of it the monadic functions with identical textual implementations with different signatures and different overloads being called.
-
-In place construction is not supported as it would just be a way of providing immediate life-time issues.
-
-## Relational Operations
-
-The definitions of the relational operators are the same as for the base template. Interoperable comparisons between T and optional\ work as expected. This is not true for the boost optional\.
-
-## make_optional
-
-With further research, the existing uses of make_optional\ seem to be primarily test cases, and deliberate use seems to be exceedingly rare in the wild. Reflector review was much more positive about removing the misleading ability to create an via . In addition, the multiple argument forms can be used to attempt to construct a optional that contains a reference, but this becomes ill formed because of existing mandates at the type level. In order to preserve existing behavior, where make_optional is not well formed if it constructs a reference, changes to should be made.
-
-Adding a non-type template parameter as the first template parameter to the single argument and mandating that the multi-argument version not request a reference type as the parameter, will diagnose mistaken use of and preserve the existing behavior.
-
-Since construction of an object in order to make a reference to it to construct an optional containing a reference would always dangle, there do not seem to be any use cases for the multi-argument or initializer list forms of make_optional for reference types, and the constructor form seems to satisfy all cases for single argument construction of a optional containing a reference, there does not seem to be a need for a factory function for optional over reference.
-
-There was also discussion of using to indicate reference use, in analogy with std::tuple. Unfortunately there are existing uses of optional over reference_wrapper as a workaround for lack of reference specialization, and it would be a breaking change for such code.
-
-## Trivial construction
-
-Construction of should be trivial, because it is straightforward to implement, and is trivial. Boost is not.
-
-## Value Category Affects value()
-
-For several implementations there are distinct overloads for functions depending on value category, with the same implementation. However, this makes it very easy to accidentally steal from the underlying referred to object. Value category should be shallow. Thanks to many people for pointing this out. If “Deducing ” had been used, the problem would have been much more subtle in code review.
-
-## Shallow vs Deep const
-
-There is some implementation divergence in optionals about deep const for . That is, can the referred to be modified through a . Does return an or a , and does return an or a . I believe it is overall more defensible if the is shallow as it would be for a where the constness of the struct ref does not affect if the p pointer can be written through. This is consistent with the rebinding behavior being proposed.
-
-Where deeper constness is desired, would prevent non const access to the underlying object.
-
-## Conditional Explicit
-
-As in the base template, is made conditional on the type used to construct the optional. . This is not present in boost::optional, leading to differences in construction between braced initialization and = that can be surprising.
-
-## value_or
-
-After extensive discussion, it seems there is no particularly wonderful solution for that does not involve a time machine. Implementations of optionals that support reference semantics diverge over the return type, and the current one is arguably wrong, and should use something based on , which of course did not exist when was standardized.
-
-The weak consensus is to return a from as this is least likely to cause issues. There was at least one strong objection to this choice, but all other choices had more objections. The author intends to propose free functions , , , and over all types modeling optional-like, , in the next revision of . This would cover , , and pointer types.
-
-Having return by value also allows the common case of using a literal as the alternative to be expressed concisely.
-
-## in_place_t construction
-
-The reference specialization allows a limited form of in_place construction where the argument can be converted to the appropriate reference without creation of a temporary. As the reference specialization is non-owning, there is no “place” for a temporary to be constructed that will not dangle. For cases where the lifetime of the constructed object would match the lifetime of the optional, the temporary can be constructed explicitly, instead.
-
-## Converting assignment
-
-A similarly limited converting assignment operator is provided for cases where an optional\ has a value or refers to a value which can be converted to a T& without construction of a temporary. In particular, converting an optional\ to an optional\ is supported.
-
-## Compiler Explorer Playground
-
-See for an updated playground with relevant Google Test functions and various optional implementations made available for cross reference including a flattened in-place version of the reference implementation.
-
-# Principles for Reification of Design
-
-Optional must never construct a temporary, or knowingly take the address of an temporary or part of an temporary.
-
-It is always presumed safe to copy the pointer value from an optional, since by induction, it is not dangling.
-
-Optional has no storage, so should never construct a T, it may convert a U to a T, so long as that conversion does not create a temporary.
-
-Constructors that would convert from temporary are marked deleted. They should be sufficiently constrained that it was the correct choice and there is no more general, less constrained constructor that would not have created a dangling pointer.
-
-Failure to compile either by ambiguity or no eligible constructors in the overload set is preferable to optional being responsible for use after free or dangling.
-
-Assignment is always from an optional, which may have been an implicit construction. The assignment cannot throw, the construction/conversion may. The assignment may therefore need annotation converting the rhs if that constructor was explicit. This must not be necessary in the default case of creating an optional reference to an lvalue of the same type.
-
-The model for the constraints and mandates for is taken from over reference types. The type takes the most care of types in the standard library in dealing with creation of temporaries.
-
-As is designed to be converting, to create instances from arguments that can be used to create the underlying type, constructors should be explicit only where the operations used to create the pointer or the notional reference would be or are explicit.
-
-## Construction from temporary
-
-We disallow construction of from any type U in which:
-
-- the constructor body will create a temporary and bind it to a reference.
-
-- a const lvalue reference would be bound to rvalue.
-
-An example of the first case would be construction from . These cases always dangle.
-
-An example of the second case would be a construction from temporary .
-
-Prohibiting the second case does prevent some safe uses of the optional as the function parameter.
-
-Given:
-
-This will make a invocation ill-formed, despite the arg being safe to use from within the function body.
-
-This deviates from the design of the “view” parameters type, like or . However, we believe that this is the right choice due to the following:
-
-- Only a subset of cases would be working. As an illustration the very similar invocation is ill-formed, due to always being dangling.
-
-- Such design leads to the detection of reference to temporaries or local variables when is used as the return type.
-
- | | |
- |:----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
- | | \> getValue() std::string localString; return localString; // Ill-formed. std::optional\ localOptionalString; return localOptionalString; // ill-formed |
-
- One of the main motivational examples of is return from a lookup function, and eliminating dangling in such cases outweighs parameter cases.
-
- We are very grateful to Arthur O’Dwyer for his work on P2266R3 Simpler implicit move accepted in C++23, which makes it possible to implement this correctly.
-
-- We provide behavior consistent with , that disallows binding to xvalues. We believe that is closer in spirit to than any view type. It certainly shares some of the features.
-
-## Deleting dangling overloads
-
-To achieve the dangling safety expressed before, the constructor is marked deleted if it would lead to binding of the reference to temporary or the xvalue. However, deleted constructors are still considered to be candidates during overload resolution, leading to ambiguity in the following examples:
-
-During the reflector discussion, an option of an alternate design was presented, where the dangling overload would be constrained, and eliminated from the overload set.
-
-We strongly oppose changing this behavior, as:
-
-- We think that it is impossible to detect temporary binding to xvalue in such a design.
-
-- The behavior we propose is consistent with the behavior for optional for object types
-
- [TABLE]
-
-As language in general treats functions accepting by value and by const reference in the same manner during overload resolution, we believe achieving this consistency is a feature.
-
-The design that was introduced by , and , for references, is followed, where the detection of dangling does not affect the results of overload resolution and instead makes a call that would dangle be ill-formed and diagnosed.
-
-## Assignment of optional\
-
-In the case of , any assignment operation is equivalent to assigning a pointer, and there is no observable difference between: using converting assignment from or constructing temporary , and then assigning it to it.
-
-This observation allows us to provide only copy-assignment for , instead of a set of converting assignments, that would need to replicate the signatures of constructors and their constraints. Assignment from any other value is handled by first implicitly constructing and then using copy-assignment. Move-assignment is the same as copy-assignment, since only pointer copy is involved.
-
-## Copy and Assignment of optional\&& to optional\
-
-Care must be take to prevent the assignment of a movable optional to disallow the copy or assignment of the underlying referred to value to be stolen. The assignment or copy constructor should be used instead, which also needs to check slightly different constraints for and for testing . We thank Jan Kokemüller for uncovering this bug. The bug seems to be present in many optional implementations that support references.
-
-# Proposal
-
-Add an lvalue reference specialization for the std::optional template.
-
-# Wording
-
-The wording here cross references and adopts the wording in . The proposed changes are relative to the current working draft .
-
-# Impact on the standard
-
-A pure library extension, affecting no other parts of the library or language.
-
-# Acknowledgments
-
-Many thanks to all of the reviewers and authors of beman.optional, , in particular A. Jiang, Darius Neațu, David Sankel, Eddie Nolan, Jan Kokemüller, Jeff Garland, and River (Xueqing) Wu. Tomasz Kamiński provided extensive support for the library wording of optional\.
-
-# Document history
-
-- **Changes since R8**
-
- - Fix move/assign optional\ allowing stealing of referenced U
-
-- **Changes since R7**
-
- - Wording mandates/constraint fixes
-
- - Hash on T& pulled out
-
- - Notes on wording rendering
-
- - “Fix” make_optional\
-
-- **Changes since R6**
-
- - strike refref specialization
-
- - add converting assignment operator
-
- - add converting in place constructor
-
-- **Changes since R5**
-
- - refref specialization
-
- - fix monadic constraints on base template
-
-- **Changes since R4**
-
- - feature test macro
-
- - value_or updates from P3091
-
-- **Changes since R3**
-
- - make_optional discussion - always value
-
- - value_or discussion - always value
-
-- **Changes since R1**
-
- - Design points called out
-
-- **Changes since R0**
-
- - Wording Updates
-
-# Implementation
-
-``` c++
-// ----------------------
-// BASE AND DETAILS ELIDED
-// ----------------------
-
-/****************/
-/* optional */
-/****************/
-
-template
-class optional {
- public:
- using value_type = T;
- using iterator =
- std::contiguous_iterator; // see [optionalref.iterators]
- public:
- // \ref{optionalref.ctor}, constructors
-
- constexpr optional() noexcept = default;
- constexpr optional(nullopt_t) noexcept : optional() {}
- constexpr optional(const optional& rhs) noexcept = default;
-
- template
- requires(std::is_constructible_v &&
- !std::reference_constructs_from_temporary_v)
- constexpr explicit optional(in_place_t, Arg&& arg);
-
- template
- requires(std::is_constructible_v &&
- !(std::is_same_v, in_place_t>) &&
- !(std::is_same_v, optional>) &&
- !std::reference_constructs_from_temporary_v)
- constexpr explicit(!std::is_convertible_v)
- optional(U&& u) noexcept(std::is_nothrow_constructible_v) {
- convert_ref_init_val(u);
- }
-
- template
- requires(std::is_constructible_v &&
- !(std::is_same_v, in_place_t>) &&
- !(std::is_same_v, optional>) &&
- std::reference_constructs_from_temporary_v)
- constexpr optional(U&& u) = delete;
-
- // The full set of 4 overloads on optional by value category, doubled to
- // 8 by deleting if reference_constructs_from_temporary_v is true. This
- // allows correct constraints by propagating the value category from the
- // optional to the value within the rhs.
- template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- !std::reference_constructs_from_temporary_v)
- constexpr explicit(!std::is_convertible_v) optional(
- optional& rhs) noexcept(std::is_nothrow_constructible_v);
-
- template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- !std::reference_constructs_from_temporary_v)
- constexpr explicit(!std::is_convertible_v)
- optional(const optional& rhs) noexcept(
- std::is_nothrow_constructible_v);
-
- template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- !std::reference_constructs_from_temporary_v)
- constexpr explicit(!std::is_convertible_v)
- optional(optional&& rhs) noexcept(
- noexcept(std::is_nothrow_constructible_v));
-
- template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- !std::reference_constructs_from_temporary_v)
- constexpr explicit(!std::is_convertible_v)
- optional(const optional&& rhs) noexcept(
- noexcept(std::is_nothrow_constructible_v));
-
- template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- std::reference_constructs_from_temporary_v)
- constexpr optional(optional& rhs) = delete;
-
- template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- std::reference_constructs_from_temporary_v)
- constexpr optional(const optional& rhs) = delete;
-
- template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- std::reference_constructs_from_temporary_v)
- constexpr optional(optional&& rhs) = delete;
-
- template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- std::reference_constructs_from_temporary_v)
- constexpr optional(const optional&& rhs) = delete;
-
- // \ref{optionalref.dtor}, destructor
- constexpr ~optional() = default;
-
- // \ref{optionalref.assign}, assignment
- constexpr optional& operator=(nullopt_t) noexcept;
-
- constexpr optional& operator=(const optional& rhs) noexcept = default;
-
- template
- requires(std::is_constructible_v &&
- !std::reference_constructs_from_temporary_v)
- constexpr T&
- emplace(U&& u) noexcept(std::is_nothrow_constructible_v);
-
- // \ref{optionalref.swap}, swap
- constexpr void swap(optional& rhs) noexcept;
-
- // \ref{optional.iterators}, iterator support
- constexpr iterator begin() const noexcept;
- constexpr iterator end() const noexcept;
-
- // \ref{optionalref.observe}, observers
- constexpr T* operator->() const noexcept;
- constexpr T& operator*() const noexcept;
- constexpr explicit operator bool() const noexcept;
- constexpr bool has_value() const noexcept;
- constexpr T& value() const;
- template >
- constexpr std::remove_cv_t value_or(U&& u) const;
-
- // \ref{optionalref.monadic}, monadic operations
- template
- constexpr auto and_then(F&& f) const;
- template
- constexpr optional> transform(F&& f) const;
- template
- constexpr optional or_else(F&& f) const;
-
- // \ref{optional.mod}, modifiers
- constexpr void reset() noexcept;
-
- private:
- T* value_ = nullptr; // exposition only
-
- // \ref{optionalref.expos}, exposition only helper functions
- template
- constexpr void convert_ref_init_val(U&& u) {
- // Creates a variable, \tcode{r},
- // as if by \tcode{T\& r(std::forward(u));}
- // and then initializes \exposid{val} with \tcode{addressof(r)}
- T& r(std::forward(u));
- value_ = std::addressof(r);
- }
-};
-
-// \rSec3[optionalref.ctor]{Constructors}
-template
-template
- requires(std::is_constructible_v &&
- !std::reference_constructs_from_temporary_v)
-constexpr optional::optional(in_place_t, Arg&& arg) {
- convert_ref_init_val(std::forward(arg));
-}
-
-// Clang is unhappy with the out-of-line definition
-//
-// template
-// template
-// requires(std::is_constructible_v &&
-// !(is_same_v, in_place_t>) &&
-// !(is_same_v, optional>) &&
-// !std::reference_constructs_from_temporary_v)
-// constexpr optional::optional(U&& u)
-// noexcept(is_nothrow_constructible_v)
-// : value_(std::addressof(static_cast(std::forward(u)))) {}
-
-template
-template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- !std::reference_constructs_from_temporary_v)
-constexpr optional::optional(optional& rhs) noexcept(
- std::is_nothrow_constructible_v) {
- if (rhs.has_value()) {
- convert_ref_init_val(*rhs);
- }
-}
-
-template
-template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- !std::reference_constructs_from_temporary_v)
-constexpr optional::optional(const optional& rhs) noexcept(
- std::is_nothrow_constructible_v) {
- if (rhs.has_value()) {
- convert_ref_init_val(*rhs);
- }
-}
-
-template
-template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- !std::reference_constructs_from_temporary_v)
-constexpr optional::optional(optional&& rhs) noexcept(
- noexcept(std::is_nothrow_constructible_v)) {
- if (rhs.has_value()) {
- convert_ref_init_val(*std::move(rhs));
- }
-}
-
-template
-template
- requires(std::is_constructible_v &&
- !std::is_same_v, optional> &&
- !std::is_same_v &&
- !std::reference_constructs_from_temporary_v)
-constexpr optional::optional(const optional&& rhs) noexcept(
- noexcept(std::is_nothrow_constructible_v)) {
- if (rhs.has_value()) {
- convert_ref_init_val(*std::move(rhs));
- }
-}
-
-// \rSec3[optionalref.assign]{Assignment}
-template
-constexpr optional& optional::operator=(nullopt_t) noexcept {
- value_ = nullptr;
- return *this;
-}
-
-template
-template
- requires(std::is_constructible_v &&
- !std::reference_constructs_from_temporary_v)
-constexpr T&
-optional::emplace(U&& u) noexcept(std::is_nothrow_constructible_v) {
- convert_ref_init_val(std::forward(u));
- return *value_;
-}
-
-// \rSec3[optionalref.swap]{Swap}
-
-template
-constexpr void optional::swap(optional& rhs) noexcept {
- std::swap(value_, rhs.value_);
-}
-
-// \rSec3[optionalref.iterators]{Iterator Support}
-
-template
-constexpr optional::iterator optional::begin() const noexcept {
- return iterator(has_value() ? value_ : nullptr);
-};
-
-template
-constexpr optional::iterator optional::end() const noexcept {
- return begin() + has_value();
-}
-
-// \rSec3[optionalref.observe]{Observers}
-template
-constexpr T* optional::operator->() const noexcept {
- return value_;
-}
-
-template
-constexpr T& optional::operator*() const noexcept {
- return *value_;
-}
-
-template
-constexpr optional::operator bool() const noexcept {
- return value_ != nullptr;
-}
-template
-constexpr bool optional