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);