Skip to content

Functions

There are many ways to store a reference to a function. This document gives an overview of them and gives recommendations for when to use each approach.

  1. Pass function pointer and user data (as void *) separately:
    • The only method that is compatible with C interfaces.
    • Is cumbersome to work with in many cases, because one has to keep track of two parameters.
    • Not type safe at all, because of the void pointer.
    • It requires workarounds when one wants to pass a lambda into a function.
  2. Using std::function:
    • It works well with most callables and is easy to use.
    • Owns the callable, so it can be returned from a function more safely than other methods.
    • Requires that the callable is copyable.
    • Requires an allocation when the callable is too large (typically > 16 bytes).
  3. Using a template for the callable type:
    • Most efficient solution at runtime, because compiler knows the exact callable at the place where it is called.
    • Works well with all callables.
    • Requires the function to be in a header file.
    • It's difficult to constrain the signature of the function.
  4. Using blender::FunctionRef (source):
    • Second most efficient solution at runtime.
    • It's easy to constrain the signature of the callable.
    • Does not require the function to be in a header file.
    • Works well with all callables.
    • It's a non-owning reference, so it cannot be stored safely in general.

The following diagram helps to decide which approach to use when building an API where the user has to pass a function.

flowchart TD
  is_called_from_c["Is called from C code?"]
  is_stored_when_function_ends["Is stored when function ends?"]
  is_call_performance_bottleneck["Call is performance bottleneck?"]
  use_function_pointer["Use function pointer and user data (approach 1)."]
  use_std_function["Use `std::function` (approach 2)."]
  use_template["Use `template<typename Fn>` (approach 3)."]
  use_function_ref["Use `blender::FunctionRef` (approach 4)."]


  is_called_from_c --"yes"--> use_function_pointer
  is_called_from_c --"no"--> is_stored_when_function_ends
  is_stored_when_function_ends --"yes"--> use_std_function
  is_stored_when_function_ends --"no"--> is_call_performance_bottleneck
  is_call_performance_bottleneck --"no"--> use_function_ref
  is_call_performance_bottleneck --"yes"--> use_template

blender::FunctionRef is preferred over std::function when both are applicable because it's cheaper.

  • It never requires an allocation, no matter how many variables are captured.
  • It's less expensive to call.
  • It's less expensive to move/copy around.