将 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 (&amp;)(const From&amp;)。 在您的情况下,非捕获 lambda 可以衰减到 To (*)(const From&amp;)

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&lt;[…]&gt;::type 进行模板元编程忍者以获得我需要的F 的返回类型(我不知道该怎么做获得它的模板魔法)。另请注意,*+ 不是必需的,* 就足够了。【参考方案3】:

如果我错了,请纠正我,但模板参数推导只推导出确切的类型,而不考虑可能的转换。

因此,编译器无法为 To (&amp;)(const From&amp;) 推断出 ToFrom,因为 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 表达式的类型是一些 uniqueunnamed 类型。不管那种类型是什么,它绝对不是To(const From&amp;)——所以推论失败了。


如果我将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 作为模板函数参数传递的主要内容,如果未能解决你的问题,请参考以下文章

将 lambda 作为参数传递 - 通过引用或值?

将 lambda 作为模板参数传递给函数指针函数模板化

如何将 lambda 表达式作为参数传递给 c++ 模板

将 lambda 传递给函数模板

Lambda 作为函数参数

将Lambda表达式作为参数传递并解析-在构造函数参数列表中使用Lambda表达式