P0792R14: function_ref: a type-erased callable reference

P0792R14 is a proposal for C++26 that introduces std::function_ref, a vocabulary type with reference semantics for passing callable objects.

Motivation

The higher-order function pattern is common in C++, such as the comparator of std::ranges::sort or the predicate of std:::views::filter. There are multiple methods to accept a higher-order function as a function parameter:

  • Function pointer: This approach has the minimum overhead, but it doesn't support function objects.
  • Template parameter: This approach supports all callable objects, but it requires the implementation to constraint the signature (with std:is_invocable_v, for example) and convert the function to a function template.
  • std::function or std::move_only_function: This approach supports all callable objects, but it requires them to be copy-constructible or move-constructible, respectively. The call wrappers start to allocate the callable objects when they can't fit in a small buffer.

The proposed function_ref supports all callable objects regardless of whether they are copy-constructible or move-constructible. It has minimum overhead, as it avoids allocations and exceptions. Moreover, certain calling conventions can pass its instances in registers.

Design

template <typename R, typename... ArgTypes> class function_ref;

template <typename R, typename... ArgTypes>
class function_ref<R(ArgTypes...) FUNCTION_REF_CV noexcept(FUNCTION_REF_NOEXCEPT)>;

The <functional> header provides a partial specialization of function_ref for each combination of the qualifiers on the function type:

  • FUNCTION_REF_CV is either empty or const.
  • FUNCTION_REF_NOEXCEPT is either true or false.

Each specialization of function_ref is a trivially copyable type that models copyable. Because function_ref has the reference semantic, its operator() is const-qualified and it enforces the callable object to be lvalue-callable.

std::function_ref can be constructed from a function or a function object. However, it cannot be constructed from a pointer to a member function or a pointer to a data member. This limitation exists because pointers to member functions have a different representation than regular function pointers, requiring at least twice the space in Clang. Including support for member function pointers would increase the size of std::function_ref.

template <typename R, typename... ArgTypes> class function_ref {
  template <class F> function_ref(F *f) noexcept;
  template <class F> constexpr function_ref(F &&f) noexcept;
};

Since the representation of pointers to member functions or data members is known at compile time, they can be accepted as non-type template parameters, meaning they won't occupy any storage. To support this, std::function_ref provides constructors that accept a specialization of std::nontype_t, which wraps a non-type template parameter, along with an optional object to which the member pointer will be bound, effectively converting it into a regular function.

template <typename R, typename... ArgTypes> class function_ref {
  template <auto f> constexpr function_ref(nontype_t<f>) noexcept;
  template <auto f, class U>
  constexpr function_ref(nontype_t<f>, U &&obj) noexcept;
  template <auto f, class T>
  constexpr function_ref(nontype_t<f>, FUNCTION_REF_CV T *obj) noexcept;
};