【问题标题】:Pass lambda as template function parameter将 lambda 作为模板函数参数传递
【发布时间】: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.不幸的是,将参数更改为指针会产生相同的错误。

标签: c++ c++11 templates lambda


【解决方案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、函数或函数对象。

        【讨论】: