如何绕过 C++ 无法将模板中的函数类型与 lambda 匹配

Ant*_*kov 2 c++ lambda templates functional-programming template-argument-deduction

如果您有以下过滤列表模板:

template <typename T>
inline std::list<T> list_filter(std::list<T> const& a, bool (f)(T)) {
    std::list<T> output;
    std::copy_if(a.begin(), a.end(), std::back_inserter(output), f);
    return output;
}
Run Code Online (Sandbox Code Playgroud)

然后尝试使用里面的 lambda 调用它,例如:

std::list<int> lst = {1,2,3,4,5};
auto filtered = list_filter(lst, [](int el) -> bool { return (el % 2 == 0); });
Run Code Online (Sandbox Code Playgroud)

它将产生错误no matching function for call to list_filter(..., std::__cxx11::list<int>)::<lambda(int)>)'

有没有办法绕过这个限制而不将 lambda 提取到单独的函数中?为什么 C++ 不允许这种明显的模式?

son*_*yao 6

模板参数推导中不会考虑隐式转换(从没有捕获列表的 lambda 到函数指针),它无法T在第二个函数参数上推导出模板参数。

类型推导不考虑隐式转换(上面列出的类型调整除外):这是重载解析的工作,稍后会发生。

您可以通过static_cast, 或将 lambda 显式转换为函数指针operator+。例如

auto filtered = list_filter(lst, +[](int el) -> bool { return (el % 2 == 0); });
Run Code Online (Sandbox Code Playgroud)

或者使用std::type_identity(C++20 起)从推导中排除第二个函数参数。例如

template <typename T>
inline std::list<T> list_filter(std::list<T> const& a, bool (f)(std::type_identity_t<T>)) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,type_identity如果您的编译器不支持 C++20,您可以轻松制作自己的。

  • @AntonKochkov 是的,caputring lambda 无法转换为函数指针。您可以将函数参数从函数指针更改为`std::function`,(与函数指针相同的推导问题,也需要上面的解决方案,即使用`std::function&lt;bool (std::type_identity_t&lt;T&gt;)&gt; `),或者按照 @someprogrammerdude 建议直接传递 lambda。 (3认同)
  • @Const 有关更多信息,请参阅[非推导上下文](https://en.cppreference.com/w/cpp/language/template_argument_deduction#Non-deduced_contexts)。 (2认同)
  • @Const如果您感兴趣,我最近写了[一篇简短的博客文章](https://dfrib.github.io/non-deduced-contexts/),特别是使用类型标识映射通过删除来控制模板参数推导通过将它们置于非推导的上下文中来进行论证。 (2认同)