将lambda传递给函数模板

时间:2016-10-25 08:30:09

标签: c++ c++11 templates lambda function-pointers

我正在学习C ++,而我正在尝试实现一个二进制搜索函数,它找到谓词所在的第一个元素。函数的第一个参数是向量,第二个参数是一个计算给定元素的谓词的函数。二进制搜索功能如下所示:

template <typename T> int binsearch(const std::vector<T> &ts, bool (*predicate)(T)) {
    ...
}

如果像这样使用,这可以正常工作:

bool gte(int x) {
    return x >= 5;
}

int main(int argc, char** argv) {
    std::vector<int> a = {1, 2, 3};
    binsearch(a, gte);
    return 0;
}

但是如果我使用lambda函数作为谓词,我会收到编译错误:

search-for-a-range.cpp:20:5: error: no matching function for call to 'binsearch'
    binsearch(a, [](int e) -> bool { return e >= 5; });
    ^~~~~~~~~
search-for-a-range.cpp:6:27: note: candidate template ignored: could not match 'bool (*)(T)' against '(lambda at
      search-for-a-range.cpp:20:18)'
template <typename T> int binsearch(const std::vector<T> &ts,
                          ^
1 error generated.

上述错误由

生成
binsearch(a, [](int e) -> bool { return e >= 5; });

出了什么问题?为什么编译器不相信我的lambda具有正确的类型?

5 个答案:

答案 0 :(得分:18)

您的函数binsearch将函数指针作为参数。 lambda和函数指针是不同的类型:lambda可以被视为实现operator()的结构的实例。

请注意,无状态lambdas(不捕获任何变量的lambda)可以隐式转换为函数指针。由于模板替换,隐式转换在这里不起作用:

#include <iostream>

template <typename T>
void call_predicate(const T& v, void (*predicate)(T)) {
    std::cout << "template" << std::endl;
    predicate(v);
}

void call_predicate(const int& v, void (*predicate)(int)) {
    std::cout << "overload" << std::endl;
    predicate(v);
}

void foo(double v) {
    std::cout << v << std::endl;
}

int main() {
    // compiles and calls template function
    call_predicate(42.0, foo);

    // compiles and calls overload with implicit conversion
    call_predicate(42, [](int v){std::cout << v << std::endl;});

    // doesn't compile because template substitution fails
    //call_predicate(42.0, [](double v){std::cout << v << std::endl;});

    // compiles and calls template function through explicit instantiation
    call_predicate<double>(42.0, [](double v){std::cout << v << std::endl;});
}

你应该让你的函数binsearch更通用,例如:

template <typename T, typename Predicate>
T binsearch(const std::vector<T> &ts, Predicate p) {

    // usage

    for(auto& t : ts)
    {
        if(p(t)) return t;
    }

    // default value if p always returned false

    return T{};
}

standard algorithms library获取灵感。

答案 1 :(得分:12)

带有空捕获列表的lambda expression可以隐式转换为函数指针。但是函数指针predicateT作为参数,需要推导出来。在模板类型扣除中不会考虑类型转换,T无法推断出来;正如错误消息所述,候选模板(即binsearch)被忽略。

您可以使用operator+来实现这一点,它会将lambda转换为函数指针,稍后会将其传递给binsearch,然后成功推导T [1]

binsearch(a, +[](int e) -> bool { return e >= 5; });
//           ~

当然,您可以明确使用static_cast

binsearch(a, static_cast<bool(*)(int)>([](int e) -> bool { return e >= 5; }));

请注意,如果您将predicate的类型更改为独立于T,即bool (*predicate)(int),则将带有空捕获列表的lambda传递也会起作用; lambda表达式将隐式转换为函数指针。

另一个解决方案是将参数类型从函数指针更改为std::function,这对于仿函数更为通用:

template <typename T> int binsearch(const std::vector<T> &ts, std::function<bool (typename std::vector<T>::value_type)> predicate) {
    ...
}

然后

binsearch(a, [](int e) -> bool { return e >= 5; });

[1] A positive lambda: '+[]{}' - What sorcery is this?

答案 2 :(得分:8)

  

为什么编译器不相信我的lambda具有正确的类型?

模板函数被告知推断其模板参数不进行转换。 lambda不是函数指针,因此无法推导出该参数中的T。由于所有函数参数都独立地推导出它们的模板参数(除非扣除被扣除),否则会导致错误。

您可以执行许多修复。

您可以修复模板功能。

template <class T>
int binsearch(const std::vector<T> &ts, bool (*predicate)(T))

将函数指针替换为Predicate predicatePredicate&& predicate并保持正文不变。

template <class T, class Predicate>
int binsearch(const std::vector<T> &ts, Predicate&& predicate)

使用扣减阻止:

template<class T>struct tag_t{using type=T;};
template<class T>using block_deduction=typename tag_t<T>::type;
template <class T>
int binsearch(const std::vector<T> &ts, block_deduction<bool (*)(T)> predicate)

可选地在用std::function<bool(T)>替换函数指针时。

您可以在通话网站修复它。

您可以手动T传递binsearch<T>(vec, [](int x){return x<0;})

您可以将lambda衰减为函数指针,并在其前面放置+ +[](int x) ...或static_cast<bool(*)(int)>( ... )

最佳选择是Predicate一个。这也是标准库代码的作用。

我们还可以更进一步,让您的代码更加通用:

template <class Range, class Predicate>
auto binsearch(const Range &ts, Predicate&& predicate)
-> typename std::decay< decltype(*std::begin(ts)) >::type

可以在C ++ 14中删除-> typename std::decay ...尾部返回类型部分。

这样做的一个好处是,如果正文还使用std::beginstd::end来查找开始/结束迭代器,binsearch现在支持deque s,扁平的C风格数组,std::array s,std::string s,std::vector s,甚至是一些自定义类型。

答案 3 :(得分:3)

如果你对binsearch有任何控制权,我建议你重构一下:

template <typename T, typename Predicate>
int binsearch(std::vector<T> const& vec, Predicate&& pred) {
    // This is just to illustrate how to call pred
    for (auto const& el : vec) {
        if (pred(el)) {
            // Do something
        }
    }
    return 0; // Or anything meaningful
}

另一种方法是通过将它们嵌入std::function<bool(T const&)>来对函子对象/函数指针/无论什么进行类型擦除。为此,只需将上面的函数重写为:

template <typename T>
int binsearch(std::vector<T> const& vec, std::function<bool(T const&)> pred);

但由于模板参数推断不进行任何转换,因此您需要显式提供您的函数,如下所示:

auto my_predicate = [](int x) { return true; }; // Replace with actual predicate
std::vector<int> my_vector = {1, 2, 3, 4};
binsearch(my_vector, std::function<bool (int const&)>(my_predicate));

但是,鉴于您的功能描述,它似乎与std::find_if完成相同的工作。

std::vector<int> my_vector = {1, 12, 15, 13, 16};
auto it = std::find_if(std::begin(my_vector), std::end(my_vector),
                       [](int vec_el) { return !vec_el%5; });
// it holds the first element in my_vector that is a multiple of 5.
if (it != std::end(my_vector)) {
    std::cout << *it << std::endl; // prints 15 in this case
}

请注意,要进行二元搜索,您需要的不仅仅是谓词:您需要一个谓词来定义范围和目标值的顺序。

答案 4 :(得分:0)

函数指针和lambda函数不是一回事。

无法将对象t分配给谓词where:

 bool (*predicate)(int)

auto t = [](int e) -> bool { return e >= 5; });

也可以使用std::function<bool(int)>。您的签名将如下所示:

template <typename T> 
int binsearch(const std::vector<T> &ts, std::function<bool(T)> predicate){
   // ...
}

现在这不是一个函数指针,如果必要的话,你需要绑定你的指针指针(我假设你只对lambdas很好)