Document number P0792R3
Date 2018-10-07
Reply-to Vittorio Romeo <>
Audience Library Working Group (LWG)
Project ISO JTC1/SC22/WG21: Programming Language C++

function_ref: a non-owning reference to a Callable

Abstract

This paper proposes the addition of function_ref<R(Args...)> to the Standard Library, a “vocabulary type” for non-owning references to Callable objects.

Changelog and polls

From R2 to R3

Changes

Polls

Do we want to remove the precondition that !f must hold when function_ref is constructed/assigned from an instance of std::function f?

SF F N A SA

1 8 1 0 0

Should function_ref::operator() be unconditionally const-qualified?

SF F N A SA

8 2 0 1 0

Should function_ref fully support the Callable concept at the potential cost of sizeof(function_ref) > sizeof(void(*)()) * 2?

SF F N A SA

6 4 0 0 0

From R1 to R2

Changes

Polls

We want to prevent construction of std::function from std::function_ref (but not other callable-taking things like std::bind).

SF F N A SA

0 0 4 8 0

We want to revise the paper to include discussion of ref-qualified callables.

SF F N A SA

0 3 6 6 0

Forward paper as-is to LWG for C++20?

SF F N A SA

3 9 3 0 0

From R0 to R1

Changes

Semantics: pointer versus reference

option 1

function_ref, non-nullable, not default constructible

option 2

function_ptr, nullable, default constructible

We want 1 and 2

SF F N A SA

1 2 8 3 6

ref vs ptr

SR R N P SP

6 5 2 5 0

The poll above clearly shows that the desired direction for function_ref is towards a non nullable, non default-constructible reference type. This revision (P0792R2) removes the “empty state” and default constructibility from the proposed function_ref. If those semantics are required by users, they can trivially wrap function_ref into an std::optional<function_ref</* ... */>>.

target and target_type

We want target and target-type (consistent with std::function) if they have no overhead

Unanimous consent

We want target and target-type (consistent with std::function) even though they have overhead

SF F N A SA

0 0 1 9 4

I am not sure whether target and target_type can be implemented without introducing overhead. I seek the guidance of the committee or any interested reader to figure that out. If they require overhead, I agree with the poll: they will be left out of the proposal.

Table of contents

Overview

Since the advent of C++11 writing more functional code has become easier: functional programming patterns and idioms have become powerful additions to the C++ developer’s toolbox. “Higher-order functions” are one of the key ideas of the functional paradigm - in short, they are functions that take functions as arguments and/or return functions as results.

The need of referring to an existing Callable object comes up often when writing functional C++ code, but the Standard Library unfortunately doesn’t provide a flexible facility that allows to do so. Let’s consider the existing utilities:

This paper proposes the introduction of a new function_ref class template, which is akin to std::string_view. This paper describes function_ref as a non-owning lightweight wrapper over any Callable object.

Motivating example

Here’s one example use case that benefits from higher-order functions: a retry(n, f) function that attempts to synchronously call f up to n times until success. This example might model the real-world scenario of repeatedly querying a flaky web service.

struct payload { /* ... */ };

// Repeatedly invokes `action` up to `times` repetitions.
// Immediately returns if `action` returns a valid `payload`.
// Returns `std::nullopt` otherwise.
std::optional<payload> retry(std::size_t times, /* ????? */ action);

The passed-in action should be a Callable which takes no arguments and returns std::optional<payload>. Let’s see how retry can be implemented with various techniques:

Impact on the Standard

This proposal is a pure library extension. It does not require changes to any existing part of the Standard.

Alternatives

The only existing viable alternative to function_ref currently is std::function + std::reference_wrapper. The Standard guarantees that when a std::reference_wrapper is used to construct/assign to a std::function no allocations will occur and no exceptions will be thrown.

Using std::function for non-owning references is suboptimal for various reasons.

  1. The ownership semantics of a std::function are unclear - they change depending on whether or not the std::function was constructed/assigned with a std::reference_wrapper.

  2. This technique doesn’t work with temporaries. This is a huge drawback as it prevents stateful temporary lambdas from being passed as callbacks.

    The code above doesn’t compile, as std::ref only accepts non-const lvalue references (additionally, std::cref is explicitly deleted for rvalue references). Avoiding the use of std::ref breaks the guarantee that f won’t allocate or throw an exception on construction.

  3. std::function is harder for compilers to optimize compared to the proposed function_ref. This is true due to various reasons:

    Rough benchmarks comparing the generated assembly of a std::function parameter and a function_ref parameter against a template parameter show that:

    A description of the benchmarking techniques used and the full results can be found on my article “passing functions to functions” 1.

Synopsis

namespace std
{
    template <typename Signature>
    class function_ref
    {
        void* object; // exposition only

        R(*erased_function)(Args...) qualifiers; // exposition only
        // `R`, `Args...`, and `qualifiers` are the return type, the parameter-type-list,
        // and the sequence "cv-qualifier-seq-opt noexcept-specifier-opt" of the function
        // type `Signature`, respectively.

    public:
        constexpr function_ref(const function_ref&) noexcept = default;

        template <typename F>
        constexpr function_ref(F&&);

        constexpr function_ref& operator=(const function_ref&) noexcept = default;

        template <typename F>
        constexpr function_ref& operator=(F&&);

        constexpr void swap(function_ref&) noexcept;

        R operator()(Args...) const noexcept-qualifier;
        // `R`, `Args...`, and `noexcept-qualifier` are the return type, the parameter-type-list,
        // and the sequence "noexcept-specifier-opt" of the function type `Signature`,
        // respectively.
    };

    template <typename Signature>
    constexpr void swap(function_ref<Signature>&, function_ref<Signature>&) noexcept;

    template <typename R, typename... Args>
    function_ref(R (*)(Args...)) -> function_ref<R(Args...)>;

    template <typename R, typename... Args>
    function_ref(R (*)(Args...) noexcept) -> function_ref<R(Args...) noexcept>;

    template <typename F>
    function_ref(F&&) -> function_ref<see below>;
}

The template argument Signature shall be a non-volatile-qualified function type.

<functional> header

Add the following to [functional.syn]:

namespace std
{
    // ...

    template <typename Signature> class function_ref;

    template <typename Signature>
    constexpr void swap(function_ref<Signature>& lhs, function_ref<Signature>& rhs) noexcept;

    // ...
}

Specification

constexpr function_ref(const function_ref& rhs) noexcept = default;


template <typename F>
constexpr function_ref(F&& f);


constexpr function_ref& operator=(const function_ref& rhs) noexcept = default;


template <typename F>
constexpr function_ref& operator=(F&&);


constexpr void swap(function_ref& rhs) noexcept;


R operator()(Args... xs) qualifiers;


template <typename F>
function_ref(F&&) -> function_ref<see below>;


template <typename Signature>
constexpr void swap(function_ref<Signature>& lhs, function_ref<Signature>& rhs) noexcept;


Feature test macro

I propose the feature-testing macro name __cpp_lib_function_ref.

Example implementation

An example implementation is available here on GitHub.

Existing practice

Many facilities similar to function_ref exist and are widely used in large codebases. Here are some examples:

Additionally, combining results from GitHub searches (excluding “llvm” and “folly”) for “function_ref10, “function_view11, “FunctionRef12, and “FunctionView13 roughly shows more than 2800 occurrences.

Possible issues

Accepting temporaries in function_ref’s constructor is extremely useful in the most common use case: using it as a function parameter. E.g.

void foo(function_ref<void()>);

int main()
{
    foo([]{ });
}

The usage shown above is completely safe: the temporary closure generated by the lambda expression is guarantee to live for the entirety of the call to foo. Unfortunately, this also means that the following code snippet will result in undefined behavior:

int main()
{
    function_ref<void()> f{[]{ }};
    // ...
    f(); // undefined behavior
}

The above closure is a temporary whose lifetime ends after the function_ref constructor call. The function_ref will store an address to a “dead” closure - invoking it will produce undefined behavior 14. As an example, AddressSanitizer detects an invalid memory access in this gist 15. Note that this problem is not unique to function_ref: the recently standardized std::string_view 16 has the same problem 17.

I strongly believe that accepting temporaries is a “necessary evil” for both function_ref and std::string_view, as it enables countless valid use cases. The problem of dangling references has been always present in the language - a more general solution like Herb Sutter and Neil Macintosh’s lifetime tracking 18 would prevent mistakes without limiting the usefulness of view/reference classes.

Bikeshedding

The name function_ref is subject to bikeshedding. Here are some other potential names:

Acknowledgments

Thanks to Agustín Bergé, Dietmar Kühl, Eric Niebler, Tim van Deurzen, and Alisdair Meredith for providing very valuable feedback on earlier drafts of this proposal.

Annex: previously open questions

References


  1. https://vittorioromeo.info/index/blog/passing_functions_to_functions.html#benchmark---generated-assembly

  2. http://llvm.org/doxygen/classllvm_1_1function__ref_3_01Ret_07Params_8_8_8_08_4.html

  3. https://github.com/search?q=org%3Allvm-mirror+function_ref&type=Code

  4. https://github.com/facebook/folly

  5. https://github.com/facebook/folly/blob/master/folly/Function.h#L743-L824

  6. https://github.com/search?q=org%3Afacebook+FunctionRef&type=Code

  7. https://www.gnu.org/software/gdb/

  8. https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=gdb/common/function-view.h

  9. https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=gdb/common/function-view.h

  10. https://github.com/search?utf8=%E2%9C%93&q=function_ref+AND+NOT+llvm+AND+NOT+folly+language%3AC%2B%2B&type=Code

  11. https://github.com/search?utf8=%E2%9C%93&q=function_view+AND+NOT+llvm+AND+NOT+folly+language%3AC%2B%2B&type=Code

  12. https://github.com/search?utf8=%E2%9C%93&q=functionref+AND+NOT+llvm+AND+NOT+folly+language%3AC%2B%2B&type=Code

  13. https://github.com/search?utf8=%E2%9C%93&q=functionview+AND+NOT+llvm+AND+NOT+folly+language%3AC%2B%2B&type=Code

  14. http://foonathan.net/blog/2017/01/20/function-ref-implementation.html

  15. https://gist.github.com/SuperV1234/a41eb1c825bfbb43f595b13bd4ea99c3

  16. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3762.html

  17. http://foonathan.net/blog/2017/03/22/string_view-temporary.html

  18. https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Lifetimes%20I%20and%20II%20-%20v0.9.1.pdf

  19. http://wg21.link/p0045r1

  20. http://wg21.link/N4159