Jan Schultke
Jan Schultke

Reputation: 39804

In what situation do I have to use a std::function?

It's hard for me to imagine a genuine use case for std::function that couldn't be covered by a template. Every time I consider using std::function, I find a way to avoid it:

// implementation using std::function
void forEach(std::array<int, 100> &data, const std::function<void(int&)> &f)
{
    for (size_t i = 0; i < 100; ++i) {
        f(data[i]);
    }
}

// implementation using any functor which takes an int&
template <typename Callable>
void forEach(std::array<int, 100> &data, const Callable &f)
     requires std::is_invocable_v<Callable, int&>
{
    for (size_t i = 0; i < 100; ++i) {
        f(data[i]);
    }
}

Admittedly, the implementation using std::function is a bit shorter, but due to type erasure it requires a virtual call each iteration and the compiler can't optimize it well. (Live example)

So what would be a genuine use case for std::function where a template couldn't be used instead? Is there any need for std::function at all?

Upvotes: 1

Views: 448

Answers (3)

Nicol Bolas
Nicol Bolas

Reputation: 474116

What function offers is type erasure. And type erasure is useful in places where templates are not appropriate or even viable. The typical use cases for type erasure are when there are two places in code, A and B. A needs to send something to B. But this sending of that something needs to happen through intermediate code C.

And C can't be a template because C is a generic medium for storing or passing certain data. Consider a signal-and-slot system. A particular usage of a slot expects a particular callable signature, which is what the signaller sends. Now, the creation of a slot could be a template based on the type of the callable you provide. But then you couldn't have multiple callbacks in a single slot, since you need to be able to store an array of them to call in sequence. You can't store a heterogeneous container of objects. So the slot needs to store the callable in a way that it can call it without having to be bound to a specific callable object type. The signature is specified, not the actual callable.

Enter type erasure.

std::function and similar type-erased types are for when your interface and internal implementation cannot be a template for whatever reason. It could be because you need to store a container of objects of potentially different types that have a shared interface. It could be because the intermediate code is built on a C-style interface that can't actually handle templates. Or maybe you don't want to make the type which contains a generic callable a template just so that the user can provide an arbitrary callable (templates aren't free, especially at compile-time). Or various other factors.

But the general commonality is that the intermediate code between the provider of the object and the user of it cannot be a template.

Upvotes: 2

rustyx
rustyx

Reputation: 85481

std::function type-erases a callable type and makes it possible to treat them homogenously.

For example, you can have a vector of callbacks:

std::vector<std::function<int(std::string)>> callbacks;

Upvotes: 5

Defter
Defter

Reputation: 214

For virtual methods in class as example. Virtual methods with template arguments is not allowed

class A : public Base {

public:
  virtual void forEach(std::function<void(int&)> f);
}

Upvotes: 3

Related Questions