在什么情况下我必须使用 std::function?

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

我很难想象模板无法涵盖的 std::function 的真正用例。每次考虑使用std::function,我都会想办法避免它:

// 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]);
    }
}

不可否认,使用std::function的实现有点短,但由于类型擦除,每次迭代都需要一个虚拟调用,编译器不能很好地优化它。 (Live example)

那么 std::function 不能使用模板的真正用例是什么? std::function 有必要吗?

以class中的虚方法为例。不允许带有模板参数的虚拟方法

class A : public Base {

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

std::function type-erases 一种可调用的类型,并且可以将它们同等对待。

例如,您可以有一个回调向量:

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

function 提供的是类型擦除。类型擦除在模板不合适甚至不可行的地方很有用。类型擦除的典型用例是代码中有两个地方,A 和 B。A 需要向 B 发送一些东西。但是这种发送需要通过中间代码 C 发生。

并且 C 不能是模板,因为 C 是用于存储或传递某些数据的通用介质。考虑一个 signal-and-slot 系统。插槽的特定用法需要特定的可调用签名,这是信号器发送的内容。现在,插槽的创建 可以 是一个基于您提供的可调用类型的模板。但是你不能在单个槽中有 多个 回调,因为你需要能够存储它们的数组以按顺序调用。您不能存储对象的异构容器。因此插槽需要以一种可以调用它的方式存储可调用对象 而无需 必须绑定到特定的可调用对象类型。指定了签名,而不是实际的可调用对象。

输入擦除类型。

std::function 和类似的 type-erased 类型用于当您的界面和内部实现 不能 出于任何原因成为模板时。这可能是因为您需要存储一个容器,其中包含可能具有共享接口的不同类型的对象。可能是因为中间代码建立在 C-style 接口上,实际上无法处理模板。或者您可能不想将包含通用可调用对象的类型制作成模板,以便用户可以提供任意可调用对象(模板不是免费的,尤其是 compile-time)。或其他各种因素。

但一般的共性是对象的提供者和对象的使用者之间的中间代码不能是模板。