| Doc. no.: | P0792R11 |
| Date: | 2022-09-12 |
| Audience: | LWG |
| Reply-to: | Vittorio Romeo <vittorio.romeo@outlook.com>
Zhihao Yuan <zy@miator.net>
Jarrad Waterloo <descender76@gmail.com> |
function_ref: a type-erased callable reference
Table of contents
Changelog
R11
- Fix oversights and typos in the wording.
R10
R9
- Declare the main template as variadic for future extension;
- Allow declaring a variable of
function_ref constinit.
R8
- Stop supporting pointer-to-members;
- Prevent assigning from callable objects other than function pointers while retaining copy assignment;
- Consolidate the wording with better terminologies.
R7
- Clarify the proposal to handle function and function pointers in the same way.
R6
- Avoid double-wrapping existing references to callables;
- Reworked the wording to follow the latest standardese;
- Applied changes requested by LWG (2020-07);
- Removed a deduction guide that is incompatible with explicit object parameters.
R5
- Removed “qualifiers” from
operator() specification (typo);
R4
- Removed
constexpr due to implementation concerns;
- Explicitly say that the type is trivially copyable;
- Added brief before synopsis;
- Reworded specification following P1369.
R3
- Constructing or assigning from
std::function no longer has a precondition;
function_ref::operator() is now unconditionally const-qualified.
R2
- Made copy constructor and assignment operator
= default;
- Added exposition only data members.
R1
- Removed empty state, comparisons with
nullptr, and default constructor;
- Added support for
noexcept and const-qualified function signatures;
- Added deduction guides similar to
std::function;
- Added example implementation;
- Added feature test macro;
- Removed
noexcept from constructor and assignment operator.
Abstract
This paper proposes the addition of function_ref<R(Args...)>, a vocabulary type with reference semantics for passing entities to call, to the standard library.
Motivating examples
Here’s one example use case that benefits from higher-order functions: a retry(n, f) function that attempts to call f up to n times synchronously until success. This example might model the real-world scenario of repeatedly querying a flaky web service.
using payload = std::optional< >;
payload retry(size_t times, action);
The passed-in action should be a callable entity that takes no arguments and returns a payload. Let’s see how to implemented retry with various techniques.
Using function pointers
payload retry(size_t times, payload(*action)())
{
}
-
Advantages:
-
Easy to implement: no template, nor constraint. The function pointer type has a signature that nails down which functions to pass.
-
Minimal overhead: no allocations, no exceptions, and major calling conventions can pass a function pointer in a register.
-
Drawbacks:
- A function is usually not stateful, nor does captureless closure objects. One cannot pass other function objects in C++ this way.
Using a template
template<class F>
auto retry(size_t times, F&& action)
requires std::is_invocable_r_v<payload, F>
{
}
-
Advantages:
-
Support arbitrary function objects, such as closures with captures.
-
Zero-overhead: no allocations, no exceptions, no indirections.
-
Drawbacks:
-
Harder to implement: users must constrain action’s signature.
-
Fail to support separable compilation: the implementation of retry must appear in a header file. A slight change at the call site will cause recompilation of the function body.
Using std::function or std::move_only_function
payload retry(size_t times, std::move_only_function<payload()> action)
{
}
-
Advantages:
-
Take more callable objects, from closures to pointer-to-members.
-
Easy to implement: no need to use a template or any explicit constraint. std::function and std::move_only_function constructor is constrained.
-
Drawbacks:
-
std::function and std::move_only_function’s converting constructor require their target objects to be copy-constructible or move-constructible, respectively;
-
Comes with potentially significant overhead:
-
The call wrappers start to allocate the target objects when they do not fit in a small buffer, introducing more indirection when calling the objects.
-
No calling conventions can pass these call wrappers in registers.
-
Modern compilers cannot inline these call wrappers, often resulting in inferior codegen then previously mentioned techniques.
One rarely known technique is to pass callable objects to call wrappers via a std::reference_wrapper:
auto result = retry(3, std::ref(downloader));
But users cannot replace the downloader in the example with a lambda expression as such an expression is an rvalue. Meanwhile, all the machinery that implements type-erased copying or moving must still be present in the codegen.
Using the proposed function_ref
payload retry(size_t times, function_ref<payload()> action)
{
}
-
Advantages:
-
Takes any callable objects regardless of whether they are constructible.
-
Easy to implement: no need to use a template or any constraint. function_ref is constrained.
-
Clean ownership semantics: function_ref has reference semantics as its name suggests.
-
Minimal overhead: no allocations, no exceptions, certain calling conventions can pass function_ref in registers.
- Modern compilers can perform tail-call optimization when generating thunks. If the function body is visible, they can deliver optimal codegen, identical to the template solution.
Design considerations
This paper went through LEWG at R5, with a number of consensuses reached and applied to the wording:
- Do not provide
target() or target_type;
- Do not provide
operator bool, default constructor, or comparison with nullptr;
- Provide
R(Args...) noexcept specializations;
- Provide
R(Args...) const specializations;
- Require the target entity to be Lvalue-Callable;
- Make
operator() unconditionally const;
- Choose
function_ref as the right name.
One design question remains not fully understood by many: how should a function pointer initialize function_ref?
In a typical scenario, there is no lifetime issue no matter whether the download entity below is a function, a function pointer, or a closure:
auto result = retry(3, download);
However, even if the users use function_ref only as parameters initially, it’s not uncommon to evolve the API by grouping parameters into structures,
struct retry_options
{
size_t times;
function_ref<payload()> action;
seconds step_back;
};
payload retry(retry_options);
auto result = retry({.times = 3,
.action = download,
.step_back = 1.5s});
and structures start to need constructors or factories to simplify initialization:
auto opt = default_strategy();
opt.action = download;
auto result = retry(opt);
According to the P0792R5 wording, the code has well-defined behavior if download is a function. However, one cannot write the code as
auto opt = default_strategy();
opt.action = &download;
auto result = retry(opt);
since this will create a function_ref with a dangling object pointer that points to a temporary object – the function pointer.
In other words, the following code also has undefined behavior:
auto opt = default_strategy();
opt.action = ssh.get_download_callback();
auto result = retry(opt);
The users have to write the following to get well-defined behavior.
auto opt = default_strategy();
opt.action = *ssh.get_download_callback();
auto result = retry(opt);
Survey
We collected the following function_ref implementations available today:
■llvm::function_ref – from LLVM
■tl::function_ref – by Sy Brand
■folly::FunctionRef – from Meta
■gdb::function_view – from GNU
■type_safe::function_ref – by Jonathan Müller
■absl::function_ref – from Google
They have diverging behaviors when initialized from function pointers:
Behavior A.1: Stores a function pointer if initialized from a function, stores a pointer to function pointer if initialized from a function pointer
| Outcome | Library |
|
Undefined:
opt.action = ssh.get_download_callback();
Well-defined:
opt.action = download;
|
■llvm::function_ref
■tl::function_ref
|
Behavior A.2: Stores a function pointer if initialized from a function or a function pointer
| Outcome | Library |
|
Well-defined:
opt.action = ssh.get_download_callback();
Well-defined:
opt.action = download;
|
■folly::FunctionRef
■gdb::function_view
■type_safe::function_ref
■absl::function_ref
|
P0792R5 wording gives Behavior A.1.
A related question is what happens when initialized from pointer-to-members. In the following tables, assume &Ssh::connect is a pointer to member function:
Behavior B.1: Stores a pointer to pointer-to-member if initialized from a pointer-to-member
| Outcome | Library |
|
Well-defined:
lib.send_cmd(&Ssh::connect);
Undefined:
function_ref<void(Ssh&)> cmd = &Ssh::connect;
|
■tl::function_ref
■folly::FunctionRef
■absl::function_ref
|
Behavior B.2: Only supports callable entities with function call expression
| Outcome | Library |
|
Ill-formed:
lib.send_cmd(&Ssh::connect);
Ill-formed:
function_ref<void(Ssh&)> cmd = &Ssh::connect;
Well-defined:
lib.send_cmd(std::mem_fn(&Ssh::connect));
|
■llvm::function_ref
■gdb::function_view
■type_safe::function_ref
|
P0792R5-R7 wording gives Behavior B.1.
P2472 “make function_ref more functional” suggests a way to initialize function_ref from pointer-to-members without dangling in all contexts:
function_ref<void(Ssh&)> cmd = nontype<&Ssh::connect>;
Not convertible from pointer-to-members means that function_ref does not need to use invoke_r in the implementation, improving debug codegen in specific toolchains with little effort.
Making function_ref large enough to fit a thunk pointer plus any pointer-to-member-function may render std::function_ref irrelevant in the real world. Some platform ABIs can pass a trivially copyable type of a 2-word size in registers and cannot do the same to a bigger type. Here is some LLVM IR to show the difference:
https://godbolt.org/z/Ke3475vz8.
For good or bad, the expression &qualified-id that retrieves pointer-to-member shares grammar with the expression that gets a pointer to explicit object member functions. [expr.unary.op/3]
Design decisions
-
Behavior A.2 is incorporated to eliminate the difference between initializing function_ref from a function and initializing function_ref from a function pointer.
-
Behavior B.2 is incorporated to reduce potential damage at a small cost. This change will reflect on the constructor’s constraints.
-
LEWG further requested making converting assignment from anything other than functions and function pointers ill-formed. Note that function_ref will still be copy-assignable.
-
LEWG achieved consensus on P2472R3, and the wording is merged here. This change brings back the pointer-to-member support in a different form.
Wording
The wording is relative to N4910.
Add new templates to [utility.syn], header <utility> synopsis, after in_place_index_t and in_place_index:
namespace std {
[...]
// nontype argument tag
template<auto V>
struct nontype_t {
explicit nontype_t() = default;
};
template<auto V> inline constexpr nontype_t<V> nontype{};
}
Add the template to [functional.syn], header <functional> synopsis:
[…]
// [func.wrap.move], move only wrapper
template<class... S> class move_only_function; // not defined
template<class R, class... ArgTypes>
class move_only_function<R(ArgTypes...) cv ref noexcept(noex)>; // see below
// [func.wrap.ref], non-owning wrapper
template<class... S> class function_ref; // not defined
template<class R, class... ArgTypes>
class function_ref<R(ArgTypes...) cv noexcept(noex)>; // see below
[…]
Create a new section “Non-owning wrapper”, [func.wrap.ref] with the following:
General
[func.wrap.ref.general]
The header provides partial specializations of function_ref for each combination of the possible replacements of the placeholders cv and noex where:
- cv is either
const or empty.
- noex is either
true or false.
Class template function_ref
[func.wrap.ref.class]
namespace std
{
template<class... S> class function_ref; // not defined
template<class R, class... ArgTypes>
class function_ref<R(ArgTypes...) cv noexcept(noex)>
{
public:
// [func.wrap.ref.ctor], constructors and assignment operators
template<class F> function_ref(F*) noexcept;
template<class F> constexpr function_ref(F&&) noexcept;
template<auto f> constexpr function_ref(nontype_t<f>) noexcept;
template<auto f, class U>
constexpr function_ref(nontype_t<f>, U&&) noexcept;
template<auto f, class T>
constexpr function_ref(nontype_t<f>, cv T*) noexcept;
constexpr function_ref(const function_ref&) noexcept = default;
constexpr function_ref& operator=(const function_ref&) noexcept = default;
template<class T> function_ref& operator=(T) = delete;
// [func.wrap.ref.inv], invocation
R operator()(ArgTypes...) const noexcept(noex);
private:
template<class... T>
static constexpr bool is-invocable-using = see below; // exposition only
};
// [func.wrap.ref.deduct], deduction guides
template<class F>
function_ref(F*) -> function_ref<F>;
template<auto f>
function_ref(nontype_t<f>) -> function_ref<see below>;
template<auto f>
function_ref(nontype_t<f>, auto) -> function_ref<see below>;
}
An object of class function_ref<R(Args...) cv noexcept(noex)> stores a pointer to thunk thunk-ptr and a bound entity bound-entity.
The bound entity has an implementation-defined type BoundEntityType.
BoundEntityType is trivially copyable and models copyable.
BoundEntityType is capable of storing a pointer to object value, a pointer to function value, an unused value, or a null pointer value.
A thunk is a function of signature R(BoundEntityType, Args&&...) noexcept(noex).
Each specialization of function_ref is a trivially copyable type [basic.types].
Within this subclause, call-args is an argument pack with elements that have types Args&&... respectively.
Constructors and assignment operators
[func.wrap.ref.ctor]
template<class... T>
static constexpr bool is-invocable-using = see below;
If noex is true, is-invocable-using<T...> is equal to:
is_nothrow_invocable_r_v<R, T..., ArgTypes...>
Otherwise, is-invocable-using<T...> is equal to:
is_invocable_r_v<R, T..., ArgTypes...>
template<class F> function_ref(F* f);
Constraints:
is_function_v<F> is true, and
is-invocable-using<F> is true.
Effects: Initializes bound-entity with f, and thunk-ptr to the address of a function such that thunk-ptr(bound-entity, call-args...) is expression equivalent to invoke_r<R>(f, call-args...).
template<class F> constexpr function_ref(F&& f);
Let T be remove_reference_t<F>.
Constraints:
remove_cvref_t<F> is not the same type as function_ref,
is_member_pointer_v<T> is false, and
is-invocable-using<cv T&> is true.
Effects: Initializes bound-entity with addressof(f), and thunk-ptr to address of a function such that thunk-ptr(bound-entity, call-args...) is expression equivalent to invoke_r<R>(static_cast<cv T&>(f), call-args...).
template<auto f> constexpr function_ref(nontype_t<f>) noexcept;
Constraints: is-invocable-using<decltype(f)> is true.
Effects: Initializes bound-entity with an unused value, and thunk-ptr to address of a function such that thunk-ptr(bound-entity, call-args...) is expression equivalent to invoke_r<R>(f, call-args...).
template<auto f, class U>
constexpr function_ref(nontype_t<f>, U&& obj) noexcept;
Let T be remove_reference_t<U>.
Constraints:
is_rvalue_reference_v<U&&> is false,
is-invocable-using<decltype(f), cv T&> is true.
Effects: Initializes bound-entity with addressof(obj), and thunk-ptr to address of a function such that thunk-ptr(bound-entity, call-args...) is expression equivalent to invoke_r<R>(f, static_cast<cv T&>(obj), call-args...).
template<auto f, class T>
constexpr function_ref(nontype_t<f>, cv T* obj) noexcept;
Constraints: is-invocable-using<decltype(f), cv T*> is true.
Effects: Initializes bound-entity with obj, and thunk-ptr to address of a function such that thunk-ptr(bound-entity, call-args...) is expression equivalent to invoke_r<R>(f, obj, call-args...).
template<class T> function_ref& operator=(T) = delete;
Constraints:
T is not the same type as function_ref, and
is_pointer_v<T> is false.
Invocation
[func.wrap.ref.inv]
R operator()(ArgTypes... args) const noexcept(noex);
Preconditions: bound-entity does not store a null pointer value.
Effects: Equivalent to
return thunk-ptr(bound-entity, std::forward<ArgTypes>(args)...);
Deduction guides
[func.wrap.ref.deduct]
template<class F>
function_ref(F*) -> function_ref<F>;
Constraints: is_function_v<F> is true.
template<auto f>
function_ref(nontype_t<f>) -> function_ref<see below>;
Let F be remove_pointer_t<decltype(f)>.
Constraints: is_function_v<F> is true.
Remarks: The deduced type is function_ref<F>.
template<auto f>
function_ref(nontype_t<f>, auto) -> function_ref<see below>;
Constraints:
decltype(f) is of the form R(G::*)(A...) cv &opt noexcept(E) for a type G, or
decltype(f) is of the form R G::* for a type G, in which case let A... be an empty pack and E be false, or
decltype(f) is of the form R(*)(G, A...) noexcept(E) for a type G.
Remarks: The deduced type is function_ref<R(A...) noexcept(E)>.
Feature test macro
Insert the following to [version.syn], header <version> synopsis, after __cpp_lib_move_only_function:
#define __cpp_lib_function_ref 20XXXXL // also in <functional>
Implementation Experience
A complete implementation is available from
■ zhihaoy/nontype_functional@p0792r11.
Many facilities similar to function_ref exist and are widely used in large codebases. See Survey for details.
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.
Thanks to Jens Maurer for encouraging participation and Tomasz Kamiński for the thorough wording review.
References
Zhihao Yuan <zy@miator.net>
Jarrad Waterloo <descender76@gmail.com>
function_ref: a type-erased callable referenceTable of contents
Changelog
R11
R10
nontype_tconstructors.R9
function_refconstinit.R8
R7
R6
R5
operator()specification (typo);R4
constexprdue to implementation concerns;R3
std::functionno longer has a precondition;function_ref::operator()is now unconditionallyconst-qualified.R2
= default;R1
nullptr, and default constructor;noexceptandconst-qualified function signatures;std::function;noexceptfrom constructor and assignment operator.Abstract
This paper proposes the addition of
function_ref<R(Args...)>, a vocabulary type with reference semantics for passing entities to call, to the standard library.Motivating examples
Here’s one example use case that benefits from higher-order functions: a
retry(n, f)function that attempts to callfup tontimes synchronously until success. This example might model the real-world scenario of repeatedly querying a flaky web service.The passed-in
actionshould be a callable entity that takes no arguments and returns apayload. Let’s see how to implementedretrywith various techniques.Using function pointers
Advantages:
Easy to implement: no template, nor constraint. The function pointer type has a signature that nails down which functions to pass.
Minimal overhead: no allocations, no exceptions, and major calling conventions can pass a function pointer in a register.
Drawbacks:
Using a template
Advantages:
Support arbitrary function objects, such as closures with captures.
Zero-overhead: no allocations, no exceptions, no indirections.
Drawbacks:
Harder to implement: users must constrain
action’s signature.Fail to support separable compilation: the implementation of
retrymust appear in a header file. A slight change at the call site will cause recompilation of the function body.Using
std::functionorstd::move_only_functionAdvantages:
Take more callable objects, from closures to pointer-to-members.
Easy to implement: no need to use a template or any explicit constraint.
std::functionandstd::move_only_functionconstructor is constrained.Drawbacks:
std::functionandstd::move_only_function’s converting constructor[1] require their target objects to be copy-constructible or move-constructible, respectively;Comes with potentially significant overhead:
The call wrappers start to allocate the target objects when they do not fit in a small buffer, introducing more indirection when calling the objects.
No calling conventions can pass these call wrappers in registers.
Modern compilers cannot inline these call wrappers, often resulting in inferior codegen then previously mentioned techniques.
One rarely known technique is to pass callable objects to call wrappers via a
std::reference_wrapper:But users cannot replace the
downloaderin the example with a lambda expression as such an expression is an rvalue. Meanwhile, all the machinery that implements type-erased copying or moving must still be present in the codegen.Using the proposed
function_refAdvantages:
Takes any callable objects regardless of whether they are constructible.
Easy to implement: no need to use a template or any constraint.
function_refis constrained.Clean ownership semantics:
function_refhas reference semantics as its name suggests.Minimal overhead: no allocations, no exceptions, certain calling conventions can pass
function_refin registers.Design considerations
This paper went through LEWG at R5, with a number of consensuses reached and applied to the wording:
target()ortarget_type;operator bool, default constructor, or comparison withnullptr;R(Args...) noexceptspecializations;R(Args...) constspecializations;operator()unconditionallyconst;function_refas the right name.One design question remains not fully understood by many: how should a function pointer initialize
function_ref?In a typical scenario, there is no lifetime issue no matter whether the
downloadentity below is a function, a function pointer, or a closure:However, even if the users use
function_refonly as parameters initially, it’s not uncommon to evolve the API by grouping parameters into structures,and structures start to need constructors or factories to simplify initialization:
According to the P0792R5[2] wording, the code has well-defined behavior if
downloadis a function. However, one cannot write the code assince this will create a
function_refwith a dangling object pointer that points to a temporary object – the function pointer.In other words, the following code also has undefined behavior:
The users have to write the following to get well-defined behavior.
Survey
We collected the following
function_refimplementations available today:■
llvm::function_ref– from LLVM[3]■
tl::function_ref– by Sy Brand■
folly::FunctionRef– from Meta■
gdb::function_view– from GNU■
type_safe::function_ref– by Jonathan Müller[4]■
absl::function_ref– from GoogleThey have diverging behaviors when initialized from function pointers:
Undefined:
Well-defined:
■
llvm::function_ref■
tl::function_refWell-defined:
Well-defined:
■
folly::FunctionRef■
gdb::function_view■
type_safe::function_ref■
absl::function_refP0792R5 wording gives Behavior A.1.
A related question is what happens when initialized from pointer-to-members. In the following tables, assume
&Ssh::connectis a pointer to member function:Well-defined:
Undefined:
■
tl::function_ref■
folly::FunctionRef■
absl::function_refIll-formed:
Ill-formed:
Well-defined:
■
llvm::function_ref■
gdb::function_view■
type_safe::function_refP0792R5-R7 wording gives Behavior B.1.
Additional information
P2472 “make
function_refmore functional” [5] suggests a way to initializefunction_reffrom pointer-to-members without dangling in all contexts:Not convertible from pointer-to-members means that
function_refdoes not need to useinvoke_rin the implementation, improving debug codegen in specific toolchains with little effort.Making
function_reflarge enough to fit a thunk pointer plus any pointer-to-member-function may renderstd::function_refirrelevant in the real world. Some platform ABIs can pass a trivially copyable type of a 2-word size in registers and cannot do the same to a bigger type. Here is some LLVM IR to show the difference: https://godbolt.org/z/Ke3475vz8.For good or bad, the expression
&qualified-idthat retrieves pointer-to-member shares grammar with the expression that gets a pointer to explicit object member functions. [expr.unary.op/3]Design decisions
Behavior A.2 is incorporated to eliminate the difference between initializing
function_reffrom a function and initializingfunction_reffrom a function pointer.Behavior B.2 is incorporated to reduce potential damage at a small cost. This change will reflect on the constructor’s constraints.
LEWG further requested making converting assignment from anything other than functions and function pointers ill-formed. Note that
function_refwill still be copy-assignable.LEWG achieved consensus on P2472R3, and the wording is merged here. This change brings back the pointer-to-member support in a different form.
Wording
The wording is relative to N4910.
Add new templates to [utility.syn], header
<utility>synopsis, afterin_place_index_tandin_place_index:namespace std { [...] // nontype argument tag template<auto V> struct nontype_t { explicit nontype_t() = default; }; template<auto V> inline constexpr nontype_t<V> nontype{}; }Add the template to [functional.syn], header
<functional>synopsis:// [func.wrap.move], move only wrapper template<class... S> class move_only_function; // not defined template<class R, class... ArgTypes> class move_only_function<R(ArgTypes...) cv ref noexcept(noex)>; // see below // [func.wrap.ref], non-owning wrapper template<class... S> class function_ref; // not defined template<class R, class... ArgTypes> class function_ref<R(ArgTypes...) cv noexcept(noex)>; // see belowCreate a new section “Non-owning wrapper”,
[func.wrap.ref]with the following:namespace std { template<class... S> class function_ref; // not defined template<class R, class... ArgTypes> class function_ref<R(ArgTypes...) cv noexcept(noex)> { public: // [func.wrap.ref.ctor], constructors and assignment operators template<class F> function_ref(F*) noexcept; template<class F> constexpr function_ref(F&&) noexcept; template<auto f> constexpr function_ref(nontype_t<f>) noexcept; template<auto f, class U> constexpr function_ref(nontype_t<f>, U&&) noexcept; template<auto f, class T> constexpr function_ref(nontype_t<f>, cv T*) noexcept; constexpr function_ref(const function_ref&) noexcept = default; constexpr function_ref& operator=(const function_ref&) noexcept = default; template<class T> function_ref& operator=(T) = delete; // [func.wrap.ref.inv], invocation R operator()(ArgTypes...) const noexcept(noex); private: template<class... T> static constexpr bool is-invocable-using = see below; // exposition only }; // [func.wrap.ref.deduct], deduction guides template<class F> function_ref(F*) -> function_ref<F>; template<auto f> function_ref(nontype_t<f>) -> function_ref<see below>; template<auto f> function_ref(nontype_t<f>, auto) -> function_ref<see below>; }Feature test macro
Implementation Experience
A complete implementation is available from ■ zhihaoy/nontype_functional@p0792r11.
Many facilities similar to
function_refexist and are widely used in large codebases. See Survey for details.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.
Thanks to Jens Maurer for encouraging participation and Tomasz Kamiński for the thorough wording review.
References
move_only_function http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0288r9.html ↩︎
function_ref: a non-owning reference to a Callable http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0792r5.html ↩︎
The function_ref class template. LLVM Programmer’s Manual https://llvm.org/docs/ProgrammersManual.html#the-function-ref-class-template ↩︎
Implementing function_view is harder than you might think http://foonathan.net/blog/2017/01/20/function-ref-implementation.html ↩︎
make function_ref more functional https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2472r3.html ↩︎