将 lambda 作为模板函数参数传递
Posted
技术标签:
【中文标题】将 lambda 作为模板函数参数传递【英文标题】:Pass lambda as template function parameter 【发布时间】:2017-06-14 17:22:27 【问题描述】:为什么以下代码无法编译(在 C++11 模式下)?
#include <vector>
template<typename From, typename To>
void qux(const std::vector<From>&, To (&)(const From&))
struct T ;
void foo(const std::vector<T>& ts)
qux(ts, [](const T&) return 42; );
错误信息是:
prog.cc:9:5: error: no matching function for call to 'qux'
qux(ts, [](const T&) return 42; );
^~~
prog.cc:4:6: note: candidate template ignored: could not match 'To (const From &)' against '(lambda at prog.cc:9:13)'
void qux(const std::vector<From>&, To (&)(const From&))
^
但它并没有解释为什么它不能匹配参数。
如果我将qux
设为非模板函数,将From
替换为T
并将To
替换为int
,它将编译。
【问题讨论】:
首先qux
没有返回任何东西,所以这不应该编译auto bar = qux...
@nbro 这与手头的问题无关。
如果你知道这么多,你就不会问问题了。无论如何,赞成,因为我也不知道为什么。
非捕获 lambda 可以转换为函数指针,而不是函数引用。
@n.m.不幸的是,将参数更改为指针会产生相同的错误。
【参考方案1】:
lambda 函数不是普通函数。每个 lambda 都有自己的类型在任何情况下都不是To (&)(const From&)
。
在您的情况下,非捕获 lambda 可以衰减到 To (*)(const From&)
:
qux(ts, +[](const T&) return 42; );
如 cmets 中所述,从 lambda 中取出它的最佳方法是:
#include <vector>
template<typename From, typename To>
void qux(const std::vector<From>&, To (&)(const From&))
struct T ;
void foo(const std::vector<T>& ts)
qux(ts, *+[](const T&) return 42; );
int main()
注意:我假设推断返回类型和参数类型对于真正的问题是强制性的。否则你可以很容易地将整个 lambda 推导出为一个通用的可调用对象并直接使用它,无需衰减任何东西。
【讨论】:
谢谢!在 lambda 前面加上*+
可以让我通过 ref 传递它。一定要爱 C++……
其实*
就够了,*+
就不需要了。
如何将 lambda 分配给 std::function 并将其作为模板参数传递?
@madduci 如果不是严格要求,你不应该仅仅因为它存在就使用包装器。
@madduci 不,真的。如果您不需要使用std::function
,则使用它不会使您的代码更干净。错了,仅此而已。【参考方案2】:
如果你不需要使用推导的To
类型,你可以只推导整个参数的类型:
template<typename From, typename F>
void qux(const std::vector<From>&, const F&)
【讨论】:
是的,这是推荐的方法。 @DanielJour 这有一个不幸的副作用,即必须使用std::result_of<[…]>::type
进行模板元编程忍者以获得我需要的F
的返回类型(我不知道该怎么做获得它的模板魔法)。另请注意,*+
不是必需的,*
就足够了。【参考方案3】:
如果我错了,请纠正我,但模板参数推导只推导出确切的类型,而不考虑可能的转换。
因此,编译器无法为 To (&)(const From&)
推断出 To
和 From
,因为 qux
需要对函数的引用,但您提供了一个具有自己类型的 lambda。
【讨论】:
【参考方案4】:您绝对没有机会让编译器猜测To
是什么。因此,您需要明确指定它。
另外,这里的lambda需要通过指针传递。
终于,这个版本编译ok了:
template<typename From, typename To>
void qux(const std::vector<From>&, To (*)(const From&))
struct T ;
void foo(const std::vector<T>& ts)
qux<T,int>(ts,[](const T&) return 42; );
【讨论】:
【参考方案5】:您期望同时发生隐式类型转换(从未命名函数对象类型到函数引用类型)和模板类型推导。但是,you can't have both,因为您需要知道目标类型才能找到合适的转换顺序。
【讨论】:
【参考方案6】:但它并没有解释为什么它不能匹配参数。
模板推导尝试完全匹配类型。如果不能推导出类型,则推导失败。从不考虑转换。
在这个表达式中:
qux(ts, [](const T&) return 42; );
lambda 表达式的类型是一些 unique,unnamed 类型。不管那种类型是什么,它绝对不是To(const From&)
——所以推论失败了。
如果我将
qux
设为非模板函数,将From
替换为T
并将To
替换为int
,它将编译。
那不是真的。但是,如果参数是函数的指针,而不是函数的引用,那么它就是。这是因为没有捕获的 lambda 可以隐式转换为等效的函数指针类型。这种转换是允许在扣除范围之外进行的。
template <class From, class To>
void func_tmpl(From(*)(To) )
void func_normal(int(*)(int ) )
func_tmpl([](int i)return i; ); // error
func_tmpl(+[](int i)return i; ); // ok, we force the conversion ourselves,
// the type of this expression can be deduced
func_normal([](int i)return i; ); // ok, implicit conversion
这与失败的原因相同:
template <class T> void foo(std::function<T()> );
foo([] return 42; ); // error, this lambda is NOT a function<T()>
但这成功了:
void bar(std::function<int()> );
bar([] return 42; ); // ok, this lambda is convertible to function<int()>
首选方法是推断可调用对象的类型并使用std::result_of
挑选出结果:
template <class From,
class F&&,
class To = std::result_of_t<F&&(From const&)>>
void qux(std::vector<From> const&, F&& );
现在您可以很好地传递您的 lambda、函数或函数对象。
【讨论】:
以上是关于将 lambda 作为模板函数参数传递的主要内容,如果未能解决你的问题,请参考以下文章