Skip to content

pack reference elements bind to temporaries #463

Open

Description

@Bronek

fn::pack is an aggregate, so brace initialization can bind a reference element to a temporary: fn::pack<int const &>{42} compiles. For a named local initialized this way, the temporary lives as long as the pack. Returning the pack does not extend the temporary's lifetime, so the returned pack's element dangles.

#include <fn/pack.hpp>

template <typename T, typename... Args>
concept brace_constructible = requires(Args &&...args) { T{static_cast<Args &&>(args)...}; };
template <typename T, typename... Args>
concept spelled_lift = requires(Args &&...args) { fn::as_pack<T>(static_cast<Args &&>(args)...); };

// pack's braces bind a reference element to a temporary ...
static_assert(brace_constructible<fn::pack<int const &>, int>);
// ... which the spelled lift refuses, binding only an lvalue
static_assert(not spelled_lift<int const &, int>);
static_assert(spelled_lift<int const &, int &>);

// Compiles; the int dies when make returns, so reading the element of make() is a use-after-return.
auto make() -> fn::pack<int const &> { return fn::pack<int const &>{42}; }

int main() {}

Reading the element of make() is reported by AddressSanitizer as stack-use-after-return (gcc 16, with ASAN_OPTIONS=detect_stack_use_after_return=1).

Other reference APIs reject temporaries, following C++26 optional<T&>, reference alternatives in copack and choice (#451), as_pack with explicit element types, and append(std::in_place_type<T&>, ...). Brace initialization of pack still accepts them.

To reject temporaries, reference elements must be initialized through constructors. pack could gain constructors, or store reference elements behind a constructor that rejects temporaries, as copack does for reference alternatives. Either approach needs to account for aggregate initialization, structured bindings, triviality and constexpr.

This issue also blocks deprecating as_pack. #451 deprecates as_copack and as_choice in favour of naming the type, whose constructors refuse a temporary; the same advice for as_pack would replace as_pack<int const &>(x) with pack<int const &>{x}, which accepts what the lift refuses.

Related: #447.

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

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions