標籤:

C++11中能否顯式聲明一個lambda類型的變數,而不用auto?

=========追加提問=========

所以,不存在符合標準的(不利用編譯器內部實現特徵)的顯式聲明lambda型變數的方法,是這樣嗎?(std::function應當不算,因為它實際上是在內部保存了一份lambda)

==========原問題==========

我發現,如果一個lambda不capture變數,就可以被賦值給相應類型的函數指針,如果capture了,就無法賦值,比如:

int (*p)(int);

p=[](int){return 1;}; //OK

p=[=](int){return 1;}; //error

p=[](int){return 1;}; //error

g++和clang++編譯結果相同。

看來之所以能賦值給函數指針,只不過是因為能進行隱式類型轉換。那麼:

1. 能否顯式聲明一個lambda類型的變數?

2. 為什麼capture會使隱式類型轉換無法進行?


你需要的是std::function&。至於為什麼capture了就不能變成函數指針,是因為函數指針代表的就是全局函數,你capture了,不同的時候調用lambda給出來的結果都不一樣,當然不是一個全局函數。


所以,不存在符合標準的(不利用編譯器內部實現特徵)的顯式聲明lambda型變數的方法,是這樣嗎?

是。

你可以寫個程序,用 typeid 列印出類型的名字看一下。

#include &
#include &

template &
void printType(T obj) {
std::cout &<&< typeid(obj).name() &<&< std::endl; } int main() { printType([](){}); printType([](){}); printType(0L); return 0; }

用 clang 編譯後運行輸出為:

Z4mainE3$_0
Z4mainE3$_1
l

你看,即使是一樣的 lambda 表達式,它們的類型也是不同的。所以你只能用 std::function 一類的玩意把它包起來。


沒辦法,只能auto,因為每一個lambda都是獨一無二的,即使參數是相同的類型。

語言律師版的答案:

5.1.2/3 The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type — called the closure type — whose properties are described below. This class type is not an aggregate (8.5.1). The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [..]

注意這裡的unique


lambda本身就是一個匿名類,它內部的實現和一般的c++ class沒有區別,之所以一個可以隱式轉換一個不能,就是因為編譯器在生成這個類的時候,一個重載了對應的隱式轉換運算符,一個沒有

出處Lambda functions (since C++11)


不用function之類的東東的話,不行。因為ISO要求lambda表達式的類型不能被顯式訪問。即便你使用了一些奇技淫巧訪問到這個類型,也很難保證跨編譯器的兼容性。因為各個編譯器的實現方法可能不太一樣。

所以只能用類似function的方式做內部複製,反正是可以move嘛


類似於 Objective-C 的 block 語法,它通過複製的方式隱式捕獲 scope 內的變數,那這些變數去哪了呢?它們都被存儲在一個 struct 中,這個 struct 將作為參數傳遞到 block 體指針所指向的函數實現中被執行。當然如果外部變數用 __block 限定符聲明了可被 block 體修改,那其相應的指針也會被存放在 struct 中。其實意思就是,這個我們稱之為匿名函數的東西一旦需要捕獲外部變數,那這個函數原有的簽名來作為最終真正執行的函數簽名肯定是不夠的,所以不能被賦值到單純相同的函數指針中。


沒用gcc,就是vs2013來說。lambda在沒有引用變數的時候可以被實現為一個函數指針,在有引用變數的時候必然實現為重載了op ()的匿名對象。在這裡可以認為lambda只是1x標準之前早已支持的局部對象的一個語法糖。

有變數就是這貨的語法糖:

struct {

int opearator() (int);

int v;

}lambda;

lambda.v = i;

沒有就是封裝函數指針的語法糖


std::function,百度看看function怎麼用就明白了,bind與lambda都是返回的function,累死封裝的函數指針(帶對象)


函數指針指向函數入口地址,不綁定具體參數值,lamda表達式結果應該賦值給function


現代編程語言的閉包都是帶有特殊方法的對象,跟函數指針那種低級的東西還是有些區別的。


1. 可以,但是你需要正確的聲明type,例如

std::function& p = [=](int i)-&>int{
return -1;
};

要注意這裡的完整type是std::function&,而不是int(*)(int)

2. 之所以捕獲時無法轉換是因為:std::function模板本身在實例化時是生成了一個進行過operator()重載的類實例。當沒有捕獲列表時,其內部的函數入口指針的封裝等價於傳統的函數指針。但是,當捕獲列表出現時,其入口包含了實例化時對捕獲對象的封裝,而這個捕獲對象的實體是以類成員的身份存在的,因此無法被轉化成函數指針。

一個例子

int k = 5;
int* p = k;

std::function& func = [p](int i)-&>int{
(*p)++;
return i+(*p);
};

cout&<&

程序的輸出結果是

9

11

可以看到,在函數調用的時候,其捕獲列表並不會出現在參數列表中,但是卻真實的影響了實變數k。因此,在該function類實例中,指針p是作為一個實際存在的類實例(func)的成員變數而真實存在的,而這種存在意味著在執行重載後的operator()時,需要有相應的this指針執行實例內部的member access,這也就決定了其類實例無法等價於函數指針。


1. 可以使用lambda初始化std:function&類型的變數,但兩者類型是不一樣的。

2. 對於帶有capture的lambda,我將它看成一個functor, 當然不能直接賦值給一個函數指針。不帶capture 的lambda就像一個普通函數,所以可以賦值。


lambda其實更像個重載了operator()的class。你不能只記錄一個成員函數的指針而不保存這個對象


這裡面有解釋:item 5: 比起顯式的類型聲明,更偏愛auto

結論是,你無法自己寫出lambda的底層類型,但是可以用std::function來存放。


lambda的capture相當於就地聲明了一個新的匿名struct,當然沒法顯式聲明一個同類型的變數了。


因為lambda表達式的返回值是std::function,實際上是類


推薦閱讀:

QQ上發送么么噠時候,彈出彈跳錶情,是如何實現的?
求一個數學公式:要求生成一個可控制分布的隨機數?
Python的for使用問題?
怎麼樣算是學會一門編程語言?
你們開始是如何學習編程的?

TAG:編程 | CC | C11 | C14 |