Implement optional C++26 polyfill#176
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Rename the pfn::detail namespace-scope helpers (_storage -> _expected_base, _storage_union_t -> _expected_union_t, _is_storage_union -> _is_expected_union) so the generic names are free for the parallel pfn::optional helpers to come.
The general template mirrors pfn::detail::_expected_union_t<void, E> with the value/empty roles reversed: the engaged value lives in v_, and a trivial _dummy_t placeholder lives in e_ so the union always has an active member (for constexpr). The optional<T&> specialization is a single T* (nullptr encodes the disengaged state). Not yet wired into the optional class. Assisted-by: Claude:claude-opus-4-8
…uctors _optional_base<T> holds the flag-discriminated value union; its <T&> specialization holds the referent directly as a pointer (nullptr encodes the disengaged state, so no union, no discriminant flag, and optional<T&> stays pointer-sized and trivial). Both optional templates now privately inherit their base and drop the placeholder storage. optional<T>'s constructors and destructor are implemented over the base: default (disengaged), nullopt, in_place (and the initializer_list overload), the conditional copy/move (trivial -> defaulted, non-trivial -> the base's discriminant ctor), and a defaulted destructor. optional<T&> likewise gets its default / nullopt / in_place / copy ctors and a defaulted destructor. Converting constructors and assignment are deferred. The converting ctor placeholders are removed for now: unconstrained, optional(U&&) is an exact match for a non-const optional lvalue and would hijack the copy ctor. Assisted-by: Claude:claude-opus-4-8
Cover optional<T> and optional<T&> constructors and destructors: type aliases, triviality/noexcept/constructibility traits, constexpr construction, throwing construction, and construction-path checks via helper_t::state. Observers, assignment, swap and monadic operations are not yet defined, so the tests are compile-time-focused and avoid ODR-using them. Assisted-by: Claude:claude-opus-4-8 Assisted-by: Augment:auggie-0.31.0
The move ctor's conditional noexcept is standard-mandated ([optional.optional]), not a pfn extension like the copy ctor's; the sibling sections already leave it unconditional. Assisted-by: Claude:claude-sonnet-5
…tional Mirrors expected_validation.cpp's nested-include pattern, but runs unconditionally (no LIBFN_MODE gate) since std::optional predates C++23. The reference specialization is excluded: no released standard library implements C++26's optional<T&> yet. Assisted-by: Claude:claude-sonnet-5
optional<T>'s copy/move assignment reuse _optional_union_t::_reinit the same way _expected_base::_assign already does for expected<void, E>: one side of the transition is always the trivial _dummy_t, so no strong- exception-guarantee snapshot of the old member is needed. _optional_base gains _reset() (the shared disengage step used by operator=(nullopt_t) and emplace) and _assign(), and optional<T> wires up operator=(nullopt_t), operator=(optional const&) and operator=(optional&&) over them. Converting assignment stays deferred, same as the converting constructors. emplace() and the core observers (has_value, operator bool, operator*, operator->) are implemented in _optional_base and re-exposed via `using`, matching how expected already does it. Observers were pulled forward from their own [optional.observe] slice because assignment tests need them to verify anything past compile-time traits; value()/value_or() stay declared-only since they need a bad_optional_access type. Tests mirror expected.cpp's assignment/emplace shape (same helper_t<V> fixture naming, from-rval/from-lval-const nesting, constexpr sections). Verified against both pfn and, via optional_validation.cpp, real std::optional on libstdc++ and libc++ under ASan+UBSan+LeakSan; the run surfaced a genuine libstdc++/libc++ disagreement over whether emplace is conditionally noexcept (unspecified by the standard), gated accordingly. Assisted-by: Claude:claude-sonnet-5
optional<T&> is pointer-like: T* is trivial, so unlike optional<T> none of this needs _reinit/_assign/_reset. operator=(nullopt_t) just clears the pointer. emplace(U&&) is constrained on is_constructible_v<T&, U> and implemented via _convert_ref_init_val, the [optional.ref.expos] helper that binds a reference through whatever conversion T& requires and stores its address; the dangling-reference guard from [optional.ref.assign]'s constraints (reference_constructs_from_temporary_v) is left as a TODO, since it's a C++23-only trait with no portable C++20 fallback -- the same gap the existing in_place ctor already has. The observers (has_value, operator bool, operator*, operator->) each get a single overload: const does not propagate to the referent here, so a const optional<T&> still yields a mutable T&, unlike optional<T>'s const/ref-qualified overload set. Tests cover the pointer-rebind semantics explicitly (assignment and emplace rebind rather than assign through the old referent) and the const-non-propagation property via static_assert on decltype. Assisted-by: Claude:claude-sonnet-5
Construction and assignment from optional<U> read the source through its public has_value()/operator* rather than expected's friend-cast into the private base: optional<U&> sources have a differently-shaped base, and a helper returning the base by value gets no copy elision in a delegating constructor's mem-initializer, so a _from_optional_t tag constructor constructs the union member in place instead. Assisted-by: Claude:claude-sonnet-5
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-fable-5
Fixes a cpp:S5018 BLOCKER on _expected_base's move constructor. None of the added specs can lie: the unions' defaulted copy/move overloads are constrained to trivially copy/move constructible contents, where the implicit specification is already noexcept(true); the bases' defaulted copy/move are non-deleted only when the union's trivial-only overload is, so wherever they are callable they are trivial and cannot throw. The non-trivial paths construct through the (bool, S&&) tag constructor instead, which keeps its own conditional noexcept.
2f8ade9 to
4769dba
Compare
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-fable-5
|
Also restrict _is_valid_optional to lvalue reference types per [optional.optional.general]; transform's result-type Mandates relies on it. Assisted-by: Claude:claude-fable-5
|
augment review |
🤖 Augment PR SummarySummary: Adds a C++26-oriented Changes:
Technical Notes: The optional implementation uses a union+discriminant for value optionals and a pointer representation for 🤖 Was this summary useful? React with 👍 or 👎 |
| @@ -0,0 +1,1441 @@ | |||
| // Copyright (c) 2025 Bronek Kozicki | |||
There was a problem hiding this comment.
include/pfn/optional.hpp:1: This file is newly added in this PR but the copyright header says 2025; repo guidance expects the current year (2026) for new files. (Rule: CLAUDE.md)
Other locations where this applies: tests/pfn/optional.cpp:1.
Severity: low
Other Locations
tests/pfn/optional.cpp:1
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.



Delaying range support to a follow-up PR.