Doc. no.: | P0792R6 |
Date: | 2022-01-17 |
Audience: | LEWG, 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
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.
Design considerations
This paper went through LEWG at R5, with a number of consensus 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, a function pointer, or a pointer-to-member initialize function_ref
? The sixth revision of the paper aims to provide full background and dive deep into that question.
Question 1
There has been a concern on whether
int f();
function_ref fr = &f;
fr();
creates a function_ref
object that points to a temporary function pointer and results in undefined behavior when called on the last line.
LEWG believes that fr
should not be dangling here; it should store the function pointer. This is also folly::FunctionRef
, gdb::function_view
, and type_safe::function_ref
(by Jonathan Müller)'s behavior.
P0792R5 believes that fr
should be dangling here because the function pointer here is a prvalue callable object, and the nature of the problem is indifferent from string_view
. This is llvm::function_ref
, tl::function_ref
(by Sy Brand), and P0792R5 sample implementation’s behavior.
LWG inferred and recognized the 2nd behavior from P0792R5 wording.
This paper (R6) supports the 1st behavior.
Question 2
The question above leads to the follow-up question: should fr
be dangling if assigned a function?
int f();
function_ref fr = f;
fr();
LEWG believes that this case is indifferent to Question 1 and should work. This is folly::FunctionRef
, llvm::function_ref
, tl::function_ref
, and P0792R5 sample implementation’s behavior assuming the C++ implementation supports converting a function pointer to an object pointer type or vice versa. This is also gdb::function_view
and type_safe::function_ref
’s behavior because their implementation involves a union.
P0792R5’s design is that, function_ref
references nothing but callable objects [func.def]. A function is not an object, so there is nothing we can reference. The function should decay into a function pointer prvalue, and we are back to Question 1. No known implementation supports this behavior.
LWG inferred and recognized the 1st behavior from P0792R5 wording.
This paper (R6) supports the 1st behavior.
Question 3
The last question is about pointer to members.
struct A { int mf(); } a;
function_ref fr = &A::mf;
fr(a);
Does the above have defined behavior?
LEWG made a poll and gave permission to “fully support the Callable
concept at the potential cost of sizeof(function_ref) >= sizeof(void(*)()) * 2
,” 6/4/0/0/0 in 2018-6. This implies that an implementation should expand its union to store the pointer to member functions. No known implementation supports this behavior.
P0792R5 emphasizes that pointer-to-member objects are callable. Therefore we should point to those objects and deal with the potential dangling issue with tools. All the implementations mentioned above except type_safe::function_ref
give this behavior.
type_safe::function_ref
does not support initializing from a pointer-to-member and suggests the users to opt-in the 2nd behavior with std::mem_fn(&A::mf)
.
LWG recognized the 2nd behavior while reviewing P0792R5 and generally agreed that a two-pointer function_ref
is fat enough.
This is an open question in R6 of the paper, even though we bring in new information. We will discuss all options and their defenses.
Discussion
Is std::function_ref
“parameter-only?”
Some people believe that std::function_ref
only needs to support being used as a parameter. In a typical scenario,
auto retry(function_ref<optional<payload>()> action);
there is no lifetime issue and all the three questions above do not matter:
auto result = retry(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, then structures become classes with constructors… And the end-users of the library will be tempted to prepare the parameter set before calling into the API:
retry_options opt(download, 1.5s);
auto result = retry(opt);
If download
is a function, the answer to Question 1 will now decide whether the code has well-defined behavior.
Is this only a lifetime issue?
The three questions we show above are often deemed “lifetime issues.” Being a lifetime issue in C++ implies a misuse and a workaround. Moreover, users can look for lifetime issues with tools such as lifetime checkers, sanitizers, etc.
However, although we demonstrate the problem as a lifetime issue, the solutions we chose can inevitably impact the semantics of function_ref
.
In the following line,
retry_options opt(p, 1.5s);
p
is a pointer to function. It’s an lvalue, so there is no lifetime issue. But if we go with the 2nd behavior when answering Question 2, a latter code that rebinds p
to something else
p = winhttp_download;
will replace opt
’s behavior.
One might argue that this behavior difference wouldn’t be visible if function_ref
were used only in parameter lists. However, this is not true either. If that function is a coroutine, the caller side can rebind p
and ultimately change the behavior of the copy after the coroutine resumes.
Is this an issue or non-issue?
One might argue that what is stated so far are all corner cases. For example, lambda expressions create rvalue closures, so they always outlive function_ref
immediately if you initialize function_ref
variables with them. So what motivates us to guarantee more for functions and even function pointers?
The situation is that no implementation in the real world dangles in all three cases. Inevitably, some guarantees have become features in practice. For example,
struct retry_options
{
retry_options() {}
function_ref<sig> callback = fn_fail;
seconds gap;
};
is a natural way to replace the default behavior with a specific one that reports failure. In addition, this style will become necessary after upgrading to std::function_ref
as it has no default constructor.
In short, we’re standardizing existing practice and can’t leave “production” behavior implementation-defined. We expect the committee to give firm answers to these questions.
Rationale
Background knowledge
We have always treated function_ref
as a std::function
or std::move_only_function
with reference semantics. But when applying this understanding, we worked out certain aspects of function_ref
based on existing jargons, while these jargons reflect a contrived view of std::move_only_function
.
The move_only_function
class template provides polymorphic wrappers that generalize the notion of a callable object.
– [func.wrap.move.class]/1
A callable object is an object of a callable type.
– [func.def]/4
A callable type is a function object type or a pointer to member.
– [func.def]/3
In other words, from the library’s point of view, function is not “callable.” A function, reference to function, or reference to a callable object, can be used in std::invoke
, and they satisfy the new invocable
concept, but std::move_only_function
is unaware of their existence. move_only_function
stores callable objects (i.e., target objects). As long as something is to initialize that object, move_only_function
is happy to accept it.
So defining function_ref
as a “reference to a callable” leads to a problem: what do we do when there is no “callable object,” invent one?
But function_ref
had never meant to initialize any target object by treating the argument to function_ref(F&&)
as an initializer. To move_only_function
’s constructor, its argument is an initializer; if the argument is an object, the entity to call is always a different object – a distinct copy of the argument. To function_ref
’s constructor, its argument is the entity to call.
I think this is a fundamental difference between function_ref
and move_only_function
. In the two (F&&)
constructors, F&&
in move_only_function(F&&)
is to forward value, while in function_ref(F&&)
, F&&
's role is to pass an identity so that we can call the entity that owns the identity via the reference. In my definition, F&&
is a callable reference.
Theory
By saying something “models after a pointer,” I mean the identity of such thing is irrelevant to the identity of the entity to reach after dereferencing. In C++, the “identity” here can be an address as nothing is relocatable in the abstract machine. By saying something “models after a reference,” I mean it is similar to a pointer but lets you use that thing in place of the dereferenced entity.
When saying something is a “callable reference,” I mean that thing models after a reference and is invocable. For example, an object pointer is not a callable reference, but a function pointer is. A reference to a function (e.g., void (&)(int)
) is naturally a callable reference. reference_wrapper<T>
is also a callable reference if T
is invocable.
function_ref<S>
is a callable reference as well. And I believe that when initializing function_ref
from another callable reference, the function_ref
object should bind to the entity behind the other callable reference. Such a callable reference needs to be type-passing since the conversion only can work as if we have both the identity and the type to the referenced entity.
Test
What should happen when a function_ref<S>
object is initialized from or binds to another object of the same type?
Both are type-erased callable references. By saying something is type-erasing, we mean there exists a type C that can be used in place of A and B without forming a dependent expression. Type-passing means that substituting A with B changes the type of the expression. But when copy-constructingfunction_ref
, the arguments are of the same type. So you can say that an expression to initialize function_ref<S>
is dependent on function_ref<S>
. The source type is equivalent to a type-passing callable reference, so we duplicate the internal states to make the two objects behave as if they are bound to the same entity.
Why bring up the suggestion to unwrap reference_wrapper
again?
Doing so fixes an issue: if initializing an object of function_ref<R(Args...) const>
from std::ref(obj)
given a modifiable obj
, the const
in the signature is not propagated to obj
. It is the opposite behavior compared to the following:
struct A
{
int operator()() const;
int operator()();
} obj;
function_ref<int() const> fr = obj;
fr();
If obj
were const
, std::ref(obj)
produces reference_wrapper<T const>
, which calls #2. This tells us std::ref
had never meant to “ensure” that an object is modifiable. By not creating function_ref
on top of reference_wrapper
, we prevent dangling, avoid object pointers that point to function pointers once more, and preserve reference_wrapper
’s design logic.
On pointer-to-member
We are back to the open question. Pointer-to-members are hard to categorize. You could imagine that a pointer-to-member is some callable reference, but the entity it references does not exist in the language.
I think all of the following arguments make sense:
- Because their referenced entities do not exist in the language, the code that initializes
function_ref
from a pointer-to-member should be ill-formed.
- Their referenced entities do not exist⟹they are not callable references. Therefore they should be treated as callable objects, even though they might be prvalues.
- Themselves should be treated as callable references even though they refer to hypothetical things.
function_ref
’s internal pointer may be an object pointer, a function pointer, and a pointer-to-member.
Meanwhile, there are other ways to use pointer-to-members in function_ref
. std::mem_fn(&A::mf)
is an option. Another option is nontype<&A::mf>
from P2511:
struct A { int mf(); } a;
function_ref<int(A&)> fr = nontype<&A::mf>;
fr(a);
Interestingly, this makes function_ref
comparable to a GCC feature that converts PMF constants to function pointers, except that the underlying implementation involves a thunk.
Wording
The wording is relative to N4901.
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
.
An object of class function_ref<S>
stores a pointer to thunk and a bound argument entity. A thunk is a function where a pointer to that function is a perfect forwarding call wrapper [func.def]. The bound argument is of an implementation-defined type to represent a pointer to object value, a pointer to function value, or a null pointer value.
function_ref<S>
is a trivially copyable type [basic.types].
Within this subclause, call_args
is an argument pack used in a function call expression [expr.call] of *this
, and val
is the value that the bound argument stores.
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 operator
template<class F> function_ref(F*) noexcept;
template<class F> function_ref(F&&) noexcept;
function_ref(const function_ref&) noexcept = default;
function_ref& operator=(const function_ref&) noexcept = default;
// [func.wrap.ref.inv], invocation
R operator()(ArgsTypes...) 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>;
}
Constructors and assignment operator
[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: Constructs a function_ref
object with the following properties:
- Its bound argument stores the value
f
.
- Its target object points to a thunk with call pattern
invoke_r<R>(val, call_args...)
.
template<class F> function_ref(F&& f);
Let U
be remove_reference_t<F>
. If remove_cv_t<U>
is reference_wrapper<X>
for some X
, let T
be X
; otherwise, T
is U
.
Constraints:
remove_cvref_t<F>
is not the same type as function_ref
, and
is-invocable-using<cv T&>
is true
.
Effects: Constructs a function_ref
object with the following properties:
- Its bound argument stores the value
addressof(static_cast<T&>(f))
.
- Its target object points to a thunk with call pattern
invoke_r<R>(obj, call_args...)
where obj
is an invented variable introduced in:
cv T& obj = *val;
function_ref(const function_ref& f) noexcept = default;
Effects: Constructs a function_ref
object with a copy of f
’s state entities.
Remarks: This constructor is trivial.
function_ref& operator=(const function_ref& f) noexcept = default;
Effects: Replaces the state entities of *this
with the state entities of f
.
Returns: *this
.
Remarks: This assignment operator is trivial.
Invocation
[func.wrap.ref.inv]
R operator()(ArgsTypes... args) const noexcept(noex);
Let g
be the target object and p
be the bound argument entity of *this
.
Preconditions: p
stores a non-null pointer value.
Effects: Equivalent to return g(p, 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
.
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
Here is a macro-free implementation that supports const
and noexcept
qualifiers: [Github] [Godbolt]
Sy Brand’s tl::function_ref
[Github] is distributed in vcpkg ports. This implementation does not support qualified signatures.
Many facilities similar to function_ref
exist and are widely used in large codebases. Here are some examples:
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.
References
Zhihao Yuan <zy@miator.net>
Jarrad Waterloo <descender76@gmail.com>
function_ref
: a type-erased callable referenceTable of contents
Changelog
R6
R5
operator()
specification (typo);R4
constexpr
due to implementation concerns;R3
std::function
no longer has a precondition;function_ref::operator()
is now unconditionallyconst
-qualified.R2
= default
;R1
nullptr
, and default constructor;noexcept
andconst
-qualified function signatures;std::function
;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.Design considerations
This paper went through LEWG at R5, with a number of consensus reached and applied to the wording:
target()
ortarget_type
;operator bool
, default constructor, or comparison withnullptr
;R(Args...) noexcept
specializations;R(Args...) const
specializations;operator()
unconditionallyconst
;function_ref
as the right name.One design question remains not fully understood by many: how should a function, a function pointer, or a pointer-to-member initialize
function_ref
? The sixth revision of the paper aims to provide full background and dive deep into that question.Question 1
There has been a concern on whether
creates a
function_ref
object that points to a temporary function pointer and results in undefined behavior when called on the last line.LEWG believes that
fr
should not be dangling here; it should store the function pointer. This is alsofolly::FunctionRef
,gdb::function_view
, andtype_safe::function_ref
(by Jonathan Müller)'s behavior.P0792R5[1] believes that
fr
should be dangling here because the function pointer here is a prvalue callable object, and the nature of the problem is indifferent fromstring_view
[2]. This isllvm::function_ref
,tl::function_ref
(by Sy Brand), and P0792R5 sample implementation’s behavior.LWG inferred and recognized the 2nd behavior from P0792R5 wording.
This paper (R6) supports the 1st behavior.
Question 2
The question above leads to the follow-up question: should
fr
be dangling if assigned a function?LEWG believes that this case is indifferent to Question 1 and should work. This is
folly::FunctionRef
,llvm::function_ref
,tl::function_ref
, and P0792R5 sample implementation’s behavior assuming the C++ implementation supports converting a function pointer to an object pointer type or vice versa. This is alsogdb::function_view
andtype_safe::function_ref
’s behavior because their implementation involves a union.[3]P0792R5’s design is that,
function_ref
references nothing but callable objects [func.def]. A function is not an object, so there is nothing we can reference. The function should decay into a function pointer prvalue, and we are back to Question 1. No known implementation supports this behavior.LWG inferred and recognized the 1st behavior from P0792R5 wording.
This paper (R6) supports the 1st behavior.
Question 3
The last question is about pointer to members.
Does the above have defined behavior?
LEWG made a poll and gave permission to “fully support the
Callable
concept at the potential cost ofsizeof(function_ref) >= sizeof(void(*)()) * 2
,” 6/4/0/0/0 in 2018-6. This implies that an implementation should expand its union to store the pointer to member functions. No known implementation supports this behavior.P0792R5 emphasizes that pointer-to-member objects are callable. Therefore we should point to those objects and deal with the potential dangling issue with tools. All the implementations mentioned above except
type_safe::function_ref
give this behavior.type_safe::function_ref
does not support initializing from a pointer-to-member and suggests the users to opt-in the 2nd behavior withstd::mem_fn(&A::mf)
.LWG recognized the 2nd behavior while reviewing P0792R5 and generally agreed that a two-pointer
function_ref
is fat enough.This is an open question in R6 of the paper, even though we bring in new information. We will discuss all options and their defenses.
Discussion
Is
std::function_ref
“parameter-only?”Some people believe that
std::function_ref
only needs to support being used as a parameter.[4] In a typical scenario,there is no lifetime issue and all the three questions above do not matter:
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, then structures become classes with constructors… And the end-users of the library will be tempted to prepare the parameter set before calling into the API:If
download
is a function, the answer to Question 1 will now decide whether the code has well-defined behavior.Is this only a lifetime issue?
The three questions we show above are often deemed “lifetime issues.” Being a lifetime issue in C++ implies a misuse and a workaround. Moreover, users can look for lifetime issues with tools such as lifetime checkers, sanitizers, etc.
However, although we demonstrate the problem as a lifetime issue, the solutions we chose can inevitably impact the semantics of
function_ref
.In the following line,
p
is a pointer to function. It’s an lvalue, so there is no lifetime issue. But if we go with the 2nd behavior when answering Question 2, a latter code that rebindsp
to something elsewill replace
opt
’s behavior.One might argue that this behavior difference wouldn’t be visible if
function_ref
were used only in parameter lists. However, this is not true either. If that function is a coroutine, the caller side can rebindp
and ultimately change the behavior of the copy after the coroutine resumes.Is this an issue or non-issue?
One might argue that what is stated so far are all corner cases. For example, lambda expressions create rvalue closures, so they always outlive
function_ref
immediately if you initializefunction_ref
variables with them. So what motivates us to guarantee more for functions and even function pointers?The situation is that no implementation in the real world dangles in all three cases. Inevitably, some guarantees have become features in practice. For example,
is a natural way to replace the default behavior with a specific one that reports failure. In addition, this style will become necessary after upgrading to
std::function_ref
as it has no default constructor.In short, we’re standardizing existing practice and can’t leave “production” behavior implementation-defined. We expect the committee to give firm answers to these questions.
Rationale
Background knowledge
We have always treated
function_ref
as astd::function
orstd::move_only_function
with reference semantics. But when applying this understanding, we worked out certain aspects offunction_ref
based on existing jargons, while these jargons reflect a contrived view ofstd::move_only_function
.In other words, from the library’s point of view, function is not “callable.” A function, reference to function, or reference to a callable object, can be used in
std::invoke
, and they satisfy the newinvocable
concept, butstd::move_only_function
is unaware of their existence.move_only_function
stores callable objects (i.e., target objects). As long as something is to initialize that object,move_only_function
is happy to accept it.So defining
function_ref
as a “reference to a callable” leads to a problem: what do we do when there is no “callable object,” invent one?But
function_ref
had never meant to initialize any target object by treating the argument tofunction_ref(F&&)
as an initializer. Tomove_only_function
’s constructor, its argument is an initializer; if the argument is an object, the entity to call is always a different object – a distinct copy of the argument. Tofunction_ref
’s constructor, its argument is the entity to call.I think this is a fundamental difference between
function_ref
andmove_only_function
. In the two(F&&)
constructors,F&&
inmove_only_function(F&&)
is to forward value, while infunction_ref(F&&)
,F&&
's role is to pass an identity so that we can call the entity that owns the identity via the reference. In my definition,F&&
is a callable reference.Theory
By saying something “models after a pointer,” I mean the identity of such thing is irrelevant to the identity of the entity to reach after dereferencing. In C++, the “identity” here can be an address as nothing is relocatable in the abstract machine. By saying something “models after a reference,” I mean it is similar to a pointer but lets you use that thing in place of the dereferenced entity.
When saying something is a “callable reference,” I mean that thing models after a reference and is invocable. For example, an object pointer is not a callable reference, but a function pointer is. A reference to a function (e.g.,
void (&)(int)
) is naturally a callable reference.reference_wrapper<T>
is also a callable reference ifT
is invocable.function_ref<S>
is a callable reference as well. And I believe that when initializingfunction_ref
from another callable reference, thefunction_ref
object should bind to the entity behind the other callable reference. Such a callable reference needs to be type-passing since the conversion only can work as if we have both the identity and the type to the referenced entity.Test
What should happen when a
function_ref<S>
object is initialized from or binds to another object of the same type?Both are type-erased callable references. By saying something is type-erasing, we mean there exists a type C that can be used in place of A and B without forming a dependent expression. Type-passing means that substituting A with B changes the type of the expression. But when copy-constructing
function_ref
, the arguments are of the same type. So you can say that an expression to initializefunction_ref<S>
is dependent onfunction_ref<S>
. The source type is equivalent to a type-passing callable reference, so we duplicate the internal states to make the two objects behave as if they are bound to the same entity.Doing so fixes an issue: if initializing an object of
function_ref<R(Args...) const>
fromstd::ref(obj)
given a modifiableobj
, theconst
in the signature is not propagated toobj
. It is the opposite behavior compared to the following:If
obj
wereconst
,std::ref(obj)
producesreference_wrapper<T const>
, which calls #2. This tells usstd::ref
had never meant to “ensure” that an object is modifiable. By not creatingfunction_ref
on top ofreference_wrapper
, we prevent dangling, avoid object pointers that point to function pointers once more, and preservereference_wrapper
’s design logic.On pointer-to-member
We are back to the open question. Pointer-to-members are hard to categorize. You could imagine that a pointer-to-member is some callable reference, but the entity it references does not exist in the language.
I think all of the following arguments make sense:
function_ref
from a pointer-to-member should be ill-formed.function_ref
’s internal pointer may be an object pointer, a function pointer, and a pointer-to-member.Meanwhile, there are other ways to use pointer-to-members in
function_ref
.std::mem_fn(&A::mf)
is an option. Another option isnontype<&A::mf>
from P2511[5]:Interestingly, this makes
function_ref
comparable to a GCC feature that converts PMF constants to function pointers[6], except that the underlying implementation involves a thunk.Wording
The wording is relative to N4901.
Add the template to [functional.syn], header
<functional>
synopsis:Create a new section “Non-owning wrapper”,
[func.wrap.ref]
with the following:Feature test macro
Implementation Experience
Here is a macro-free implementation that supports
const
andnoexcept
qualifiers: [Github] [Godbolt]Sy Brand’s
tl::function_ref
[Github] is distributed in vcpkg ports. This implementation does not support qualified signatures.Many facilities similar to
function_ref
exist and are widely used in large codebases. Here are some examples:llvm::function_ref
from LLVM[7]folly::FunctionRef
from Metagdb::function_view
from GNUAcknowledgments
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.
References
function_ref: a non-owning reference to a Callable http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0792r5.html ↩︎
std::string_view accepting temporaries: good idea or horrible pitfall? https://www.foonathan.net/2017/03/string_view-temporary/ ↩︎
Implementing function_view is harder than you might think http://foonathan.net/blog/2017/01/20/function-ref-implementation.html ↩︎
On function_ref and string_view https://quuxplusone.github.io/blog/2019/05/10/function-ref-vs-string-view/ ↩︎
Beyond operator(): NTTP callables in type-erased call wrappers http://wg21.link/p2511r0 ↩︎
Extracting the Function Pointer from a Bound Pointer to Member Function. Using the GNU Compiler Collection (GCC) https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html ↩︎
The function_ref class template. LLVM Programmer’s Manual https://llvm.org/docs/ProgrammersManual.html#the-function-ref-class-template ↩︎