proxygen
Function Class Reference

A polymorphic function wrapper that is not copyable and does not require the wrapped function to be copy constructible. More...

#include <Function.h>

Detailed Description

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::Functions: 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.


The documentation for this class was generated from the following file: