Skip to content

Reject binding reference payloads to temporaries #447

Description

@Bronek

fn::optional<T&> and fn::just<T&> (PR #437) accept a temporary and dangle:

fn::just<int const &> j{42};     // compiles, j refers to a destroyed int
fn::optional<int const &> o{42}; // same
fn::optional<int const &> m = fn::optional<int>{1}; // same, through the converting constructor

C++26 optional<T&> deletes such constructors using std::reference_constructs_from_temporary_v, a C++23 trait: [optional.ref.ctor] deletes the constructors from U&& and from every optional<U> value category when the trait is true, and constrains the in_place_t constructor and emplace ([optional.ref.assign]) on it being false. A polyfill pfn::reference_constructs_from_temporary needs to be created, based on the compiler builtin __reference_constructs_from_temporary (or similar, depending on compiler).

The check applies to every carrier holding a reference: pfn::optional<T&>, fn::optional<T&>, fn::just<T&>, and copack reference alternatives once those are admitted (#448).

Builtin availability

__reference_constructs_from_temporary(T, U) works in C++20 mode where it exists. Checked on Compiler Explorer against nine cases with known answers (prvalue, conversion, conversion function to a value and to an lvalue, lvalue, xvalue, derived-to-base lvalue and prvalue, non-reference T):

Compiler Builtin Matches the trait
GCC 12 no —
GCC 13 and later yes all nine cases
Clang 19 and later yes all nine cases
MSVC, Visual Studio 2022 (17.x) no —
MSVC, Visual Studio 18.0 to 18.2 no —
MSVC, Visual Studio 18.6 and later yes all nine cases (latest)
Apple Clang 21 untested —

Clang's older __reference_binds_to_temporary is not a substitute: Clang 19 and 20 answer it differently from the trait for a prvalue of the referenced type (int const& from int) and for a derived prvalue (B const& from D). GCC 12 has neither builtin, and MSVC's standard library exposes std::reference_constructs_from_temporary only in versions that have the builtin.

Best effort without the builtin

Release 0.2.0 supports GCC 12 and Visual Studio 2022, so pfn falls back to a library emulation there.

The trait shapes the interface: it constrains overloads and deletes constructors, so its answer is visible through is_constructible, is_convertible, concepts and overload resolution. An error in each direction has a different cost:

  • A false positive (reporting a temporary where none is created) rejects valid code on the older compilers only. That breaks the interface and is not acceptable.
  • A false negative (missing a temporary) leaves a dangling construction accepted, which is the behaviour without any check.

The emulation therefore errs only towards false negatives: it reports a temporary only where reference binding rules make one certain, and reports none otherwise. Certain cases include a scalar prvalue source, a class prvalue of the referenced or a derived type, a scalar source that is not reference-compatible with the referenced type, and a non-class source converted by the referenced class type's constructor. The emulation cannot see through conversion functions, so a class source that is not reference-compatible is reported as creating no temporary. The documentation of the polyfill states this.

On compilers with the builtin, a test asserts over a matrix of cases that whenever the emulation reports a temporary, the builtin does too; the same matrix records the cases the emulation misses.

Assisted-by: Claude:claude-opus-5-5

Activity

  1. added
    bugSomething isn't working
    release-0.2Planned for release 0.2
    on Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingrelease-0.2Planned for release 0.2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions