This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4264. Skipping indirection is not allowed for function_ref

Section: 22.10.17.1 [func.wrap.general] Status: New Submitter: Tomasz Kamiński Opened: 2025-05-15 Last modified: 2025-05-26

Priority: Not Prioritized

View all issues with New status.

Discussion:

Currently the wording in 22.10.17.1 [func.wrap.general] allows implementation to avoid double indirection when constructing owning functions wrappers from another one:

-2- Let t be an object of a type that is a specialization of function, copyable_function, or move_only_function, such that the target object x of t has a type that is a specialization of function, copyable_function, or move_only_function. Each argument of the invocation of x evaluated as part of the invocation of t may alias an argument in the same position in the invocation of t that has the same type, even if the corresponding parameter is not of reference type.

However, the wording does not cover a function_ref, disallowing implementation to perform this optimization when signatures are compatible, for example:

std::function_ref<void() noexcept> f1(ptr);
std::function_ref<void()> f1(f2);

We should include function_ref in the list. Note that this allows, but does not require, an implementation to perform such an optimization. As a consequence, it is acceptable to specify the allowance for all combinations of polymorphic wrappers, even for creating an owning wrapper from a non-owning one, where implementing such an optimization may not be possible.

Previous resolution [SUPERSEDED]:

This wording is relative to N5008.

  1. Modify 22.10.17.1 [func.wrap.general] as indicated:

    -2- Let t be an object of a type that is a specialization of function, copyable_function, or move_only_function, or function_ref, such that the target object x of t has a type that is a specialization of function, copyable_function, ormove_only_function, or function_ref. Each argument of the invocation of x evaluated as part of the invocation of t may alias an argument in the same position in the invocation of t that has the same type, even if the corresponding parameter is not of reference type.

[2024-05-21; Tomasz's comment and upates proposed resolution]

After implementing double indirection avoidance in the libstdc++, I have realized that above wording change is insufficient to cover all user observable effects of the change. Revelant quote from the Avoid double indirection in function_ref from libstdc++ mailing lists:

To avoidance of double indirection requires that constructed function_ref, refers directly to the target function of the source, instead of source, and this is visible after the assigment:

void foo() noexcept;
void bar() noexcept;

std::function_ref<void() noexcept> sr(&foo);
std::function_ref<void()> dr(sr);
dr(); // calls foo regardless of implementation

sr = &bar;
sr(); // calls bar
dr(); // still calls foo if we avoid indirection,
      // calls bar if we do not

Similary for move_only_function/copyable_function source:

std::move_only_function<void()> sm;
std::function_ref<void()> dm(sm);

dm(); // UB because sm is empty

sm = &foo;

dm(); // remains UB if we avoid indirection,
      // calls bar if we do not.

While we may want to allow skipping indirection for function_ref, as this produces same behavior as in case for copy constructor (matching signatures):

void foo() noexcept;
void bar() noexcept;

std::function_ref<void() noexcept> sr(&foo);
std::function_ref<void() noexcept> dr(sr); // copy-cosntructor
dr(); // calls foo regardless of implementation

sr = &bar;
sr(); // calls bar
dr(); // still calls foo if we avoid indirection

I do not think this is acceptable for move_only_function. …

Note that for the same reason, implementations are not free to avoid dangling when constructing function_ref from reference_wrapper:

auto srw = std::ref(&foo);
std::function_ref<void()> drw(srw);
drw(); // calls foo

srw = std::ref(&bar);
drw(); // calls foo if we unwrap referenc wrapper,
       // calls bar otherwise.

Note that this is limited to function_ref due reference nature of this wrapper.

The updated resolution allows indirection but making it unspecified if function_ref constructed from other function_ref specialization, will refer to source object or its target.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 22.10.17.1 [func.wrap.general] as indicated:

    -2- Let t be an object of a type that is a specialization of function, copyable_function, or move_only_function, or function_ref, such that the target object x of t has a type that is a specialization of function, copyable_function, ormove_only_function, or function_ref. Each argument of the invocation of x evaluated as part of the invocation of t may alias an argument in the same position in the invocation of t that has the same type, even if the corresponding parameter is not of reference type.

  2. Modify 22.10.17.6.3 [func.wrap.ref.ctor] as indicated:

    template<class F> constexpr function_ref(F&&) noexcept;
    
    […]

    -7- Effects: Initializes bound-entity with addressof(f) and thunk-ptr with the address of a function thunk such that thunk(bound-entity, call-args...) is expression-equivalent (3.22 [defns.expression.equivalent]) to invoke_r<R>(static_cast<cv T&>(f), call-args...).

    -?- Remarks: If remove_cveref_t<F> is a specialization of function_ref an implementation may initialize bound-entity with bound-entity of f. [Example::

    void f1() noexcept;
    void f2() noexcept;
    
    function_ref<void() noexcept> r1(&r1);
    function_ref<void()> r2(r1);
    r1 = &f2;
    f2(); // it is unspecified if f1 or f2 is invoked
    

    end example]