名稱空間
變體
操作

Lambda 表示式 (C++11 起)

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
通用主題
流程控制
條件執行語句
if
迭代語句(迴圈)
for
range-for (C++11)
跳轉語句
函式
函式宣告
Lambda 函式表示式
inline 說明符
動態異常規範 (直到 C++17*)
noexcept 說明符 (C++11)
異常
名稱空間
型別
說明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
儲存期說明符
初始化
表示式
替代表示
字面量
布林 - 整型 - 浮點型
字元 - 字串 - nullptr (C++11)
使用者定義 (C++11)
工具
屬性 (C++11)
型別
typedef 宣告
類型別名宣告 (C++11)
型別轉換
記憶體分配
類特有的函式屬性
explicit (C++11)
static

特殊成員函式
模板
雜項
 
 
 

構造一個 閉包(一個能夠捕獲作用域內變數的未命名函式物件)。

目錄

[編輯] 語法

[編輯] 不帶顯式模板引數列表的 Lambda 表示式(可能為非泛型)
[捕獲列表 ] 前置屬性 (可選) (引數列表 ) 說明符 (可選) 異常說明 (可選)
後置屬性 (可選) 尾隨返回型別 (可選) requires 子句 (可選) 契約說明符 (可選) { 函式體 }
(1)
[捕獲列表 ] { 函式體 } (2) (直至 C++23)
[捕獲列表 ] 前置屬性 (可選) 尾隨返回型別 (可選) 契約說明符 (可選) { 函式體 } (2) (C++23 起)
[捕獲列表 ] 前置屬性 (可選) 異常說明
後置屬性 (可選) 尾隨返回型別 (可選) 契約說明符 (可選) { 函式體 }
(3) (C++23 起)
[捕獲列表 ] 前置屬性 (可選) 說明符 異常說明 (可選)
後置屬性 (可選) 尾隨返回型別 (可選) 契約說明符 (可選) { 函式體 }
(4) (C++23 起)
[編輯] 帶顯式模板引數列表的 Lambda 表示式(總是泛型) (C++20 起)
[捕獲列表 ] <模板引數列表 > 模板約束 (可選)
前置屬性 (可選) (引數列表 ) 說明符 (可選) 異常說明 (可選)
後置屬性 (可選) 尾隨返回型別 (可選) requires 子句 (可選) 契約說明符 (可選) { 函式體 }
(1)
[捕獲列表 ] <模板引數列表 > 模板約束 (可選) { 函式體 } (2) (直至 C++23)
[捕獲列表 ] <模板引數列表 > 模板約束 (可選)
前置屬性 (可選) 尾隨返回型別 (可選) 契約說明符 (可選) { 函式體 }
(2) (C++23 起)
[捕獲列表 ] <模板引數列表 > 模板約束 (可選) 前置屬性 (可選) 異常說明
後置屬性 (可選) 尾隨返回型別 (可選) 契約說明符 (可選) { 函式體 }
(3) (C++23 起)
[捕獲列表 ] <模板引數列表 > 模板約束 (可選) 前置屬性 (可選) 說明符 異常說明 (可選)
後置屬性 (可選) 尾隨返回型別 (可選) 契約說明符 (可選) { 函式體 }
(4) (C++23 起)
1) 帶引數列表的 lambda 表示式。
2-4) 不帶引數列表的 lambda 表示式。
2) 最簡單的語法。後置屬性不能應用。
3,4) 只有當 說明符異常說明 中存在任何一個時,才能應用 後置屬性

[編輯] 解釋

捕獲列表 - 指定要捕獲的實體。
模板引數列表 - 非空的逗號分隔的模板引數列表,用於為泛型 lambda 的模板引數提供名稱(見下方的 ClosureType::operator())。
模板約束 - 模板引數列表 新增約束

如果 模板約束 以屬性說明符序列結尾,則序列中的屬性被視為 前置屬性 中的屬性。

(C++23 起)
前置屬性 - (C++23 起) 屬性說明符序列應用於閉包型別的 operator()(因此可以使用 [[noreturn]] 屬性)。
引數列表 - 閉包型別的 operator()引數列表

它可以有一個顯式物件引數

(C++23 起)
說明符 - 以下說明符的列表,每個說明符在每個序列中最多允許出現一次。
說明符 效果
mutable 允許 函式體 修改按值捕獲的物件,並呼叫它們的非 const 成員函式。
  • 如果存在顯式物件引數,則不能使用。
(C++23 起)
constexpr     
(C++17 起)
顯式指定 operator() 是一個 constexpr 函式
  • 如果 operator() 滿足所有 constexpr 函式要求,則即使不存在 constexproperator() 也會是 constexpr。
consteval
(C++20 起)
指定 operator() 是一個立即函式
  • constevalconstexpr 不能同時指定。
static
(C++23 起)
指定 operator() 是一個靜態成員函式
  • staticmutable 不能同時指定。
  • 如果 捕獲列表 不為空,或者存在顯式物件引數,則不能使用。
異常規範 - 提供 動態異常說明(C++20 前) 閉包型別的 operator()noexcept 說明符
後置屬性 - 一個屬性說明符序列應用於閉包型別的 operator() 的型別(因此不能使用 [[noreturn]] 屬性)。
尾隨返回型別 - -> 返回型別,其中 返回型別 指定返回型別。
requires 子句 - (C++20 起) 為閉包型別的 operator() 新增約束
契約說明符 - (C++26 起) 閉包型別的 operator()函式契約說明符列表。
函式體 - 函式體。


如果引數型別使用 auto,或者提供了顯式模板引數列表(C++20 起),則 lambda 是一個 _泛型 lambda_。

(C++14 起)

變數 __func__函式體 的開頭隱式定義,其語義如此處所述。

[編輯] 閉包型別

lambda 表示式是一個右值表示式,其型別為唯一的未命名非聯合聚合類型別,稱為_閉包型別_,它被宣告在包含 lambda 表示式的最小塊作用域、類作用域或名稱空間作用域中(用於 ADL)。

當且僅當 捕獲列表 為空時,閉包型別是結構化型別。

(C++20 起)

閉包型別具有以下成員,它們不能被顯式例項化顯式特化,或(C++14 起)友元宣告中命名。

ClosureType::operator()(引數列表)

返回型別 operator()(引數列表) { 函式體 }
(static 和 const 可能存在,見下文)
template<模板引數>
返回型別 operator()(引數列表) { 函式體 }
(C++14 起)
(泛型 lambda,static 和 const 可能存在,見下文)

呼叫時執行 lambda 表示式的函式體。訪問變數時,訪問其捕獲的副本(對於按值捕獲的實體),或原始物件(對於按引用捕獲的實體)。

如果提供了引數列表,則 operator() 的引數列表為 引數列表,否則引數列表為空。

operator() 的返回型別是 尾隨返回型別 中指定的型別。

如果未提供 尾隨返回型別,則 operator() 的返回型別會自動推導[1]

除非在 lambda 說明符中使用了關鍵字 mutable,或者存在顯式物件引數(C++23 起),否則 operator() 的 cv 限定符為 const,並且透過複製捕獲的物件在此 operator() 內部不可修改。不允許顯式 const 限定符。operator() 永遠不是虛擬函式,也不能有 volatile 限定符。

如果 operator() 滿足constexpr 函式的要求,則它總是 constexpr。如果 lambda 說明符中使用了關鍵字 constexpr,它也是 constexpr。

(C++17 起)

如果 lambda 說明符中使用了關鍵字 consteval,則 operator() 是一個立即函式

(C++20 起)

如果 lambda 說明符中使用了關鍵字 static,則 operator() 是一個靜態成員函式

如果 引數列表 包含一個顯式物件引數,則 operator() 是一個顯式物件成員函式

(C++23 起)


對於 引數列表 中型別指定為 auto 的每個引數,一個虛構的模板引數會按出現的順序新增到 模板引數 中。如果 引數列表 對應的函式成員是函式引數包,則虛構的模板引數可能是一個引數包

// generic lambda, operator() is a template with two parameters
auto glambda = [](auto a, auto&& b) { return a < b; };
bool b = glambda(3, 3.14); // OK
 
// generic lambda, operator() is a template with one parameter
auto vglambda = [](auto printer)
{
    return [=](auto&&... ts) // generic lambda, ts is a parameter pack
    { 
        printer(std::forward<decltype(ts)>(ts)...);
        // nullary lambda (takes no parameters):
        return [=] { printer(ts...); };
    };
};
 
auto p = vglambda([](auto v1, auto v2, auto v3)
{
    std::cout << v1 << v2 << v3;
});
 
auto q = p(1, 'a', 3.14); // outputs 1a3.14
q();                      // outputs 1a3.14
(C++14 起)


如果 lambda 定義使用顯式模板引數列表,則該模板引數列表會與 operator() 一起使用。對於 引數列表 中型別指定為 auto 的每個引數,一個額外的虛構模板引數會附加到該模板引數列表的末尾。

// generic lambda, operator() is a template with two parameters
auto glambda = []<class T>(T a, auto&& b) { return a < b; };
 
// generic lambda, operator() is a template with one parameter pack
auto f = []<typename... Ts>(Ts&&... ts)
{
    return foo(std::forward<Ts>(ts)...);
};
(C++20 起)

lambda 表示式上的異常說明 異常說明 適用於 operator()

為了進行名稱查詢、確定this 指標的型別和值,以及訪問非靜態類成員,閉包型別的 operator() 的函式體在 lambda 表示式的上下文中被考慮。

struct X
{
    int x, y;
    int operator()(int);
    void f()
    {
        // the context of the following lambda is the member function X::f
        [=]() -> int
        {
            return operator()(this->x + y); // X::operator()(this->x + (*this).y)
                                            // this has type X*
        };
    }
};

懸空引用

如果非引用實體被隱式或顯式地按引用捕獲,並且在實體生命週期結束後呼叫了閉包物件的 operator(),則會發生未定義行為。C++ 閉包不會延長按引用捕獲的物件的生命週期。

透過 this 捕獲的當前 *this 物件的生命週期也適用同樣的情況。

  1. 儘管 C++14 引入了函式返回型別推導,但在 C++11 中,它的規則可用於 lambda 返回型別推導。

ClosureType::operator 返回型別(*)(引數列表)()

無捕獲非泛型 lambda
using F = 返回型別(*)(引數列表);
operator F() const noexcept;
(C++17 前)
using F = 返回型別(*)(引數列表);
constexpr operator F() const noexcept;
(C++17 起)
無捕獲泛型 lambda
template<模板引數> using fptr_t = /* 見下文 */;

template<模板引數>

operator fptr_t<模板引數>() const noexcept;
(C++14 起)
(C++17 前)
template<模板引數> using fptr_t = /* 見下文 */;

template<模板引數>

constexpr operator fptr_t<模板引數>() const noexcept;
(C++17 起)

使用者定義轉換函式僅在 lambda 表示式沒有 捕獲列表 且沒有顯式物件引數時才定義(C++23 起)。它是閉包物件的公共、constexpr、(C++17 起)非虛、非顯式、const noexcept 成員函式。

如果函式呼叫運算子(或泛型 lambda 的特化)是立即函式,則此函式是立即函式

(C++20 起)

無捕獲泛型 lambda 具有一個使用者定義轉換函式模板,其模板引數列表與 operator() 相同。

void f1(int (*)(int)) {}
void f2(char (*)(int)) {}
void h(int (*)(int)) {}  // #1
void h(char (*)(int)) {} // #2
 
auto glambda = [](auto a) { return a; };
f1(glambda); // OK
f2(glambda); // error: not convertible
h(glambda);  // OK: calls #1 since #2 is not convertible
 
int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK
(C++14 起)


轉換函式返回的值是指向具有 C++ 語言連結的函式的指標,當呼叫時,其效果與在閉包型別的預設構造例項上呼叫閉包型別的函式呼叫運算子相同。

(C++14 前)

轉換函式(模板)返回的值是指向具有 C++ 語言連結的函式的指標,當呼叫時,其效果與以下情況相同

  • 對於非泛型 lambda,在閉包型別的預設構造例項上呼叫閉包型別的 operator()
  • 對於泛型 lambda,在閉包型別的預設構造例項上呼叫泛型 lambda 的相應 operator() 特化。
(C++14 起)
(直至 C++23)

轉換函式(模板)返回的值是

  • 如果 operator() 是靜態的,則是指向具有 C++ 語言連結operator() 的指標,
  • 否則,是指向具有 C++ 語言連結的函式的指標,當呼叫時,其效果與以下情況相同:
    • 對於非泛型 lambda,在閉包型別的預設構造例項上呼叫閉包型別的 operator()
    • 對於泛型 lambda,在閉包型別的預設構造例項上呼叫泛型 lambda 的相應 operator() 特化。
(C++23 起)


如果函式呼叫運算子(或泛型 lambda 的特化)是 constexpr,則此函式是 constexpr。

auto Fwd = [](int(*fp)(int), auto a) { return fp(a); };
auto C = [](auto a) { return a; };
static_assert(Fwd(C, 3) == 3);  // OK
 
auto NC = [](auto a) { static int s; return a; };
static_assert(Fwd(NC, 3) == 3); // error: no specialization can be
                                // constexpr because of static s

如果閉包物件的 operator() 具有非丟擲異常說明,則此函式返回的指標具有指向 noexcept 函式的型別。

(C++17 起)

ClosureType::ClosureType()

ClosureType() = default;
(C++20 起)
(僅當未指定捕獲時)
ClosureType(const ClosureType&) = default;
ClosureType(ClosureType&&) = default;

閉包型別不是可預設構造的。閉包型別沒有預設建構函式。

(C++20 前)

如果未指定 捕獲列表,則閉包型別具有預設的預設建構函式。否則,它沒有預設建構函式(這包括存在 捕獲預設 的情況,即使它實際上沒有捕獲任何東西)。

(C++20 起)

複製建構函式和移動建構函式被宣告為預設,並可根據複製建構函式移動建構函式的通常規則隱式定義。

ClosureType::operator=(const ClosureType&)

ClosureType& operator=(const ClosureType&) = delete;
(C++20 前)
ClosureType& operator=(const ClosureType&) = default;
ClosureType& operator=(ClosureType&&) = default;
(C++20 起)
(僅當未指定捕獲時)
ClosureType& operator=(const ClosureType&) = delete;
(C++20 起)
(否則)

複製賦值運算子被定義為已刪除(並且未宣告移動賦值運算子)。閉包型別不是可複製賦值的

(C++20 前)

如果未指定 捕獲列表,則閉包型別具有預設的複製賦值運算子和預設的移動賦值運算子。否則,它具有已刪除的複製賦值運算子(這包括存在 捕獲預設 的情況,即使它實際上沒有捕獲任何東西)。

(C++20 起)

ClosureType::~ClosureType()

~ClosureType() = default;

解構函式是隱式宣告的。

ClosureType::捕獲

T1 a;

T2 b;

...

如果 lambda 表示式按值捕獲任何內容(無論是透過捕獲子句 [=] 隱式捕獲,還是透過不包含字元 & 的捕獲(例如 [a, b, c])顯式捕獲),則閉包型別包含未命名非靜態資料成員,以未指定順序宣告,它們持有所有如此捕獲的實體的副本。

那些沒有初始化器的捕獲對應的資料成員在 lambda 表示式求值時直接初始化。那些帶有初始化器的捕獲對應的資料成員根據初始化器要求進行初始化(可以是複製初始化或直接初始化)。如果捕獲了陣列,陣列元素按索引遞增順序直接初始化。資料成員的初始化順序是它們宣告的順序(未指定)。

每個資料成員的型別是相應捕獲實體的型別,除非實體具有引用型別(在這種情況下,函式引用被捕獲為對引用函式的左值引用,物件引用被捕獲為引用物件的副本)。

對於按引用捕獲的實體(使用 捕獲預設 [&] 或使用字元 &,例如 [&a, &b, &c]),是否在閉包型別中宣告額外的資料成員是未指定的,但任何此類額外成員必須滿足字面量型別(C++17 起)


Lambda 表示式不允許出現在未求值表示式模板引數別名宣告typedef 宣告中,以及函式(或函式模板)宣告中的任何位置,除了函式體和函式的預設引數

(C++20 前)

[編輯] Lambda 捕獲

捕獲列表 定義了可從 lambda 函式體內部訪問的外部變數。其語法定義如下:

捕獲預設 (1)
捕獲列表 (2)
捕獲預設 , 捕獲列表 (3)
捕獲預設 - &= 之一
捕獲列表 - 逗號分隔的 捕獲 列表


捕獲 的語法定義如下:

識別符號 (1)
識別符號 ... (2)
識別符號 初始化器 (3) (C++14 起)
& 識別符號 (4)
& 識別符號 ... (5)
& 識別符號 初始化器 (6) (C++14 起)
this (7)
* this (8) (C++17 起)
... 識別符號 初始化器 (9) (C++20 起)
& ... 識別符號 初始化器 (10) (C++20 起)
1) 簡單的按值捕獲
2) 作為包擴充套件的簡單按值捕獲
3)初始化器的按值捕獲
4) 簡單的按引用捕獲
5) 簡單的按引用捕獲,它是一個包擴充套件
6) 帶初始化器的按引用捕獲
7) 當前物件的簡單按引用捕獲
8) 當前物件的簡單按值捕獲
9) 帶初始化器的按值捕獲,它是一個包擴充套件
10) 帶初始化器的按引用捕獲,它是一個包擴充套件

如果 capture-default&,則後續的簡單捕獲不能以 & 開頭。

struct S2 { void f(int i); };
void S2::f(int i)
{
    [&] {};          // OK: by-reference capture default
    [&, i] {};       // OK: by-reference capture, except i is captured by copy
    [&, &i] {};      // Error: by-reference capture when by-reference is the default
    [&, this] {};    // OK, equivalent to [&]
    [&, this, i] {}; // OK, equivalent to [&, i]
}

如果 capture-default=,則後續的簡單捕獲必須以 & 開頭,或者為 *this(C++17 起) 或者為 this(C++20 起)

struct S2 { void f(int i); };
void S2::f(int i)
{
    [=] {};        // OK: by-copy capture default
    [=, &i] {};    // OK: by-copy capture, except i is captured by reference
    [=, *this] {}; // until C++17: Error: invalid syntax
                   // since C++17: OK: captures the enclosing S2 by copy
    [=, this] {};  // until C++20: Error: this when = is the default
                   // since C++20: OK, same as [=]
}

任何捕獲只能出現一次,並且其名稱必須不同於任何引數名稱。

struct S2 { void f(int i); };
void S2::f(int i)
{
    [i, i] {};        // Error: i repeated
    [this, *this] {}; // Error: "this" repeated (C++17)
 
    [i] (int i) {};   // Error: parameter and capture have the same name
}

如果變數滿足以下條件,lambda 表示式可以使用變數而無需捕獲它:

  • 它是一個非區域性變數,或者具有靜態或執行緒區域性儲存期(在這種情況下,變數不能被捕獲),或者
  • 它是一個已用常量表達式初始化的引用。

如果變數滿足以下條件,lambda 表示式可以讀取變數的值而無需捕獲它:

  • 它具有 const 非 volatile 整型或列舉型別,並且已用常量表達式初始化,或者
  • 它是 constexpr 且沒有可變成員。

如果存在任何捕獲預設值,則當前物件(*this)可以被隱式捕獲。如果隱式捕獲,它總是按引用捕獲,即使捕獲預設值為 =當捕獲預設值為 = 時隱式捕獲 *this 已棄用。(C++20 起)

只有滿足以下任何條件的 lambda 表示式才能具有 capture-default 或不帶初始化器的 capture

(C++26 起)

對於此類 lambda 表示式,可達作用域定義為包含直到幷包括最內層函式(及其引數)的所有封閉作用域的集合。這包括巢狀的塊作用域以及如果此 lambda 是巢狀的則包含的 lambda 的作用域。

任何不帶初始化器(除了 this 捕獲)的捕獲中的 identifier 使用通常的非限定名稱查詢在 lambda 的可達作用域中查詢。查詢結果必須是在可達作用域中宣告的具有自動儲存期的變數,或者是一個結構化繫結,其對應的變數滿足此類要求(C++20 起)。該實體是顯式捕獲的。

帶有初始化器的捕獲,稱為初始化捕獲,其行為就像它宣告並顯式捕獲了一個用型別說明符auto和相同初始化器宣告的變數,該變數的宣告區域是 lambda 表示式的函式體(也就是說,它不在其初始化器中作用域),但以下情況除外:

  • 如果捕獲是按值捕獲,則閉包物件引入的非靜態資料成員是引用該變數的另一種方式;
    • 換句話說,源變數實際上不存在,並且透過 auto 進行的型別推導和初始化應用於非靜態資料成員;
  • 如果捕獲是按引用捕獲,則引用變數的生命週期在閉包物件的生命週期結束時結束。

這用於透過諸如 x = std::move(x) 的捕獲來捕獲僅移動型別。

這也使得透過 const 引用捕獲成為可能,例如 &cr = std::as_const(x) 或類似的方式。

int x = 4;
 
auto y = [&r = x, x = x + 1]() -> int
{
    r += 2;
    return x * x;
}(); // updates ::x to 6 and initializes y to 25.
(C++14 起)

如果 captures 包含 capture-default 並且沒有顯式捕獲封閉物件(作為 this*this),或者在 lambda 函式體中可 ODR 使用的自動變數,或者一個對應的變數具有自動儲存期的結構化繫結(C++20 起),則如果該實體在表示式(包括在使用非靜態類成員之前隱式新增 this-> 時)的潛在求值表示式中命名,則它會隱式捕獲該實體。

為了確定隱式捕獲,typeid 從不被視為使其運算元未求值。

即使實體只在 lambda 函式體例項化後被丟棄語句中命名,也可能被隱式捕獲。

(C++17 起)
void f(int, const int (&)[2] = {}) {}   // #1
void f(const int&, const int (&)[1]) {} // #2
 
struct NoncopyableLiteralType
{
    constexpr explicit NoncopyableLiteralType(int n) : n_(n) {}
    NoncopyableLiteralType(const NoncopyableLiteralType&) = delete;
 
    int n_;
};
 
void test()
{
    const int x = 17;
 
    auto l0 = []{ f(x); };           // OK: calls #1, does not capture x
    auto g0 = [](auto a) { f(x); };  // same as above
 
    auto l1 = [=]{ f(x); };          // OK: captures x (since P0588R1) and calls #1
                                     // the capture can be optimized away
    auto g1 = [=](auto a) { f(x); }; // same as above
 
    auto ltid = [=]{ typeid(x); };   // OK: captures x (since P0588R1)
                                     // even though x is unevaluated
                                     // the capture can be optimized away
 
    auto g2 = [=](auto a)
    {
        int selector[sizeof(a) == 1 ? 1 : 2] = {};
        f(x, selector); // OK: is a dependent expression, so captures x
    };
 
    auto g3 = [=](auto a)
    {
        typeid(a + x);  // captures x regardless of
                        // whether a + x is an unevaluated operand
    };
 
    constexpr NoncopyableLiteralType w{42};
    auto l4 = []{ return w.n_; };      // OK: w is not odr-used, capture is unnecessary
    // auto l5 = [=]{ return w.n_; };  // error: w needs to be captured by copy
}

如果 lambda 的函式體ODR-使用按值捕獲的實體,則訪問閉包型別的成員。如果它沒有 ODR-使用該實體,則訪問的是原始物件。

void f(const int*);
void g()
{
    const int N = 10;
    [=]
    { 
        int arr[N]; // not an odr-use: refers to g's const int N
        f(&N); // odr-use: causes N to be captured (by copy)
               // &N is the address of the closure object's member N, not g's N
    }();
}

如果 lambda ODR-使用按引用捕獲的引用,則它使用的是原始引用所指向的物件,而不是捕獲的引用本身。

#include <iostream>
 
auto make_function(int& x)
{
    return [&] { std::cout << x << '\n'; };
}
 
int main()
{
    int i = 3;
    auto f = make_function(i); // the use of x in f binds directly to i
    i = 5;
    f(); // OK: prints 5
}

在捕獲預設值為 = 的 lambda 函式體中,任何可捕獲實體的型別都如同被捕獲一樣(因此如果 lambda 不是 mutable,通常會新增 const 限定符),即使該實體在未求值運算元中且未被捕獲(例如在decltype中)。

void f3()
{
    float x, &r = x;
    [=]
    { // x and r are not captured (appearance in a decltype operand is not an odr-use)
        decltype(x) y1;        // y1 has type float
        decltype((x)) y2 = y1; // y2 has type float const& because this lambda
                               // is not mutable and x is an lvalue
        decltype(r) r1 = y1;   // r1 has type float& (transformation not considered)
        decltype((r)) r2 = y2; // r2 has type float const&
    };
}

任何被 lambda 捕獲的實體(隱式或顯式)都被 lambda 表示式 ODR-使用(因此,巢狀 lambda 的隱式捕獲會觸發封閉 lambda 中的隱式捕獲)。

所有隱式捕獲的變數必須在 lambda 的可達作用域內宣告。

如果 lambda 捕獲了封閉物件(作為 this*this),則最近的封閉函式必須是非靜態成員函式,或者 lambda 必須在預設成員初始化器中。

struct s2
{
    double ohseven = .007;
    auto f() // nearest enclosing function for the following two lambdas
    {
        return [this]      // capture the enclosing s2 by reference
        {
            return [*this] // capture the enclosing s2 by copy (C++17)
            {
                return ohseven;// OK
            }
        }();
    }
 
    auto g()
    {
        return [] // capture nothing
        { 
            return [*this] {};// error: *this not captured by outer lambda expression
        }();
    }
};

如果 lambda 表示式(或泛型 lambda 的函式呼叫運算子的特化)(C++14 起) ODR-使用 *this 或任何具有自動儲存期的變數,則它必須被 lambda 表示式捕獲。

void f1(int i)
{
    int const N = 20;
    auto m1 = [=]
    {
        int const M = 30;
        auto m2 = [i]
        {
            int x[N][M]; // N and M are not odr-used 
                         // (ok that they are not captured)
            x[0][0] = i; // i is explicitly captured by m2
                         // and implicitly captured by m1
        };
    };
 
    struct s1 // local class within f1()
    {
        int f;
        void work(int n) // non-static member function
        {
            int m = n * n;
            int j = 40;
            auto m3 = [this, m]
            {
                auto m4 = [&, j] // error: j is not captured by m3
                {
                    int x = n; // error: n is implicitly captured by m4
                               // but not captured by m3
                    x += m;    // OK: m is implicitly captured by m4
                               // and explicitly captured by m3
                    x += i;    // error: i is outside of the reaching scope
                               // (which ends at work())
                    x += f;    // OK: this is captured implicitly by m4
                               // and explicitly captured by m3
                };
            };
        }
    };
}

類成員不能透過不帶初始化器的捕獲顯式捕獲(如上所述,capture-list 中只允許變數

class S
{
    int x = 0;
 
    void f()
    {
        int i = 0;
    //  auto l1 = [i, x] { use(i, x); };      // error: x is not a variable
        auto l2 = [i, x = x] { use(i, x); };  // OK, copy capture
        i = 1; x = 1; l2(); // calls use(0,0)
        auto l3 = [i, &x = x] { use(i, x); }; // OK, reference capture
        i = 2; x = 2; l3(); // calls use(1,2)
    }
};

當 lambda 使用隱式按值捕獲捕獲成員時,它不會複製該成員變數:使用成員變數 m 被視為表示式 (*this).m,並且 *this 總是被隱式按引用捕獲。

class S
{
    int x = 0;
 
    void f()
    {
        int i = 0;
 
        auto l1 = [=] { use(i, x); }; // captures a copy of i and
                                      // a copy of the this pointer
        i = 1; x = 1; l1();           // calls use(0, 1), as if
                                      // i by copy and x by reference
 
        auto l2 = [i, this] { use(i, x); }; // same as above, made explicit
        i = 2; x = 2; l2();           // calls use(1, 2), as if
                                      // i by copy and x by reference
 
        auto l3 = [&] { use(i, x); }; // captures i by reference and
                                      // a copy of the this pointer
        i = 3; x = 2; l3();           // calls use(3, 2), as if
                                      // i and x are both by reference
 
        auto l4 = [i, *this] { use(i, x); }; // makes a copy of *this,
                                             // including a copy of x
        i = 4; x = 4; l4();           // calls use(3, 2), as if
                                      // i and x are both by copy
    }
};

如果 lambda 表示式出現在預設實參中,則它不能顯式或隱式捕獲任何東西,除非所有捕獲都具有滿足預設實參中出現的表示式的限制條件的初始化器(C++14 起)

void f2()
{
    int i = 1;
 
    void g1( int = [i] { return i; }() ); // error: captures something
    void g2( int = [i] { return 0; }() ); // error: captures something
    void g3( int = [=] { return i; }() ); // error: captures something
 
    void g4( int = [=] { return 0; }() );       // OK: capture-less
    void g5( int = [] { return sizeof i; }() ); // OK: capture-less
 
    // C++14
    void g6( int = [x = 1] { return x; }() ); // OK: 1 can appear
                                              //     in a default argument
    void g7( int = [x = i] { return x; }() ); // error: i cannot appear
                                              //        in a default argument
}

匿名聯合體的成員不能被捕獲。位域只能按值捕獲。

如果巢狀 lambda m2 捕獲了也被直接封閉 lambda m1 捕獲的某些內容,則 m2 的捕獲會按如下方式轉換:

  • 如果封閉 lambda m1 按值捕獲,則 m2 捕獲的是 m1 閉包型別的非靜態成員,而不是原始變數或 *this;如果 m1 不是 mutable,則非靜態資料成員被視為 const 限定。
  • 如果封閉 lambda m1 按引用捕獲,則 m2 捕獲的是原始變數或 *this
#include <iostream>
 
int main()
{
    int a = 1, b = 1, c = 1;
 
    auto m1 = [a, &b, &c]() mutable
    {
        auto m2 = [a, b, &c]() mutable
        {
            std::cout << a << b << c << '\n';
            a = 4; b = 4; c = 4;
        };
        a = 3; b = 3; c = 3;
        m2();
    };
 
    a = 2; b = 2; c = 2;
 
    m1();                             // calls m2() and prints 123
    std::cout << a << b << c << '\n'; // prints 234
}

如果 lambda 捕獲了任何內容,則函式呼叫運算子的顯式物件引數(如果有)的型別只能是:

  • 閉包型別,
  • 公開且無歧義地派生自閉包型別的類型別,或者
  • 對可能是 cv 限定的此類型別的引用。
struct C 
{
    template<typename T>
    C(T);
};
 
void func(int i) 
{
    int x = [=](this auto&&) { return i; }();  // OK
    int y = [=](this C) { return i; }();       // error
    int z = [](this C) { return 42; }();       // OK
 
    auto lambda = [n = 42] (this auto self) { return n; };
    using Closure = decltype(lambda);
    struct D : private Closure {
        D(Closure l) : Closure(l) {}
        using Closure::operator();
        friend Closure;
    };
    D{lambda}(); // error
}
(C++23 起)

[編輯] 注意

功能測試宏 標準 特性
__cpp_lambdas 200907L (C++11) Lambda 表示式
__cpp_generic_lambdas 201304L (C++14) 泛型 lambda 表示式
201707L (C++20) 泛型 lambda 的顯式模板引數列表
__cpp_init_captures 201304L (C++14) Lambda 初始化捕獲
201803L (C++20) 允許在 lambda 初始化捕獲中使用包擴充套件
__cpp_capture_star_this 201603L (C++17) Lambda 透過值捕獲 *this 作為 [=, *this]
__cpp_constexpr 201603L (C++17) constexpr lambda
__cpp_static_call_operator 202207L (C++23) 無捕獲 lambda 的 static operator()

缺陷報告 P0588R1 稍微改變了隱式 lambda 捕獲的規則。截至 2023 年 10 月,一些主要實現尚未完全實現該 DR,因此在某些情況下仍使用檢測ODR-使用的舊規則。

P0588R1 之前的舊規則

如果 captures 具有 capture-default 並且沒有顯式捕獲封閉物件(作為 this*this),或者在 lambda 函式體中可 ODR 使用的自動變數,或者一個對應的變數具有自動儲存期的結構化繫結(C++20 起),則如果該實體滿足以下條件,它會隱式捕獲該實體:

  • 在依賴於泛型 lambda 的模板引數的表示式內的潛在求值表示式中命名,或者
(C++14 起)

[編輯] 示例

此示例展示瞭如何將 lambda 傳遞給泛型演算法,以及如何將 lambda 表示式產生的物件儲存在 std::function 物件中。

#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
 
int main()
{
    std::vector<int> c{1, 2, 3, 4, 5, 6, 7};
    int x = 5;
    c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end());
 
    std::cout << "c: ";
    std::for_each(c.begin(), c.end(), [](int i) { std::cout << i << ' '; });
    std::cout << '\n';
 
    // the type of a closure cannot be named, but can be inferred with auto
    // since C++14, lambda could own default arguments
    auto func1 = [](int i = 6) { return i + 4; };
    std::cout << "func1: " << func1() << '\n';
 
    // like all callable objects, closures can be captured in std::function
    // (this may incur unnecessary overhead)
    std::function<int(int)> func2 = [](int i) { return i + 4; };
    std::cout << "func2: " << func2(6) << '\n';
 
    constexpr int fib_max {8};
    std::cout << "Emulate `recursive lambda` calls:\nFibonacci numbers: ";
    auto nth_fibonacci = [](int n)
    {
        std::function<int(int, int, int)> fib = [&](int n, int a, int b)
        {
            return n ? fib(n - 1, a + b, a) : b;
        };
        return fib(n, 0, 1);
    };
 
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci(i) << (i < fib_max ? ", " : "\n");
 
    std::cout << "Alternative approach to lambda recursion:\nFibonacci numbers: ";
    auto nth_fibonacci2 = [](auto self, int n, int a = 0, int b = 1) -> int
    {
        return n ? self(self, n - 1, a + b, a) : b;
    };
 
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci2(nth_fibonacci2, i) << (i < fib_max ? ", " : "\n");
 
#ifdef __cpp_explicit_this_parameter
    std::cout << "C++23 approach to lambda recursion:\n";
    auto nth_fibonacci3 = [](this auto self, int n, int a = 0, int b = 1) -> int
    {
         return n ? self(n - 1, a + b, a) : b;
    };
 
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci3(i) << (i < fib_max ? ", " : "\n");
#endif
}

可能的輸出

c: 5 6 7
func1: 10
func2: 10
Emulate `recursive lambda` calls:
Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13
Alternative approach to lambda recursion:
Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 974 C++11 預設實參不允許出現在
lambda 表示式的引數列表中
允許
CWG 1048
(N3638)
C++11 返回型別只能推斷出包含一個 return 語句的 lambda 函式體的返回型別
僅包含一個 return 語句的 lambda 函式體
改進了返回
型別推導
CWG 1249 C++11 不清楚封閉的非 mutable lambda 的被捕獲成員是否被認為是 const
不清楚封閉的非 mutable lambda 的被捕獲成員是否被認為是 const
被認為是 const
CWG 1557 C++11 未指定閉包型別的轉換函式返回的函式型別的語言連結
未指定閉包型別的轉換函式返回的函式型別的語言連結
它具有 C++
語言連結
CWG 1607 C++11 lambda 表示式可以出現在
函式和函式模板簽名中
不允許
CWG 1612 C++11 匿名聯合的成員可以被捕獲 不允許
CWG 1722 C++11 無捕獲 lambda 的轉換函式
具有未指定的異常規範
轉換函式
是 noexcept
CWG 1772 C++11 lambda 函式體中 __func__ 的語義不明確 它引用閉包
類的 operator()
CWG 1780 C++14 不清楚泛型 lambda 閉包型別的成員是否可以顯式例項化或顯式特化
不清楚泛型 lambda 閉包型別的成員是否可以顯式例項化或顯式特化
兩者都不允許
CWG 1891 C++11 閉包具有已刪除的預設建構函式
和隱式複製/移動建構函式
沒有預設和預設的
複製/移動建構函式
CWG 1937 C++11 關於呼叫轉換函式結果的效果,未指定
呼叫其 operator() 對哪個物件具有相同的效果
在哪個物件上呼叫其 operator() 具有相同的效果
在一個預設構造的
閉包型別例項上
CWG 1973 C++11 閉包型別的 operator() 的引數列表
可以引用 trailing 中給定的引數列表
只能引用
params
CWG 2011 C++11 對於按引用捕獲的引用,未指定
捕獲的識別符號引用哪個實體
它引用原始的
引用實體
CWG 2095 C++11 按值捕獲函式右值引用的行為不明確
按值捕獲函式右值引用的行為不明確
已明確
CWG 2211 C++11 如果捕獲的名稱與引數名稱相同,則行為未指定
如果捕獲的名稱與引數名稱相同,則行為未指定
程式格式錯誤
在這種情況下,列舉是病態的
CWG 2358 C++14 出現在預設實參中的 lambda 表示式必須是無捕獲的,即使所有捕獲都用可以出現在預設實參中的表示式初始化
必須是無捕獲的,即使所有捕獲都用可以出現在預設實參中的表示式初始化
即使所有捕獲都用可以出現在預設實參中的表示式初始化
允許此類 lambda
帶有捕獲的表示式
CWG 2509 C++17 每個說明符在說明符序列中可以有多個
出現次數
每個說明符最多隻能
在說明符序列中出現一次
在說明符序列中出現一次
CWG 2561 C++23 具有顯式物件引數的 lambda 可以具有到不期望的函式指標型別的轉換函式
具有顯式物件引數的 lambda 可以具有到不期望的函式指標型別的轉換函式
它沒有這樣的
轉換函式
CWG 2881 C++23 當繼承不是公開的或有歧義時,可以為派生類例項化帶有顯式引數的 operator()
當繼承不是公開的或有歧義時,可以為派生類例項化帶有顯式引數的 operator()
導致格式錯誤
P0588R1 C++11 隱式 lambda 捕獲的規則檢測 ODR-使用 檢測被簡化

[編輯] 另見

auto 說明符 (C++11) 指定從表示式推導的型別 [編輯]
(C++11)
任何可複製構造的可呼叫物件的包裝器
(類模板) [編輯]
任何支援給定呼叫簽名中限定符的可呼叫物件的僅移動包裝器
(類模板) [編輯]

[編輯] 外部連結

巢狀函式 - 在另一個(封閉)函式內部定義的函式。