Search code examples
c++c++11std-functionperfect-forwarding

using a std::function or a forwarding reference as general-purpose callable object input parameter of a higher-order function?


I was wondering about the main differences, pros and cons in writing a higher order function taking, as input parameter, a std::function or a forwarding reference, e.g. template<typename F> void hof(F&& fun);. Obviously, the former is more strict than the latter in that it specifies the function type that the input callable object has to conform to.


Solution

  • std::function has a lot of pros, but it has also a series of cons that you must take into account.
    As an example:

    • The callable object should be copy constructible.
      In other terms, this compiles:

      #include <functional>
      #include <utility>
      #include <memory>
      
      template<typename F>
      void func(F &&f) { std::forward<F>(f)(); }
      
      int main() {
          func([ptr = std::make_unique<int>()](){});
      }
      

      But this does not:

      #include <functional>
      #include <utility>
      #include <memory>
      
      void func(std::function<void(void)> f) { f(); }
      
      int main() {
          func([ptr = std::make_unique<int>()](){});
      }
      
    • Even though (emphasis mine):

      Implementations are encouraged to avoid the use of dynamically allocated memory for small callable objects, for example, where f's target is an object holding only a pointer or reference to an object and a member function pointer.

      You have no guarantees that you won't have allocations on the dynamic storage when they could be avoided, and you'll have allocations for sure in all the other cases.

    • When you construct a std::function, the constructor may throw a bad_alloc.

    • ... Probably we can continue, but it doesn't worth it, you got the point.

    std::functions are in incredible useful tool, but you should use them when you need them.
    As an example, if you plan to store your function somewhere, likely you will end up using a std::function. On the other side, if you plan to accept a callable object and invoke it on the fly, probably you won't use a std::function.
    These are only a few examples, a golden rule doesn't exist unfortunately.