lambda是一种可调用对象,它是一个对象,每一个lambda都有本身不一样的类型。ios
年轻时觉得STL和lambda混用时会有一些奇怪现象,好比我没法像这样定义优先队列:c++
priority_queue<int, vector<int>, [](int a, int b) {return a > b;}> que;
可是却能够这样用sortexpress
sort (vec.begin(), vec.end(), [](int a, int b) {return a < b;});
以及能够这样用sort数组
bool less(int a, int b) { return a < b;} typedef bool (*cmp) (int, int); cmp mycmp = less; sort (vec.begin(), vec.end(), mycmp); sort (vec.begin(), vec.end(), less);
之因此会出现这样的疑问,是由于没有搞清楚函数对象 (也叫可调用对象) 和 模板的类型参数之间的关系, 首先说明如何正确的使用 lambda 对象来实例化priority_queue :less
#include <iostream> #include <queue> #include <vector> #include <utility> using my_pair_t = std::pair<size_t,bool>; using my_container_t = std::vector<my_pair_t>; int main() { auto my_comp = [](const my_pair_t& e1, const my_pair_t& e2) { return e1.first > e2.first; }; std::priority_queue<my_pair_t, my_container_t, decltype(my_comp)> queue(my_comp); queue.push(std::make_pair(5, true)); queue.push(std::make_pair(3, false)); queue.push(std::make_pair(7, true)); std::cout << std::boolalpha; while(!queue.empty()) { const auto& p = queue.top(); std::cout << p.first << " " << p.second << "\n"; queue.pop(); } }
1, 首先这里的my_comp是一个对象,因为每一个lambda对象都有一个匿名的类型,因此只能用auto来表达my_comp的类型dom
2, 优先队列的声明是这样的:函数
template< class T, class Container = std::vector<T>, class Compare = std::less<typename Container::value_type> > class priority_queue;
因此要实例化一个优先队列,尖括号里得填typename啊, 因为 lambda 对象的类型是匿名的,因此用decltype搞定,再而后光这样是不行的,这会来看构造函数:spa
explicit priority_queue( const Compare& compare = Compare(), const Container& cont = Container() );
能够看到,若是咱们构造时,不指定特定的compare对象,那么就用typename Compare的默认构造函数构造一个,然而lambda表达式的匿名类型是没有默认构造函数的,指针
因此想要正确初始化这个优先队列,还得在构造函数里再把lambda表达式自己传进去c++11
3, 函数指针,函数, lambda表达式,函数对象之间的关系
首先看sort的一个重载声明:
template <class RandomAccessIterator, class Compare> void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
这里sort接受一个函数对象,因此直接给传递lambda对象是能够的,由于lambda对象属于函数对象嘛,在C++里凡是可以使用函数调用运算符的,都是函数对象,因此
函数对象有: 函数, 函数指针, 重载了()的类的对象,以及lambda对象。
注意,函数和函数指针是两个不一样的东西,就像虽然数组在做为参数时会被当成指针传递,可是他们依旧不是同一个东西,即:
虽然在非引用时,数组和函数都会被转换成指针(包括模板参数推断也会这样),可是他们仍旧不是指针,指针的例子不用多说,举个函数的例子:
template <typename T> void t(const T& t) { cout << std::is_pointer<T>::value << endl; t(); } template <typename T> void e(T t) { cout << std::is_pointer<T>::value << endl; t(); } void f() { } int main() { t(f); cout << endl; e(f); return 0; }
第一次调用输出的是false,第二次则是true,这也就是说函数自己 和 函数指针也是两个东西
至此也就解释了一开始说到的如何使用sort以及priority_queue的缘由了
/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
关于function 和 bind :
搞清楚函数对象、函数指针、函数以及lambda对象之间的关系以后,很明显这些函数对象的类型是不同的,而且在c++11中,函数 的类型能够由函数的调用方式来表示,
好比一个函数(函数自己,而不是函数指针或者其余什么东西) int f (int, int) {...},那么咱们能够用int(int, int)来表示它的类型,可是值得注意的一点的是,若是想要声明一个
函数,并不能直接
int(int, int) f;
不过咱们能够这样:
int a (int, int) {return 1;} auto b = a;
关于这些东西的类型细节,咱们能够经过下面的代码来详细的说明:
int a(int, int) {return 1;} int f(int, int) { return 2; } int main() { auto b = []() { return 1; }; auto c = b; int (*d)(int, int); d = a; auto e = a; cout << boolalpha; cout << is_same<int(int, int), decltype(a)>::value << endl; //true cout << is_same <decltype(a), decltype(f)>::value << endl; //true cout << is_same<int(int, int), decltype(b)>::value << endl; //false cout << is_same<int(int, int), decltype(c)>::value << endl; //false cout << is_same<decltype(a), decltype(d)>::value << endl; //false return 0; }
那么问题就来了, 这些可调用对象的类型都不一致,可是他们的调用方式却能够是一致的,好比函数 int a(int); 和 lambda [](int)->int{...}他们都接受一个int类型做为参数,而后
返回一个int类型,但是因为它们的类型不一致,咱们没法在函数和lambda对象之间进行拷贝和赋值:
好比咱们没法将lambda表达式插入到std::set<int(*)(int)>中。。。
为了解决此类问题,有一群变态(是的,一群变态。。。)他们实现了一个function模板,
function模板是这样一个东西:
function仅仅以函数对象的调用方式来区分类型,也就是说,经过decltype([](int){return 1;})实例化的function对象,和经过int(int) (即上文提到的"函数自己"的类型)实例化的function对象的类型是一致的而且能够相互拷贝的。
具体使用方式能够参考下面的代码:
// function example #include <iostream> // std::cout #include <functional> // std::function, std::negate // a function: int half(int x) {return x/2;} // a function object class: struct third_t { int operator()(int x) {return x/3;} }; // a class with data members: struct MyValue { int value; int fifth() {return value/5;} }; int main () { std::function<int(int)> fn1 = half; // function std::function<int(int)> fn2 = ½ // function pointer std::function<int(int)> fn3 = third_t(); // function object std::function<int(int)> fn4 = [](int x){return x/4;}; // lambda expression std::function<int(int)> fn5 = std::negate<int>(); // standard function object std::cout << "fn1(60): " << fn1(60) << '\n'; std::cout << "fn2(60): " << fn2(60) << '\n'; std::cout << "fn3(60): " << fn3(60) << '\n'; std::cout << "fn4(60): " << fn4(60) << '\n'; std::cout << "fn5(60): " << fn5(60) << '\n';
fn1 = fn3;
std::cout << "changed fn1(60): " << fn1(60) << '\n';
// stuff with members: std::function<int(MyValue&)> value = &MyValue::value; // pointer to data member std::function<int(MyValue&)> fifth = &MyValue::fifth; // pointer to member function MyValue sixty {60}; std::cout << "value(sixty): " << value(sixty) << '\n'; std::cout << "fifth(sixty): " << fifth(sixty) << '\n'; return 0; }
从上面的代码中能够看到,只要调用方式相同,不论是类的成员指针、函数、函数指针、lambda对象,只要被function包裹以后,他们的类型就都是一致的,而且是能够相互拷贝的 (即便他们包裹的函数对象的具体类型不一致)。可是须要注意的一点是 :由于function的构造函数中,会将函数对象的一份decay copy (即去除了const volatile 以及reference以后的拷贝) 保存到function对象内部,因此若是被包裹的函数对象不能拷贝,那么function将会引起编译错误,好比:
struct foo { explicit foo(int) { p = 1;} foo (const foo&) = delete; // foo(const foo&) { static int copyCount = 0; cout << ++copyCount << endl; }; // 会发生三次拷贝 int operator()(int) {return 1;} int p; }; int main() { function<int(int)> fn1 = foo(1); function<int(int)> fn2 = foo(2); fn1 = fn2; return 0; }
上面的代码会发生编译错误: 引用了deleted 的拷贝构造函数,进一步的,若是把拷贝构造函数换成注释掉的部分,能够看到发生了三次拷贝。
/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
关于bind:
在了解了各类函数对象的细节以后,考虑当可调用对象有默认参数时的情景,如:
int f(int, int arg2 = 0);
此时f 的类型是 int(int, int) (能够验证,此处再也不贴代码证实),而f的调用方式却能够是这样的 f(0)
这个时候bind就派上用场了,咱们能够经过 bind 将默认参数绑定到某个可调用对象上,从而获得一个全新的,调用方式不同的可调用对象,bind 是一种给可调用对象加上默认参数
的手法, 可是bind比默认参数更强大, bind能够显式的改变函数对象的 调用方式,以更好的配合 function 和 STL 的组件, bind 不但能够设置默认参数,还能改变参数的位置, bind的
通常形式为:
auto newCallable = bind (callable, arg_list)
更具体的:
auto newCallable = bind (callable, _2, arg2, _1)
这意味着咱们调用 newCallable(1, 2) 时, 等价于 callabe(2, arg2, 1), 而且此时 newCallalbe 的调用方式 也已经变成相似于 int (int, int)这样只接受两个参数的类型了。
上面的_1, _2叫作占位符(placeholders), 属于命名空间 std::placeholders,其中
_1 表示 newCallable的第一个参数, _2 表示 newCallable 的第二个参数,以此类推。
值得注意的是:
bind 对于非占位符的传递方式是拷贝!!好比对于没法拷贝的cout流,咱们只能这样写bind
auto newCallable = bind (callable, _2, std::ref(arg2), _1);