Commit 94070f19 authored by Sven Over's avatar Sven Over Committed by Facebook Github Bot 6

folly/docs: add documentation about folly::Function

Summary: This diff adds folly/docs/Function.md

Reviewed By: yfeldblum

Differential Revision: D3120617

fb-gh-sync-id: fecc0e507e05016aaac43ba981eab49431229bd7
fbshipit-source-id: fecc0e507e05016aaac43ba981eab49431229bd7
parent b1e45251
...@@ -26,8 +26,10 @@ ...@@ -26,8 +26,10 @@
* type of the embedded callable. E.g. a `folly::Function<int(int)>` * 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 * 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 * function pointer or any class object implementing one or both of
*
* int operator(int); * int operator(int);
* int operator(int) const; * int operator(int) const;
*
* If both are defined, the non-const one takes precedence. * If both are defined, the non-const one takes precedence.
* *
* Unlike `std::function`, a `folly::Function` can wrap objects that are not * Unlike `std::function`, a `folly::Function` can wrap objects that are not
...@@ -42,6 +44,7 @@ ...@@ -42,6 +44,7 @@
* const-reference. * const-reference.
* *
* For example: * For example:
*
* class Foo { * class Foo {
* public: * public:
* void operator()() { * void operator()() {
...@@ -57,6 +60,7 @@ ...@@ -57,6 +60,7 @@
* foo_(); * foo_();
* } * }
* }; * };
*
* Even though `mutateFoo` is a const-method, so it can only reference `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 * as const, it is able to call the non-const `operator()` of the Foo
* object that is embedded in the foo_ function. * object that is embedded in the foo_ function.
...@@ -151,7 +155,7 @@ ...@@ -151,7 +155,7 @@
* Casting from a non-const to a const signature is potentially dangerous, * 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 * 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 * is made possible to call from a const context. Therefore this cast does
* not happen implicitly. The function `folly::constCastfolly::Function` can * not happen implicitly. The function `folly::constCastFunction` can
* be used to perform the cast. * be used to perform the cast.
* *
* // Mutable lambda: can only be stored in a non-const folly::Function: * // Mutable lambda: can only be stored in a non-const folly::Function:
...@@ -161,7 +165,7 @@ ...@@ -161,7 +165,7 @@
* *
* // const-cast to a const folly::Function: * // const-cast to a const folly::Function:
* folly::Function<void() const> print_number_const = * folly::Function<void() const> print_number_const =
* constCastfolly::Function(std::move(print_number)); * constCastFunction(std::move(print_number));
* *
* When to use const function types? * When to use const function types?
* Generally, only when you need them. When you use a `folly::Function` as a * Generally, only when you need them. When you use a `folly::Function` as a
...@@ -180,32 +184,32 @@ ...@@ -180,32 +184,32 @@
* *
* std::function<void(void)> stdfunc = someCallable; * std::function<void(void)> stdfunc = someCallable;
* *
* folly::Function<void(void) const> uniqfunc = constCastfolly::Function( * folly::Function<void(void) const> uniqfunc = constCastFunction(
* folly::Function<void(void)>(someCallable) * folly::Function<void(void)>(someCallable)
* ); * );
* *
* You need to wrap the callable first in a non-const `folly::Function` to * 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 * 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 * present), and then move it into a const `folly::Function` using
* `constCastfolly::Function`. * `constCastFunction`.
* The name of `constCastfolly::Function` should warn you that something * The name of `constCastFunction` should warn you that something
* potentially dangerous is happening. As a matter of fact, using * potentially dangerous is happening. As a matter of fact, using
* `std::function` always involves this potentially dangerous aspect, which * `std::function` always involves this potentially dangerous aspect, which
* is why it is not considered fully const-safe or even const-correct. * 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. * 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, * Either you do not require invokation of the function from a const context,
* in which case you do not need to use `constCastfolly::Function` and just * 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 * 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 * 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 * 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, * object and implements `operator() const`, or it is a normal,
* non-mutable lambda), in which case you can wrap the callable in a const * non-mutable lambda), in which case you can wrap the callable in a const
* `folly::Function` directly, without using `constCastfolly::Function`. * `folly::Function` directly, without using `constCastFunction`.
* Only if you require invokation from a const context of a callable that * 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. * 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 * However, in that case what you do is potentially dangerous and requires
* the equivalent of a `const_cast`, hence you need to call * the equivalent of a `const_cast`, hence you need to call
* `constCastfolly::Function`. * `constCastFunction`.
* *
* `folly::Function` also has two additional template paremeters: * `folly::Function` also has two additional template paremeters:
* * `NTM`: if set to `folly::FunctionMoveCtor::NO_THROW`, the * * `NTM`: if set to `folly::FunctionMoveCtor::NO_THROW`, the
......
`folly/Function.h`
------------------
`folly::Function` is a polymorphic function wrapper that is not copyable and does not require the wrapped function to be copy constructible. It is similar to `std::function`, but different with respect to some interesting features.
There are some limitations in `std::function` that `folly::Function` tries to avoid. `std::function` is copy-constructible and requires that the callable that it wraps is copy-constructible as well, which is a constraint that is often inconvenient. In most cases when using a `std::function` you don't make use of its copy-constructibility, so you might sometimes feel like you get back very little in return for a noticeable restriction.
This restriction becomes apparent when trying to use a lambda capturing a `unique_ptr` (or any non-copyable type) as a callback for a folly::Future.
``` Cpp
std::unique_ptr<Foo> foo_ptr = new Foo;
some_future.then(
[foo_ptr = std::move(foo_ptr)] mutable
(int x)
{ foo_ptr->setX(x); }
);
```
This piece of code did not compile before `folly::Future` started using `folly::Function` insted of `std::function` to store the callback. Because the lambda captures something non-copyable (the `unique_ptr`), it is not copyable itself. And `std::function` can only store copyable callables.
The implementation of folly::Future did not make use of the copy-constructibility of `std::function` at any point. There was no benefit from the fact that the `std::function` is copy-constructible, but the fact that it can only wrap copy-constructible callables posed a restriction.
A workaround was available: `folly::MoveWrapper`, which wraps an object that may be non-copyable and implements copy operations by moving the embedded object. Using a `folly::MoveWrapper`, you can capture non-copyable objects in a lambda, and the lambda itself is still copyable and may be wrapped in a `std::function`. It is a pragmatic solution for the above problem, but you have to be a little careful. The problem is that you can’t use a `MoveWrapper` anywhere where copy operations are assumed to behave like actual copy operations. Also, a `folly::MoveWrapper<std::unique_ptr<T>>` essentially behaves like `auto_ptr<T>`. Ask yourself whether you’d want to use lots of `auto_ptr`s in your codebase. And the original question still persists: we very often don’t benefit from copy-constructibility of `std::function`, so why do we have to live with this restriction? I.e. why do we have to use `MoveWrapper`?
`folly::Function` is an actual solution to this problem, as it can wrap non-copyable callables, at the cost of not being copy-constructible, which more often than not is not a relevant restriction. `folly::Future` now uses `folly::Function` to store callbacks, so the good news is: the code example from the top of this note is becoming a perfectly valid way to use future callbacks. The code compiles and behaves as you would expect.
Here are more details about `folly::Function`: much like `std::function`, it wraps an arbitrary object that can be invoked like a given function type. E.g. a `folly::Function<int(std::string, double)>` can wrap any callable object that returns an `int` (or something that is convertible to an `int`) when invoked with a `std::string` and a `double` argument. The function type is a template parameter of `folly::Function`, but the specific type of the callable is not. Also, like most implementations of `std::function`, `folly::Function` will store small callable objects in-place whereas larger callables will be stored on the heap. (Unlike `std::function`, you can set the size of the in-place storage as a template parameter of `folly::Function`.)
Other than copyability, there is one more significant difference between `std::function` and `folly::Function`, and it concerns const-correctness. `std::function does` not enforce const-correctness: it allows you to store mutable callables (i.e. callables that may change their inner state when executed, such as a mutable lambda) and call them in a const context (i.e. when you only have access to a const reference to the std::function object). For example:
``` Cpp
class FooBar {
public:
void call_func() const {
func_();
}
private:
std::function<void()> func_;
};
```
The `call_func` member function is declared const. However, when it calls `func_()`, it may change the inner state of `func_`, and thereby the inner state of the `FooBar` object. Inside the `FooBar` class, `func_` is like a non-const method that is callable from const methods.
Some people consider `std::function` in the standard broken with respect to this. (Paper N4348 explains this problem in more detail.) It also lists possible ways to fix the problem. `folly::Function`, however, goes a different way: you have to declare whether you want to store a const function, in which case you can invoke any reference (const or non-const) of the `folly::Function`, or a non-const (mutable) function, in which case you need a non-const reference to the `folly::Function` to be able to invoke it. In the above example, let’s say that `func_` stores a const function, which makes it okay that it gets invoked from `call_func` (a const method). Instead of `std::function`, you could use `folly::Function<void() const>` for the `func_` member.
Const-ness is part of a function type. To illustrate:
``` Cpp
class Foo {
public:
int operator()() { return 1; }
int operator()(char const*) { return 2; }
int operator()(int) { return 3; }
int operator()(int) const { return 4; }
int operator()(int, int) const { return 5; }
};
```
You can overload methods multiple times using different argument signatures. Const-ness is part of that signature, so even for the same set of argument types you can overload a const and a non-const version. It’s not even particularly unusual to do that. Take for instance the `begin()` method of STL container types: `begin()` returns an `iterator`, `begin() const` returns a `const_iterator`. `folly::Function` allows you to select a specific overload easily:
``` Cpp
folly::Function<int()> uf1 = Foo();
// uf1() returns 1
folly::Function<int(char const*)> uf2 = Foo();
// uf2() returns 2
folly::Function<int(int)> uf3 = Foo();
// uf3() returns 3
folly::Function<int(int) const> uf4 = Foo();
// uf4() returns 4
folly::Function<int(int, int) const> uf5 = Foo();
// uf5() returns 5
```
If `cfoo` is a const-reference to a `Foo` object, `cfoo(int)` returns 4. If `foo` is a non-const reference to a `Foo` object, `foo(int)` returns 3. Normal const-to-non-const conversion behaviour applies: if you call `foo(int, int)` it will return 5: a non-const reference will invoke the const method if no non-const method is defined. Which leads to the following behaviour:
``` Cpp
folly::Function<int(int, int)> uf5nc = Foo();
// uf5nc() returns 5
```
If you are wondering if the introduction of const function types means that you have to change lots of normal function types to const function types if you want to use `folly::Function`: not really, or at least not as much as you might think. There are only two reasons to use a `folly::Function` with a const function type:
* a callable object defines both const and non-const `operator()` and you explicitly want to select the const one
* you need to invoke a `folly::Function` from a const context (i.e. you only have a const reference to the `folly::Function`)
In practice, you will not need the const variant very often. Adding const to a function type adds a restriction for the callable: it must not change its inner state when invoked. If you don’t care whether it does or not, don’t worry about const!
A `folly::Function<R(Args...) const>` can be converted into a `folly::Function<R(Args...)>`: either way the stored callable will not change its inner state when invoked. The former type expresses and guarantees that, the latter does not. When you get rid of the const, the selected function stays the same:
``` Cpp
folly::Function<int(int)> uf4nc = std::move(uf4);
// uf4nc() returns 4, not 3!
```
If you want to go the other way, you are talking about a (potentially dangerous) const cast: a callable that may or may not change its inner state is declared as one that guarantees not to do that. Proceed at your own risk! This conversion does not happen implicitly, though:
``` Cpp
folly::Function<int() const> uf1c = folly::constCastFunction(std::move(uf1));
// uf1c() returns 1
```
Admittedly, seeing const function types as template parameters is unfamiliar. As far as I am aware it happens nowhere in the standard library. But it is the most consistent way to deal with the issue of const-correctness here. Const qualifiers are part of a function type, as a matter of fact. If you require a const-qualified function to be wrapped in a `folly::Function`, just declare it as that! More often than not you will find that you do not need the const qualifier. While writing the `folly::Function` implementation, a good set of unit tests had existed before the const function types got introduced. Not a single of those unit tests had to be changed: they all compiled and passed after the introduction of const function types. Obviously new ones were added to test the const-correctness. But in your day-to-day use of `folly::Function` you won’t have to worry about const very often.
...@@ -88,6 +88,10 @@ A C++ abstraction around files. ...@@ -88,6 +88,10 @@ A C++ abstraction around files.
Rabin fingerprinting. Rabin fingerprinting.
### [`Function.h`](Function.md)
A polymorphic wrapper for callables similar to `std::function` but not copyable and therefore able to wrap non-copyable callables, such as lambdas that capture move-only types like `std::unique_ptr` or `folly::Promise`.
### [`futures/`](folly/futures/Readme.md) ### [`futures/`](folly/futures/Readme.md)
Futures is a framework for expressing asynchronous code in C++ using the Promise/Future pattern. Futures is a framework for expressing asynchronous code in C++ using the Promise/Future pattern.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment