Document number | P2472R2 |
Date | 2022-03-08 |
Reply-to |
Jarrad J. Waterloo < Zhihao Yuan < |
Audience | Library Evolution Working Group (LEWG) |
function_ref
more functionalThis document proposes adding additional constructors to function_ref
1 in order to make it easier to use, more efficient, and safer to use with common use cases.
Currently, a function_ref
, can be constructed from a lambda/functor, a free function pointer, and a member function pointer. While the lambda/functor use case does support type erasing this
pointer, its free/member function pointer constructors do not allow type erasing any arguments, even though these two use cases are common.
|
||
Stateless | Stateful | |
Lambda/Functor | 🗸 | 🗸 |
Free Function | 🗸 | ✗ |
Member Function | 🗸 | ✗ |
struct rule {
void when() {
}
};
// rule could just as easily be test which also have when and then functions
void then(rule& r) {
}
void rule_when(rule& r) {
.when();
r}
struct callback {
* r;
rulevoid (*f)(rule&);
};
; rule localTaxRule
Since typed erased free and member functions are not currently supported, the current function_ref
proposal forces users to create an unnecessary temporary functor, likely with std::bind_front
or a stateful/capturing lambda that the developer hopes the optimizer to elide.
C/C++ core language |
|
function_ref |
|
proposed |
|
C/C++ core language |
|
function_ref |
|
proposed |
|
This has numerous disadvantages compared to what the C++ core language can do. It is inefficient, hard to use, and unsafe to use.
function_ref
is bifurcated by passing it directly as a function argument or using 2-step initialization by first creating a named temporary. Direct initializing a variable of function_ref
is needed if the same object serves as multiple arguments on either the same but more likely different functions. However, this leads to immediate dangling, which must introduce an extra line of code. Furthermore, getting in the habit of only passing function_ref
as an argument to a function results in users duplicating lambdas when needed to be used more than once, again needless increasing the code volume. Initialization is consistently safe in both the C/C++ core language and the proposed revision, regardless of whether function_ref
appears as a variable.function_ref
is a reference to a “stateful” lambda which also is a reference. Even if the optimizer can remove the cost, the user is still left with the burden in the code.nontype
does not take a pointer but rather a member function pointer initialization statement thus giving the compiler more information to resolve the function selection at compile time rather than runtime, granted it is more likely with free functions instead of member functions.function_ref
outside of a function argument immediately dangles. Some would say that this is no different than other standardized types such as string_view
. However, this is not true.
C/C++ core language |
|
function_ref |
|
proposed |
|
function_ref
proposal and the proposed addendum perform the same desired task, the former dangles and the later doesn’t. It is clear from the immediately dangling string_view
example that it dangles because sv
is a reference and ""s
is a temporary. However, it is less clear in the original function_ref
example. While it is true and clear that fr
is a reference and the stateful lambda is a temporary. It is not what the user of function_ref
is intending or wanting to express. localTaxRule
is the state that the user wants to type erase and function_ref
would not dangle if localTaxRule
was the state since it is not a temporary and has already been constructed higher up in the call stack. Further, the member function when
or the free function then
should not dangle since functions are stateless and also global. So member/free function with type erasure use cases are more like string_view
when the referenced object is safely constructed.easier to use | more efficient | safer to use | |
---|---|---|---|
C/C++ core language | 🗸 | 🗸 | 🗸 |
function_ref | ✗ | ✗ | ✗ |
proposed | 🗸 | 🗸 | 🗸 |
What is more, member/free functions with type erasure are common use cases! “Member functions with type erasure” is used by delegates/events in object oriented programming and “free functions with type erasure” are common with callbacks in procedural/functional programming.
The fact that users do not expect “stateless” things to dangle becomes even more apparent with the member function without type erasure use case.
C/C++ core language |
|
function_ref |
|
proposed |
|
Current function_ref
implementations store a reference to the member function pointer as the state inside function_ref
. A trampoline function is required regardless. However, the user expected behavior is for function_ref
referenced state to be unused/nullptr
, as all of the arguments must be forwarded since none are being type erased. Such dangling is never expected, yet the current function_ref
proposal/implementation does. Similarly, this use case suffers, just as the previous two did, with respect to ease of use, efficiency, and safety due to the superfluous lambda/functor and two-step initialization.
easier to use | more efficient | safer to use | |
---|---|---|---|
C/C++ core language | 🗸 | 🗸 | 🗸 |
function_ref | ✗ | ✗ | ✗ |
proposed | 🗸 | 🗸 | 🗸 |
The C/C++ core language, function_ref
, and the proposed examples are approximately equal concerning ease of use, efficiency, and safety for the free function without type erasure use case. While the proposed nontype
example is slightly wordier because of the template nontype
, it is more consistent with the other three use cases, making it more teachable and usable since the user does not need to choose between bifurcation practices. Also, the expectation of unused state and the function being selected at compile time still applies here, as it does for member function without type erasure use case.
C/C++ core language |
|
function_ref |
|
proposed |
|
easier to use | more efficient | safer to use | |
---|---|---|---|
C/C++ core language | 🗸 | 🗸 | 🗸 |
function_ref | 🗸 | 🗸 | 🗸 |
proposed | 🗸 | 🗸 | 🗸 |
Should the existing free function constructor be removed with the overlap in functionality with the free function without type erasure use case? NO. Initializing a function_ref
from a function pointer instead of a non-type function pointer template argument is still usable in the most runtime of libraries, such as dynamically loaded libraries where function pointers are looked up. However, it is not necessarily the general case where users work with declarations found in header files and modules.
The wording is relative to P0792R8.
Add new templates to 20.2.1 [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 a definition to 20.14.3 [func.def]:
template<auto f> constexpr function_ref(nontype_t<f>) noexcept;
Constraints:
is-invocable-using<decltype(f)>
istrue
.Effects: Initializes
bound-entity
withnullptr
, andthunk-ptr
to address of a function such thatthunk-ptr(bound-entity, call-args...)
is expression equivalent toinvoke_r<R>(f, nullptr, call-args...)
.
template<auto f, class T> function_ref(nontype_t<f>, T& state) noexcept;
Constraints:
is-invocable-using<decltype(f), cv T&>
is true.Effects: Initializes
bound-entity
withaddressof(state)
, andthunk-ptr
to address of a function such thatthunk-ptr(bound-entity, call-args...)
is expression equivalent toinvoke_r<R>(f, static_cast<T cv&>(bound-entity), call-args...)
.
template<auto f, class T> function_ref(nontype_t<f>, cv T* state) noexcept;
Constraints:
is-invocable-using<decltype(f), cv T*>
istrue
.Effects: Initializes
bound-entity
withstate
, andthunk-ptr
to address of a function such thatthunk-ptr(bound-entity, call-args...)
is expression equivalent toinvoke_r<R>(f, static_cast<cv T*>(bound-entity), call-args...)
.
template<class R, class... ArgTypes>
class function_ref<R(ArgTypes...) cv ref noexcept(noex)> {
public:
// [func.wrap.ref.ctor], constructors ...
...
template<auto f> constexpr function_ref(nontype_t<f>) noexcept;
template<auto f, class T> function_ref(nontype_t<f>, T&) noexcept;
template<auto f, class T> function_ref(nontype_t<f>, cv T*) noexcept;
...
};
// [func.wrap.ref.deduct], deduction guides
template<auto f>
(nontype_t<f>) -> function_ref<std::remove_pointer_t<decltype(f)>;
function_reftemplate<auto f>
(nontype_t<f>, auto) -> function_ref<see below>;
function_ref}
Deduction guides
[func.wrap.ref.deduct]
template<auto f>
(nontype_t<f>) -> function_ref<std::remove_pointer_t<decltype(f)>; function_ref
Constraints:
is_function_v<f>
istrue
.
template<auto f>
(nontype_t<f>, auto) -> function_ref<see below>; function_ref
Constraints: -
decltype(f)
is of the formR(G::)(A...) cv &opt noexcept(E)
for a class typeG
, or -decltype(f)
is of the formR G::
for a class typeG
, in which case letA...
be an empty pack, or -decltype(f)
is of the formR(U, A...) noexcept(E)
.Remarks: The deduced type is
function_ref<R(A...) noexcept(E)>
.
We do not need a feature macro, because we intend for this paper to modify std::function_ref
before it ships.
C# and the .NET family of languages provide this via delegates
2.
// C#
void some_name();
delegate = then;// the stateless free function use case
some_name fr = localTaxRule.when;// the stateful member function use case some_name fr
Borland C++ now embarcadero provides this via __closure
3.
// Borland C++, embarcadero __closure
void(__closure * fr)();
= localTaxRule.when;// the stateful member function use case fr
function_ref
with nontype
constructors handles all four stateless/stateful free/member use cases. It is more feature-rich than those prior arts.
The most up-to-date implementation, created by Zhihao Yuan, is available on GitHub.4
Thanks to Arthur O’Dwyer, Tomasz Kamiński, Corentin Jabot and Zhihao Yuan for providing very valuable feedback on this proposal.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p0792r6.html↩︎
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/using-delegates↩︎
http://docwiki.embarcadero.com/RADStudio/Sydney/en/Closure↩︎
https://github.com/zhihaoy/nontype_functional/blob/main/include/std23/function_ref.h↩︎