共用方式為


Lambda 運算式語法

本文示範 Lambda 運算式的語法和結構化項目。 如需 Lambda 運算式的說明,請參閱 C++ 中的 Lambda 運算式

Lambda 運算式文法

ISO C++11 標準的下列定義顯示「Lambda 運算式」(lambda expression) 的文法。 (標記 opt 註標的項目為選擇性。)

        lambda-introducer lambda-declaratoropt compound-statement

這些語法元件可進一步指定:

lambda-introducer:
        [ lambda-captureopt ]
lambda-capture:
        capture-default
        capture-list
        capture-default , capture-list
capture-default:
        &
        =
capture-list:
        capture ...opt
        capture-list , capture ...opt
capture:
        識別項
        & 識別項
        this
lambda-declarator:
        ( parameter-declaration-clause ) mutableopt
                exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt

Visual Studio 支援 C++11 標準 Lambda 運算式語法和功能,例外狀況如下:

  • Lambda 和其他所有類別一樣,不會取得自動產生的移動建構函式和移動指派運算子。 如需右值參考行為的詳細資訊,請參閱 C++11 功能的支援 (現代 C++) 中的<右值參考>一節。

  • 此版本不支援選擇性的 attribute-specifier-seq。

除了 C++11 標準 Lambda 功能,Visual Studio 還包含下列功能:

  • 無狀態 Lambda,可完全轉換為使用任意呼叫慣例的函式指標。

  • 只要所有 return 陳述式都有相同類型,就會自動推算比 { return expression; } 更複雜的 Lambda 主體傳回類型。 (此功能包含在所提議的 C++14 標準中。)

Lambda 運算式的屬性

下圖將文法對應至範例:

Structural elements of a lambda expression

  1. lambda-introducer (也稱為「擷取子句」(capture clause) )

  2. lambda declarator (也稱為「參數清單」(parameter list) )

  3. mutable (也稱為「可變動規格」(mutable specification) )

  4. exception-specification (也稱為「例外狀況規格」(exception specification) )

  5. trailing-return-type (也稱為「傳回類型」(return type) )

  6. compound-statement (也稱為「Lambda 主體」(lambda body) )

擷取子句

Lambda 運算式基本上是類別、建構函式和函式呼叫運算子。 就像在定義類別時,您必須在 Lambda 中決定所產生的物件要以傳值或傳址方式來擷取變數,或者完全不要擷取變數。 如果 Lambda 運算式必須存取區域變數和函式參數,就必須加以「擷取」(Capture)。 擷取子句 (Standard 語法中的 lambda-introducer) 可指定 Lambda 運算式的主體是否可以用傳值或傳址方式來存取封閉範圍內的變數。 含有 & 前置詞的變數會以傳址方式來存取,而不含 & 前置詞的變數會以傳值方式來存取。

空白的擷取子句 [ ] 表示 Lambda 運算式主體不存取封閉範圍中的任何變數。

您可以使用預設擷取模式 (Standard 語法中的 capture-default),以傳值或傳址方式來擷取未指定的變數。 您可以使用 & 或 = 做為擷取子句的第一個元素,以指定預設擷取模式。 & 元素會命令 Lambda 運算式主體以傳址方式來存取未指定的變數。 = 元素會命令 Lambda 主體以傳值方式來存取未指定的變數。 例如,如果 Lambda 主體以傳址方式存取外部變數 total,並以傳值方式存取外部變數 factor,則下列擷取子句相等:

[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]

關於 capture-default 的常見錯誤觀念之一,就是無論 Lambda 中是否使用這些變數,都會擷取範圍內的所有變數。 但並非如此,使用 capture-default 時只會擷取 Lambda 中提到的變數。

如果擷取子句包含 capture-default &,該擷取子句的 capture 中,沒有任何 identifier 會具有 & identifier 格式。 同樣地,如果擷取子句包含 capture-default =,則該擷取子句沒有任何 capture 會具有 = identifier 格式。 識別項或 this 不能在擷取子句中重複出現。 下列程式碼片段說明部分範例。

struct S { void f(int i); };

void S::f(int i) {
    [&, i]{};    // OK
    [&, &i]{};   // ERROR: i preceded by & when & is the default
    [=, this]{}; // ERROR: this when = is the default
    [i, i]{};    // ERROR: i repeated
}

省略符號前面的 capture 是套件擴充,如下列 variadic 樣板範例所示:

template<class... Args>
void f(Args... args) {
    auto x = [args...] { return g(args...); };
    x();
}

您可以在類別方法的主體中使用 Lambda 運算式。 將 this 指標傳遞給擷取子句,以提供對封入類別之方法和資料成員的存取。 如需示範如何搭配使用 Lambda 運算式與類別方法的範例,請參閱 Lambda 運算式的範例 中的<範例:在方法中使用 Lambda 運算式>。

使用擷取子句時,建議您記住這幾個重點,特別是同時使用 Lambda 與多執行緒時:

  • 傳址擷取可用來修改外部變數,但傳值擷取方式不能。 (mutable 允許修改複本,但不允許修改原稿。)

  • 傳址擷取方式會反映對外部變數的更新,但傳值擷取方式不會。

  • 傳址擷取方式採用存留期相依性,但傳值擷取方式沒有存留期相依性。

參數清單

參數清單 (Standard 語法中的 lambda declarator) 為選擇性,類似函式的參數清單。

Lambda 運算式可接受另一個 Lambda 運算式當做其引數。 如需詳細資訊,請參閱 Lambda 運算式的範例 主題中的<高階 Lambda 運算式>。

由於參數清單是選擇性的,所以如果您不傳遞引數至 Lambda 運算式,且其 lambda-declarator: 不包含 exception-specification、trailing-return-type 或 mutable,則可省略空括號。

可變動規格

通常 Lambda 的函式呼叫運算子是傳值常數,但使用 mutable 關鍵字會抵銷此功能。 它不會產生可變動的資料成員。 可變動規格可讓 Lambda 運算式主體修改以傳值方式擷取的變數。 本文稍後的一些範例會示範如何使用 mutable。

例外狀況規格

您可以使用 throw() 例外狀況規格,表示 Lambda 運算式不會擲回任何例外狀況。 如同一般函式,如果 Lambda 運算式宣告 throw() 例外狀況規格,而且 Lambda 主體擲回例外狀況,Visual C++ 編譯器就會產生警告 C4297,如下所示:

// throw_lambda_expression.cpp
// compile with: /W4 /EHsc 
int main() // C4297 expected
{
   []() throw() { throw 5; }();
}

如需詳細資訊,請參閱例外狀況規格

傳回型別

Lambda 運算式的傳回類型會自動推算出來。 除非您指定 trailing-return-type,否則不需要表示 auto 關鍵字。 trailing-return-type 類似一般方法或函式的傳回類型部分。 不過,傳回類型必須接在參數清單後面,而且您必須在傳回類型前面包含 trailing-return-type 關鍵字 ->。

如果 Lambda 主體包含單一 return 陳述式,或者運算式不會傳回值,您可以省略 Lambda 運算式的傳回類型部分。 如果 Lambda 主體包含單一 return 陳述式,編譯器會從傳回運算式的類型推算傳回類型。 否則,編譯器會將傳回類型推算為 void。 請考慮下列說明此原則的範例程式碼片段。

auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; };  // ERROR: return type is void, deducing 
                                  // return type from braced-init-list is not valid

Lambda 運算式可能會產生另一個 Lambda 運算式當做其傳回值。 如需詳細資訊,請參閱 Lambda 運算式的範例 中的<高階 Lambda 運算式>。

Lambda 主體

Lambda 運算式的 Lambda 主體 (Standard 語法中的 compound-statement) 可以包含一般方法或函式主體可包含的任何內容。 一般函式和 Lambda 運算式的主體都可以存取下列類型的變數:

  • 參數

  • 區域宣告變數

  • 類別資料成員 (在類別內部宣告並擷取 this 時)

  • 有靜態儲存期的任何變數 (例如,全域變數)

此外,Lambda 運算式可以存取其自封閉範圍擷取的變數。 如果出現在 Lambda 運算式的擷取子句中,變數會進行「明確擷取」(Explicitly captured)。 否則,變數是「隱含擷取的」(Implicitly Captured)。 Lambda 運算式的主體會使用預設擷取模式來存取隱含擷取的變數。

下列範例包含 Lambda 運算式,這個運算式會以傳值方式明確擷取變數 n,並以傳址方式隱含擷取變數 m:

// captures_lambda_expression.cpp
// compile with: /W4 /EHsc 
#include <iostream>
using namespace std;

int main()
{
   int m = 0;
   int n = 0;
   [&, n] (int a) mutable { m = ++n + a; }(4);
   cout << m << endl << n << endl;
}

輸出:

  

因為變數 n 是以傳值方式擷取,其值在 Lambda 運算式呼叫之後會保持為 0。 mutable 規格允許在 Lambda 內修改 n。

雖然 Lambda 運算式只能擷取有自動儲存期的變數,但是您可以在 Lambda 運算式的主體中使用有靜態儲存期的變數。 下列範例使用 generate 函式和 Lambda 運算式,將值指派給 vector 物件的每個項目。 Lambda 運算式會修改這個靜態變數以產生下一個項目的值。

void fillVector(vector<int>& v)
{
    // A local static variable.
    static int nextValue = 1;

    // The lambda expression that appears in the following call to
    // the generate function modifies and uses the local static 
    // variable nextValue.
    generate(v.begin(), v.end(), [] { return nextValue++; }); 
    //WARNING: this is not thread-safe and is shown for illustration only
}

如需詳細資訊,請參閱產生

下列程式碼範例使用前一個範例的函式,並加入使用 STL 演算法 generate_n 的 Lambda 運算式範例。 此 Lambda 運算式會將 vector 物件的元素指派給前兩個元素的總和。 使用 mutable 關鍵字,因此 Lambda 運算式的主體可以修改其外部變數 x 和 y 的複本,這兩個變數是 Lambda 運算式以傳值方式擷取的。 由於 Lambda 運算式會以傳值方式擷取原始變數 x 和 y,在 Lambda 執行後,它們的值仍然保持 1。

// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>

using namespace std;

template <typename C> void print(const string& s, const C& c) {
    cout << s;

    for (const auto& e : c) {
        cout << e << " ";
    }

    cout << endl;
}

void fillVector(vector<int>& v)
{
    // A local static variable.
    static int nextValue = 1;

    // The lambda expression that appears in the following call to
    // the generate function modifies and uses the local static 
    // variable nextValue.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    //WARNING: this is not thread-safe and is shown for illustration only
}

int main()
{
    // The number of elements in the vector.
    const int elementCount = 9;

    // Create a vector object with each element set to 1.
    vector<int> v(elementCount, 1);

    // These variables hold the previous two elements of the vector.
    int x = 1;
    int y = 1;

    // Sets each element in the vector to the sum of the 
    // previous two elements.
    generate_n(v.begin() + 2,
        elementCount - 2,
        [=]() mutable throw() -> int { // lambda is the 3rd parameter
        // Generate current value.
        int n = x + y;
        // Update previous two values.
        x = y;
        y = n;
        return n;
    });
    print("vector v after call to generate_n() with lambda: ", v);

    // Print the local variables x and y.
    // The values of x and y hold their initial values because 
    // they are captured by value.
    cout << "x: " << x << " y: " << y << endl;

    // Fill the vector with a sequence of numbers
    fillVector(v);
    print("vector v after 1st call to fillVector(): ", v);
    // Fill the vector with the next sequence of numbers
    fillVector(v);
    print("vector v after 2nd call to fillVector(): ", v);
}

輸出:

  

如需詳細資訊,請參閱generate_n

Microsoft 專有的修飾詞

如果您使用 __declspec 之類的 Microsoft 專有修飾詞,可以將其插入 Lambda 運算式中的 parameter-declaration-clause 後面,例如:

auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };

若要判定 Lambda 是否支援修飾詞,請參閱說明文件的 Microsoft 專有的修飾詞一節中,有關 Lambda 的文章。

請參閱

參考

C++ 中的 Lambda 運算式

Lambda 運算式的範例

產生

generate_n

for_each

例外狀況規格

編譯器警告 (層級 1) C4297

Microsoft 專有的修飾詞