c++functionstandardsdangling-pointerc++26

Why does std::function_ref allow passing in an expiring functor, rather than disallow it?


At https://en.cppreference.com/w/cpp/utility/functional/function_ref/function_ref.html, there is an overloaded ctor as follows:

template<class F>
function_ref(F&& f) noexcept;
  • Initializes bound-entity with std::addressof(f), and thunk-ptr with the address of a function thunk.
    • Let T be std::remove_reference_t<F>. This overload participates in overload resolution only if :
      • std::remove_cvref_t<F> is not the same type as function_ref,
      • std::is_member_pointer_v<T> is false, and
      • /\*is-invocable-using\*/</\*cv\*/ T&> is true.

It seems that the ctor allows the caller to pass in an expiring functor and uses the dangling reference to invoke the DEAD functor. The code below can demonstrate the issue.

#include <functional>

std::function_ref<int(int)> f() {
    auto       n  = 1;
    auto const fn = [n](int m) { return n + m; };
    return std::function_ref<int(int)>(std::move(fn)); // eligible for ctor 2.
}

int main() {
    auto fn_ref = f();
    // fn_ref is now referencing to a dead functor! 
    return fn_ref(2); // BANG! 
}

Compare it with std::ref and std::cref which are well designed for the same issue:

template<class T>
void ref(const T&&) = delete;

template<class T>
void cref(const T&&) = delete;

I just wonder:

Why does the standard not just reject the functor argument if it is an rvalue-reference (including && and const&&)?


Solution

  • The primary reason that std::function_ref was added was to be used as a function argument[1]:

    extern int do_something(std::function_ref<int(int)>);
    

    In this case, it entirely makes sense to pass just a reference to a temporary. Any expression involving a call to do_something will extend the lifetime of any temporary used to construct the function_ref to at least until the function call ends, so the reference will be valid:

    int x = do_something([n](int m) { return n + m; });
    // function_ref holds a rerference to a temporary with closure type whose lifetime ends after x is initialised, after `do_something` returns.
    

    This exact same foot-gun manifests with const references and std::initializer_list. As with initializer_list, just don't use function_ref unless it's the exact situation of using it for a parameter that you don't store beyond the scope of the function.