2017-04-23 68 views
4

我有一个冒泡排序函数,它需要一个数组,一个比较函数和一个布尔值来指示它是否应该颠倒数组。它是一种支持任何数据类型的模板函数,并会自动推导出数组大小。为什么在传递lambda时不能推导出模板参数而不是函数指针

指定比较函数时,如果我传递函数指针,编译器会自动推导出数组的数据类型,这很好。但是如果我传递lambda表达式,它不会自动推断出来。我必须明确指定数据类型,或者将lambda的static_cast指定为fnCompare_t<double>

这是什么原因?因为根据this post,只要lambda没有捕获,它可以像普通旧函数指针一样使用,但似乎并非总是如此?在这种情况下它怎么会有所不同?

#include <iostream> 
using namespace std; 

template <typename T> 
using fnCompare_t = int(*)(T const &, T const &); 

template <typename T, size_t count> 
inline void BubbleSort(
    T(&array)[count], 
    fnCompare_t<T> fnCompare, 
    bool reverse) 
{ 
    cout << "TODO: Implement BubbleSort" << endl; 
} 

double doubleArray[] = { 
    22.3, 11.2, 33.21, 44.2, 91.2, 15.2, 77.1, 8.2 
}; 

int CompareDouble(double const & a, double const & b) 
{ 
    return a > b ? 1 : a == b ? 0 : -1; 
} 

int main() 
{ 
    auto fnCompare = [](double const & a, double const & b) -> int { 
     return a > b ? 1 : a < b ? -1 : 0; 
    }; 
    // compile OK: 
    BubbleSort(doubleArray, CompareDouble, false); 
    BubbleSort(doubleArray, static_cast<fnCompare_t<double>>(fnCompare), false); 
    BubbleSort<double>(doubleArray, fnCompare, false); 
    // compile error, could not deduce template argument: 
    //BubbleSort(doubleArray, fnCompare, false); 
    return 0; 
} 
+0

[为什么要使用命名空间std坏习惯](http://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice) –

回答

5

的原因是因为你不能使用时,扣在模板参数获得的隐式转换。典型的例子是:

template <class T> 
T min(T x, T y); 

调用此功能min(1, 3.0)会导致失败。因为对于这两个参数,它试图找到一个T以获得完美匹配,并失败。如果明确指定模板参数,则它可以工作:min<double>(1, 3.0)。您的示例中也是如此,如果您明确指定T,它将起作用。

写入签名的功能的惯用方法是:

template <typename Iter, typename F> 
inline void BubbleSort(
    Iter b, Iter e, 
    F fnCompare, 
    bool reverse) 

然而,这种丢弃编译时间长度的信息。如果你想保住你的,你可以这样做:

template <typename T, size_t count, typename F> 
inline void BubbleSort(
    T(&array)[count], 
    F fnCompare, 
    bool reverse); 

虽然你至少应该考虑使用std::array,而不是一个C风格的数组,这将使签名少一点丑陋,但有其他好处。

这可能看起来很奇怪,因为我们没有在我们的签名中“验证”具有正确签名的比较器。但是这在C++中是正常的,如果比较器不正确,那么它在使用时会失败并且仍然是编译时错误。注意,当你试图依赖一个lambda隐式地转换成一个函数指针时,你会受到不必要的限制:lambda只能转换成与相同的签名的函数指针。即使lambda的输出可以隐式转换为函数指针的输出,您的lambda也不会隐式转换,即使lambda仍然可以使用!

作为最后的最后一个注意事项,传递函子通常更好,因为它对性能更好。比较器通常是小功能,通常会被内联。但是在你的版本中,比较器通常不会被内联,它会(因为我保留了lambda的原始类型,而你没有)。

+0

更现代的签名将是'template < typename Range,typename F> void BubbleSort(Range&rng,F fnCompare,bool reverse)'。在函数定义中,可以使用'begin(rng)'和'end(rng)'来获得迭代器。 – zett42

+0

@ zett42它被标记为C++ 14。即使不是这样,C++ 17也只是标准化的,几乎不可能用于任何地方。在17范围甚至没有标准化!这是至少在接下来的3年内,即使对于流血流血的人来说,正确的,现代的,标准的签名。 –

+0

您不需要任何C++ 17,甚至不需要C++ 14工具来编写像这样的算法。 – zett42

1

您需要明确地将lambda转换为函数指针。没有其他办法。但是,不是static_cast荷兰国际集团可以申请+的拉姆达,这将触发函数指针转换,你可以申请+为指针类型:

BubbleSort(doubleArray, +fnCompare, false); 
//      ^^ 
//  applying unary + invokes the function pointer conversion operator 

原因为何没有到无隐式调用转换操作符是在重载解析期间,编译器将只考虑完全匹配的模板(有关更多信息,请参阅this)。由于lambda不是函数指针,因此不能完美匹配,并且过载被丢弃。

+0

在GCC中工作,但在MSVC2015中出现此错误:'错误C2593:'operator +'含糊不清,注意:可能是'内置C++运算符+(main :: :: )'。 。' – raymai97

+0

@ raymai97这很糟糕。 VS2015没有很好的C++ 11/14支持,这可以解释这种模糊性。也许它在VS 2017中修复? – Rakete1111

+0

@ raymai97,很确定这是因为MSVC有一些事情正在调用约定和lambda表达式,所以你可以使用它们作为winapi回调和whatnot。 – chris

相关问题