Title
Skipping indirection is not allowed for function_ref
Status
new
Section
[func.wrap.general]
Submitter
Tomasz Kamiński

Created on 2025-05-15.00:00:00 last changed 2 weeks ago

Messages

Date: 2025-05-26.15:54:36

Proposed resolution:

This wording is relative to N5008.

  1. Modify [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 [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 ([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]

Date: 2024-05-15.00:00:00

[ 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.

Date: 2025-05-21.10:16:44

Currently the wording in [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.

This wording is relative to N5008.

  1. Modify [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.

History
Date User Action Args
2025-05-21 10:16:44adminsetmessages: + msg14764
2025-05-18 11:32:02adminsetmessages: + msg14763
2025-05-15 00:00:00admincreate