proxygen
|
A polymorphic function wrapper that is not copyable and does not require the wrapped function to be copy constructible. More...
#include <Function.h>
A polymorphic function wrapper that is not copyable and does not require the wrapped function to be copy constructible.
folly::Function
is a polymorphic function wrapper, similar to std::function
. The template parameters of the folly::Function
define the parameter signature of the wrapped callable, but not the specific type of the embedded callable. E.g. a folly::Function<int(int)>
can wrap callables that return an int
when passed an int
. This can be a function pointer or any class object implementing one or both of
int operator(int); int operator(int) const;
If both are defined, the non-const one takes precedence.
Unlike std::function
, a folly::Function
can wrap objects that are not copy constructible. As a consequence of this, folly::Function
itself is not copyable, either.
Another difference is that, unlike std::function
, folly::Function
treats const-ness of methods correctly. While a std::function
allows to wrap an object that only implements a non-const operator()
and invoke a const-reference of the std::function
, folly::Function
requires you to declare a function type as const in order to be able to execute it on a const-reference.
For example:
class Foo { public: void operator()() { // mutates the Foo object } }; class Bar { std::function<void(void)> foo_; // wraps a Foo object public: void mutateFoo() const { foo_(); } };
Even though mutateFoo
is a const-method, so it can only reference foo_
as const, it is able to call the non-const operator()
of the Foo object that is embedded in the foo_ function.
folly::Function
will not allow you to do that. You will have to decide whether you need to invoke your wrapped callable from a const reference (like in the example above), in which case it will only wrap a operator() const
. If your functor does not implement that, compilation will fail. If you do not require to be able to invoke the wrapped function in a const context, you can wrap any functor that implements either or both of const and non-const operator()
.
The template parameter of folly::Function
, the FunctionType
, can be const-qualified. Be aware that the const is part of the function signature. It does not mean that the function type is a const type.
using FunctionType = R(Args...); using ConstFunctionType = R(Args...) const;
In this example, FunctionType
and ConstFunctionType
are different types. ConstFunctionType
is not the same as const FunctionType
. As a matter of fact, trying to use the latter should emit a compiler warning or error, because it has no defined meaning.
// This will not compile: folly::Function<void(void) const> func = Foo(); // because Foo does not have a member function of the form: // void operator()() const; // This will compile just fine: folly::Function<void(void)> func = Foo(); // and it will wrap the existing member function: // void operator()();
When should a const function type be used? As a matter of fact, you will probably not need to use const function types very often. See the following example:
class Bar { folly::Function<void()> func_; folly::Function<void() const> constFunc_; void someMethod() { // Can call func_. func_(); // Can call constFunc_. constFunc_(); } void someConstMethod() const { // Can call constFunc_. constFunc_(); // However, cannot call func_ because a non-const method cannot // be called from a const one. } };
As you can see, whether the folly::Function
's function type should be declared const or not is identical to whether a corresponding method would be declared const or not.
You only require a folly::Function
to hold a const function type, if you intend to invoke it from within a const context. This is to ensure that you cannot mutate its inner state when calling in a const context.
This is how the const/non-const choice relates to lambda functions:
// Non-mutable lambdas: can be stored in a non-const... folly::Function<void(int)> print_number = [] (int number) { std::cout << number << std::endl; }; // ...as well as in a const folly::Function folly::Function<void(int) const> print_number_const = [] (int number) { std::cout << number << std::endl; }; // Mutable lambda: can only be stored in a non-const folly::Function: int number = 0; folly::Function<void()> print_number = [number] () mutable { std::cout << ++number << std::endl; }; // Trying to store the above mutable lambda in a // `folly::Function<void() const>` would lead to a compiler error: // error: no viable conversion from '(lambda at ...)' to // 'folly::Function<void () const>'
Casting between const and non-const folly::Function
s: conversion from const to non-const signatures happens implicitly. Any function that takes a folly::Function<R(Args...)>
can be passed a folly::Function<R(Args...) const>
without explicit conversion. This is safe, because casting from const to non-const only entails giving up the ability to invoke the function from a const context. Casting from a non-const to a const signature is potentially dangerous, as it means that a function that may change its inner state when invoked is made possible to call from a const context. Therefore this cast does not happen implicitly. The function folly::constCastFunction
can be used to perform the cast.
// Mutable lambda: can only be stored in a non-const folly::Function: int number = 0; folly::Function<void()> print_number = [number] () mutable { std::cout << ++number << std::endl; }; // const-cast to a const folly::Function: folly::Function<void() const> print_number_const = constCastFunction(std::move(print_number));
When to use const function types? Generally, only when you need them. When you use a folly::Function
as a member of a struct or class, only use a const function signature when you need to invoke the function from const context. When passing a folly::Function
to a function, the function should accept a non-const folly::Function
whenever possible, i.e. when it does not need to pass on or store a const folly::Function
. This is the least possible constraint: you can always pass a const folly::Function
when the function accepts a non-const one.
How does the const behaviour compare to std::function
? std::function
can wrap object with non-const invokation behaviour but exposes them as const. The equivalent behaviour can be achieved with folly::Function
like so:
std::function<void(void)> stdfunc = someCallable; folly::Function<void(void) const> uniqfunc = constCastFunction( folly::Function<void(void)>(someCallable) );
You need to wrap the callable first in a non-const folly::Function
to select a non-const invoke operator (or the const one if no non-const one is present), and then move it into a const folly::Function
using constCastFunction
. The name of constCastFunction
should warn you that something potentially dangerous is happening. As a matter of fact, using std::function
always involves this potentially dangerous aspect, which is why it is not considered fully const-safe or even const-correct. However, in most of the cases you will not need the dangerous aspect at all. Either you do not require invokation of the function from a const context, in which case you do not need to use constCastFunction
and just use the inner folly::Function
in the example above, i.e. just use a non-const folly::Function
. Or, you may need invokation from const, but the callable you are wrapping does not mutate its state (e.g. it is a class object and implements operator() const
, or it is a normal, non-mutable lambda), in which case you can wrap the callable in a const folly::Function
directly, without using constCastFunction
. Only if you require invokation from a const context of a callable that may mutate itself when invoked you have to go through the above procedure. However, in that case what you do is potentially dangerous and requires the equivalent of a const_cast
, hence you need to call constCastFunction
.