模板函数无法识别自动变量引用的 lambda

Template function cannot recognize lambda referred by an auto variable

在 c++17 中,我有一个模板函数,它接受某种 lambda 作为输入。但是它只识别那些具有显式类型的和那些使用 auto 的被拒绝。

为什么会这样,有什么方法可以将自动变量和模板函数结合起来,以指定形式的 lambda 作为输入?

#include <iostream>
#include <vector>
#include <functional>
using namespace std;

class C {};
vector<C> vec(2);

// ForEach func requires lambda must take Object with type C
// only, while its return type can vary as the function is templated.
template<typename T>
void ForEach(function<T (C a)>& lambda) {
    std::for_each(begin(vec), end(vec), lambda);
};

int main()
{
    auto
    f_add_display4 = [](C i) {
        };
    std::function<void(C)>
    f_add_display3 = f_add_display4;

    ForEach(f_add_display3);
    // ForEach(f_add_display4); // This line won't compile
}

std 函数不是 lambda,lambda 也不是 std 函数。

您的函数采用标准函数。因此,当传递给它时,它会将模板参数推导到 std 函数。如果你传递一个 lambda,它不能推导出任何东西。

其次,std函数是类型擦除类型,不是接口。您的函数试图推导类型擦除 class 的模板参数。这样做是一种反模式。

template<class F>
void ForEach(F lambda) {
  std::for_each(begin(vec), end(vec), lambda);
}

这有效。

如果你想重构 F 以接受一些签名,在 中你可以这样做:

void ForEach(std::invocable<C> auto lambda) {
  std::for_each(begin(vec), end(vec), lambda);
}

:

template<std::invocable<C> F>
void ForEach(F lambda) {
  std::for_each(begin(vec), end(vec), lambda);
}

这里其实有两个基本问题导致推导失败:

  1. 首先,对于任何签名 Signature,lambda 表达式的类型永远不会是 std::function<Signature>。但是,您的函数需要一个非 const 引用参数。由于类型不同,需要进行转换,这将是一个临时对象,而临时对象永远不会绑定到非 const 引用。您可以通过使用 const 引用作为参数来解决此问题:

    template <typename T>
    void ForEach(function<T(C)> const& lambda) { ... }
    
  2. 另一个问题是 ForEach 采用了一组概念上开放的潜在论点。但是,您拥有的参数不是直接匹配:无法根据 lambda 类型推断 T 类型以与函数参数完全匹配。相反,需要进行转换。编译器不会试图找到哪个实例可以作为实例化的目标,尽管在这种情况下只有一个选择。如果您指定目标类型(并做出先前选择使参数 const&:

    ForEach<void>(f_add_display4);
    

我建议不要 约束函数以function<T(C)> 开始!与使用实际的 lambda 函数相比,这很可能是一种悲观:虽然 lambda 函数是静态类型的,并且通常可以很好地优化,但 std::function<Signaure> 却并非如此。虽然后者有时可以优化,但通常不是。如果你想限制函数只接受 C 类型的参数,你可以用其他一些方法来做到这一点,可能涉及 C++17 编译器的 SFINAE 或 C++20+ 的概念。

也就是说,我建议使用

template <typename Fun>
void ForEach(Fun&& lambda) {
    ...
}

...的,如果你想使用C++20的概念来约束函数

template <typename Fun>
    requires requires (Fun lambda, C c) { lambda(c); }
void ForEach(Fun&& lambda) {
    ...
}

f_add_display3 的类型不是 std::function<void(C)>。它是某种未命名的 class 类型(某些 class 带有 void operator()(C i))。

您可以像在此处一样将 lamda 转换为 std::function<void(C)>

std::function<void(C)> f_add_display3 = f_add_display4;

不过,模板参数推导不考虑隐式转换。

如果将参数更改为 const 引用,则可以显式指定模板参数:

template<typename T>
void ForEach(const function<T (C a)>& lambda) {
      // ...
};
ForEach<void>(f_add_display4); // This line will compile !

或者您完全放弃对 std::function 的不必要转换:

template <typename F>
void ForEach(F f) {
    std::for_each(begin(vec), end(vec),f);
};