Namespaces
Variants

Lambda expressions (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

클로저 (스코프 내 변수를 캡처할 수 있는 이름 없는 함수 객체)를 생성합니다.

목차

구문

명시적 템플릿 매개변수 목록이 없는 람다 표현식 (비제네릭일 수 있음)
[ 캡처  ] 프론트 속성  (선택 사항) ( 매개변수  ) 명세  (선택 사항) 예외  (선택 사항)
백 속성  (선택 사항) 후행  (선택 사항) 요구 사항  (선택 사항) 계약 명세  (선택 사항) { 본문 }
(1)
[ 캡처  ] { 본문 } (2) (C++23 이전)
[ 캡처  ] 프론트 속성  (선택 사항) 후행  (선택 사항) 계약 명세  (선택 사항) { 본문 } (2) (C++23 이후)
[ 캡처  ] 프론트 속성  (선택 사항) 예외
백 속성  (선택 사항) 후행  (선택 사항) 계약 명세  (선택 사항) { 본문 }
(3) (C++23 이후)
[ 캡처  ] 프론트 속성  (선택 사항) 명세 예외  (선택 사항)
백 속성  (선택 사항) 후행  (선택 사항) 계약 명세  (선택 사항) { 본문 }
(4) (C++23 이후)
명시적 템플릿 매개변수 목록을 갖는 람다 표현식 (항상 제네릭) (C++20부터)
[ 캡처  ] < 템플릿 매개변수  > 템플릿 요구사항  (선택 사항)
앞쪽 속성  (선택 사항) ( 매개변수  ) 명세  (선택 사항) 예외 명세  (선택 사항)
뒤쪽 속성  (선택 사항) 후행 반환 타입  (선택 사항) 요구사항  (선택 사항) 계약 명세  (선택 사항) { 본문 }
(1)
[ 캡처  ] < 템플릿 매개변수  > 템플릿 요구사항  (선택 사항) { 본문 } (2) (C++23 이전)
[ 캡처  ] < 템플릿 매개변수  > 템플릿 요구사항  (선택 사항)
앞쪽 속성  (선택 사항) 후행 반환 타입  (선택 사항) 계약 명세  (선택 사항) { 본문 }
(2) (C++23 이후)
[ 캡처  ] < 템플릿 매개변수  > 템플릿 요구사항  (선택 사항) 앞쪽 속성  (선택 사항) 예외 명세
뒤쪽 속성  (선택 사항) 후행 반환 타입  (선택 사항) 계약 명세  (선택 사항) { 본문 }
(3) (C++23 이후)
[ 캡처  ] < 템플릿 매개변수  > 템플릿 요구사항  (선택 사항) 앞쪽 속성  (선택 사항) 명세 예외 명세  (선택 사항)
뒤쪽 속성  (선택 사항) 후행 반환 타입  (선택 사항) 계약 명세  (선택 사항) { 본문 }
(4) (C++23 이후)
1) 매개변수 목록이 있는 람다 표현식.
2-4) 매개변수 목록이 없는 람다 표현식.
2) 가장 간단한 구문. back-attr 은 적용할 수 없습니다.
3,4) back-attr specs except 중 하나라도 존재하는 경우에만 적용할 수 있습니다.

설명

captures - 캡처할 개체를 지정합니다 .
tparams - 비어 있지 않은 쉼표로 구분된 템플릿 매개변수 목록으로, 제네릭 람다의 템플릿 매개변수에 이름을 제공하는 데 사용됩니다(아래 ClosureType::operator() 참조).
t-requires - tparams constraints 를 추가합니다.

만약 t-requires 가 속성 지정자 시퀀스로 끝나는 경우, 해당 시퀀스의 속성들은 front-attr 의 속성으로 처리됩니다.

(C++23부터)
front-attr - (C++23부터) 속성 지정자 시퀀스 가 클로저 타입의 operator ( ) 에 적용됩니다 (따라서 [[ noreturn ]] 속성을 사용할 수 있습니다).
params - 클로저 타입의 operator ( ) 연산자의 매개변수 목록 .

명시적 객체 매개변수 를 가질 수 있습니다.

(C++23 이후)
specs - 다음 지정자들의 목록으로, 각 지정자는 각 시퀀스에서 최대 한 번만 허용됩니다.
지정자 효과
mutable body 가 복사로 캡처한 객체를 수정하고, 해당 객체의 비-const 멤버 함수를 호출할 수 있도록 허용합니다.
  • 명시적 객체 매개변수가 있는 경우 사용할 수 없습니다.
(C++23부터)
constexpr
(C++17부터)
operator ( ) constexpr 함수 임을 명시적으로 지정합니다.
  • operator ( ) 가 모든 constexpr 함수 요구사항을 충족하는 경우, constexpr 이 없더라도 operator ( ) 는 constexpr이 됩니다.
consteval
(C++20부터)
operator ( ) 즉시 함수 임을 지정합니다.
  • consteval constexpr 는 동시에 지정할 수 없습니다.
static
(C++23부터)
operator ( ) 정적 멤버 함수 임을 지정합니다.
  • static mutable 는 동시에 지정할 수 없습니다.
  • captures 가 비어있지 않거나 명시적 객체 매개변수가 있는 경우 사용할 수 없습니다.
except - 클로저 타입의 operator ( ) 에 대한 dynamic exception specification 또는 (until C++20) noexcept specifier 를 제공합니다.
back-attr - 속성 지정자 시퀀스 가 클로저 타입의 operator ( ) 연산자의 타입에 적용됩니다 (따라서 [[ noreturn ]] 속성은 사용할 수 없습니다).
trailing - -> ret , 여기서 ret 는 반환 타입을 지정합니다.
requires - (since C++20) 클로저 타입의 operator ( ) constraints 를 추가합니다.
contract-specs - (since C++26) 클로저 타입의 operator ( ) 에 대한 함수 계약 지정자 목록입니다.
body - 함수 본문.


만약 auto 가 매개변수의 타입으로 사용되거나 명시적 템플릿 매개변수 목록이 제공되는 경우 (C++20부터) , 해당 람다는 제네릭 람다 입니다.

(C++14부터)

변수 __func__ 본문 시작 부분에 암시적으로 정의되며, 의미는 여기 에 설명된 대로입니다.

클로저 타입

람다 표현식은 고유한 이름 없는 비- union 비- aggregate 클래스 타입의 prvalue 표현식으로, closure type 이라고 알려져 있으며, 람다 표현식을 포함하는 가장 작은 블록 범위, 클래스 범위, 또는 네임스페이스 범위에서 ( ADL 목적으로) 선언됩니다.

클로저 타입은 structural 타입이며, 이는 captures 가 비어 있을 때에만 해당합니다.

(since C++20)

클로저 타입은 다음과 같은 멤버들을 가지며, 이들은 명시적 인스턴스화 , 명시적 특수화 , 또는 (C++14 이후) 프렌드 선언 에서 이름을 지정할 수 없습니다:

ClosureType:: operator()( params )

ret operator ( ) ( params ) { body }
(static 및 const가 있을 수 있음, 아래 참조)
template < template - params >
ret operator ( ) ( params ) { body }
(C++14부터)
(제네릭 람다, static 및 const가 있을 수 있음, 아래 참조)

람다 표현식의 본문을 호출 시 실행합니다. 변수에 접근할 때는 복사본(값으로 캡처된 개체의 경우) 또는 원본 객체(참조로 캡처된 개체의 경우)에 접근합니다.

operator ( ) 의 매개변수 목록은 params 가 제공된 경우 해당 목록이며, 그렇지 않으면 매개변수 목록이 비어 있습니다.

operator ( ) 의 반환 타입은 trailing 에 지정된 타입입니다.

만약 trailing 이 제공되지 않으면, operator ( ) 의 반환 타입은 자동으로 추론 됩니다. [1]

람다 지정자에서 mutable 키워드가 사용되지 않았거나 명시적 객체 매개변수가 존재하지 않는 경우 (또는 명시적 객체 매개변수가 없는 경우) (C++23부터) , operator ( ) 의 cv 한정자는 const 이며, 복사로 캡처된 객체들은 이 operator ( ) 내부에서 수정할 수 없습니다. 명시적인 const 한정자는 허용되지 않습니다. operator ( ) 는 virtual이 될 수 없으며 volatile 한정자를 가질 수 없습니다.

operator ( ) constexpr 함수 의 요구사항을 충족할 경우 항상 constexpr입니다. 또한 람다 지정자에 constexpr 키워드가 사용된 경우에도 constexpr입니다.

(C++17부터)

operator ( ) 는 람다 지정자에 consteval 키워드가 사용된 경우 즉시 함수(immediate function) 입니다.

(C++20부터)

operator ( ) 는 람다 지정자에 static 키워드가 사용된 경우 정적 멤버 함수(static member function) 입니다.

operator ( ) params 에 명시적 객체 매개변수(explicit object parameter)가 포함된 경우 명시적 객체 멤버 함수(explicit object member function) 입니다.

(C++23부터)


params 에서 타입이 auto 로 지정된 각 매개변수에 대해, 발명된 템플릿 매개변수가 template-params 에 추가되며, 이는 나타나는 순서대로 진행됩니다. 발명된 템플릿 매개변수는 해당 params 의 함수 멤버가 함수 매개변수 팩인 경우 parameter pack 일 수 있습니다.

// 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
(since C++14)


람다 정의가 명시적 템플릿 매개변수 목록을 사용하는 경우, 해당 템플릿 매개변수 목록은 operator ( ) 와 함께 사용됩니다. params 에서 타입이 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부터)

람다 표현식의 예외 명세는 except operator ( ) 에 적용됩니다.

이름 검색 의 목적과 this 포인터 의 타입 및 값을 결정하고, 비정적 클래스 멤버에 접근하기 위해, 클로저 타입의 operator ( ) 본문은 람다 표현식의 컨텍스트 내에서 고려됩니다.

struct X
{
    int x, y;
    int operator()(int);
    void f()
    {
        // 다음 람다의 컨텍스트는 멤버 함수 X::f입니다
        [=]() -> int
        {
            return operator()(this->x + y); // X::operator()(this->x + (*this).y)
                                            // this의 타입은 X*입니다
        };
    }
};

댕글링 참조

참조로 캡처된 비참조 개체(암시적 또는 명시적)의 수명이 종료된 후 클로저 객체의 operator ( ) 가 호출되면 미정의 동작이 발생합니다. C++ 클로저는 참조로 캡처된 객체들의 수명을 연장하지 않습니다.

현재 * this 객체를 통해 캡처된 this 의 수명에도 동일하게 적용됩니다.

  1. 함수 반환 타입 추론은 C++14에서 도입되었지만, 그 규칙은 C++11의 람다 반환 타입 추론에도 사용 가능합니다.

ClosureType:: operator ret (*)( params )()

캡처 없는 비제네릭 람다
using F = ret ( * ) ( params ) ;
operator F ( ) const noexcept ;
(C++17 이전)
using F = ret ( * ) ( params ) ;
constexpr operator F ( ) const noexcept ;
(C++17 이후)
캡처 없는 제네릭 람다
template < template - params > using fptr_t = /* see below */ ;

template < template - params >

operator fptr_t < template - params > ( ) const noexcept ;
(C++14 이후)
(C++17 이전)
template < template - params > using fptr_t = /* see below */ ;

template < template - params >

constexpr operator fptr_t < template - params > ( ) const noexcept ;
(C++17 이후)

사용자 정의 변환 함수 는 람다 표현식에 캡처 가 없고 명시적 객체 매개변수가 없는 경우에만 (C++23부터) 정의됩니다. 이는 클로저 객체의 public, constexpr, (C++17부터) non-virtual, non-explicit, const noexcept 멤버 함수입니다.

이 함수는 함수 호출 연산자(또는 제네릭 람다의 경우 특수화)가 immediate function 인 경우 immediate function입니다.

(since C++20)

캡처 없는 제네릭 람다는 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++ language linkage 를 가진 함수에 대한 포인터로, 이 포인터를 통해 함수를 호출하면 클로저 타입의 기본 생성된 인스턴스에 대해 클로저 타입의 함수 호출 연산자를 호출하는 것과 동일한 효과를 가집니다.

(C++14 이전)

변환 함수 (템플릿)가 반환하는 값은 C++ language linkage 를 가진 함수에 대한 포인터로, 이 포인터를 통해 함수를 호출하면 다음과 동일한 효과를 가집니다:

  • non-generic lambda의 경우, 클로저 타입의 기본 생성된 인스턴스에 대해 클로저 타입의 operator ( ) 를 호출하는 것과 동일합니다.
  • generic lambda의 경우, 클로저 타입의 기본 생성된 인스턴스에 대해 해당 generic lambda의 operator ( ) 특수화를 호출하는 것과 동일합니다.
(C++14 이후)
(C++23 이전)

변환 함수 (템플릿)가 반환하는 값은

  • 만약 operator ( ) 가 static이면, C++ language linkage 를 가진 해당 operator ( ) 에 대한 포인터입니다.
  • 그렇지 않으면, C++ language linkage 를 가진 함수에 대한 포인터로, 이 포인터를 통해 함수를 호출하면 다음과 동일한 효과를 가집니다:
    • non-generic lambda의 경우, 클로저 타입의 기본 생성된 인스턴스에 대해 클로저 타입의 operator ( ) 를 호출하는 것과 동일합니다.
    • generic lambda의 경우, 클로저 타입의 기본 생성된 인스턴스에 대해 해당 generic lambda의 operator ( ) 특수화를 호출하는 것과 동일합니다.
(C++23 이후)


함수 호출 연산자(또는 제네릭 람다의 경우 특수화)가 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 ( ) 가 non-throwing 예외 명세를 가지는 경우, 이 함수가 반환하는 포인터는 noexcept 함수에 대한 포인터 타입을 가집니다.

(C++17부터)

ClosureType:: ClosureType()

ClosureType ( ) = default ;
(C++20부터)
(캡처가 지정되지 않은 경우에만)
ClosureType ( const ClosureType & ) = default ;
ClosureType ( ClosureType && ) = default ;

클로저 타입은 DefaultConstructible 이 아닙니다. 클로저 타입은 기본 생성자를 가지지 않습니다.

(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 이후)
(그 외의 경우)

복사 할당 연산자는 삭제된 것으로 정의됩니다(이동 할당 연산자는 선언되지 않음). 클로저 타입은 CopyAssignable 을 만족하지 않습니다.

(C++20 이전)

캡처 가 지정되지 않은 경우, 클로저 타입은 기본화된 복사 할당 연산자와 기본화된 이동 할당 연산자를 가집니다. 그렇지 않은 경우 삭제된 복사 할당 연산자를 가집니다(이는 실제로 아무것도 캡처하지 않는 경우에도 캡처-기본값 이 있는 경우를 포함합니다).

(C++20 이후)

ClosureType:: ~ClosureType()

~ClosureType ( ) = default ;

소멸자는 암시적으로 선언됩니다.

ClosureType:: Captures

T1 a ;

T2 b ;

...

람다 표현식이 복사로 어떤 것을 캡처하는 경우(암시적으로 [=] 캡처 절을 사용하거나 & 문자를 포함하지 않는 캡처로 명시적으로, 예: [a, b, c] ), 클로저 타입은 그렇게 캡처된 모든 엔티티들의 복사본을 보유하는 이름 없는 비정적 데이터 멤버들을 지정되지 않은 순서로 선언합니다.

초기화자가 없는 캡처에 해당하는 데이터 멤버들은 람다 표현식이 평가될 때 직접 초기화 됩니다. 초기화자가 있는 캡처에 해당하는 멤버들은 초기화자의 요구에 따라 초기화됩니다(복사 초기화 또는 직접 초기화일 수 있음). 배열이 캡처된 경우, 배열 요소들은 인덱스 순서가 증가하는 방향으로 직접 초기화됩니다. 데이터 멤버들이 초기화되는 순서는 그들이 선언된 순서(지정되지 않음)입니다.

각 데이터 멤버의 타입은 해당 캡처된 엔티티의 타입입니다. 단, 엔티티가 참조 타입인 경우는 예외입니다(이 경우, 함수에 대한 참조는 참조된 함수에 대한 좌측값 참조로 캡처되고, 객체에 대한 참조는 참조된 객체의 복사본으로 캡처됩니다).

참조로 캡처되는 엔티티들( capture-default [&] 를 사용하거나 & 문자를 사용하는 경우, 예: [&a, &b, &c] )의 경우, 클로저 타입에 추가 데이터 멤버들이 선언되는지는 지정되지 않습니다 , 하지만 그러한 추가 멤버들은 LiteralType 요구사항을 충족해야 합니다 (C++17부터) .


람다 표현식은 평가되지 않는 표현식 , 템플릿 인수 , 별칭 선언 , typedef 선언 , 그리고 함수 본문과 함수의 기본 인수 를 제외한 함수(또는 함수 템플릿) 선언의 어느 곳에서도 허용되지 않습니다.

(C++20 이전)

람다 캡처

캡처 는 람다 함수 본문 내부에서 접근 가능한 외부 변수를 정의합니다. 구문은 다음과 같이 정의됩니다:

캡처 기본값 (1)
캡처 목록 (2)
캡처 기본값 , 캡처 목록 (3)
capture-default - 다음 중 하나 & =
capture-list - 쉼표로 구분된 capture 목록


capture 의 구문은 다음과 같이 정의됩니다:

identifier (1)
identifier ... (2)
identifier initializer (3) (C++14부터)
& identifier (4)
& identifier ... (5)
& identifier initializer (6) (C++14부터)
this (7)
* this (8) (C++17부터)
... identifier initializer (9) (C++20부터)
& ... identifier initializer (10) (C++20부터)
1) 단순 복사 캡처
2) 팩 확장인 단순한 값 복사 캡처 pack expansion
3) 초기화자를 사용한 값 복사 캡처 initializer
4) 단순 참조 캡처
5) 팩 확장인 단순 참조 캡처 pack expansion
6) 초기화자를 사용한 참조에 의한 캡처
7) 현재 객체의 단순 참조 캡처
8) 현재 객체의 단순 복사 캡처
9) 초기화자가 팩 확장인 복사 캡처
10) 팩 확장인 초기화자를 사용한 참조에 의한 캡처

만약 capture-default & 인 경우, 이후의 단순 캡처는 & 로 시작해서는 안 됩니다.

struct S2 { void f(int i); };
void S2::f(int i)
{
    [&] {};          // 정상: 참조에 의한 캡처 기본값
    [&, i] {};       // 정상: 참조에 의한 캡처, 단 i는 복사에 의해 캡처됨
    [&, &i] {};      // 오류: 참조 캡처가 기본값일 때 참조에 의한 캡처
    [&, this] {};    // 정상, [&]와 동등함
    [&, this, i] {}; // 정상, [&, i]와 동등함
}

만약 capture-default = 인 경우, 이후의 단순 캡처들은 & 로 시작해야 하거나 *this 이어야 합니다 (C++17부터) 또는 this 이어야 합니다 (C++20부터) .

struct S2 { void f(int i); };
void S2::f(int i)
{
    [=] {};        // OK: 복사 캡처 기본값
    [=, &i] {};    // OK: 복사 캡처, 단 i는 참조로 캡처됨
    [=, *this] {}; // C++17까지: 오류: 잘못된 구문
                   // C++17부터: OK: 바깥쪽 S2를 복사로 캡처
    [=, this] {};  // C++20까지: 오류: =가 기본값일 때 this 사용
                   // C++20부터: OK, [=]와 동일
}

모든 캡처는 한 번만 나타날 수 있으며, 그 이름은 모든 매개변수 이름과 달라야 합니다:

struct S2 { void f(int i); };
void S2::f(int i)
{
    [i, i] {};        // 오류: i가 중복됨
    [this, *this] {}; // 오류: "this"가 중복됨 (C++17)
    [i] (int i) {};   // 오류: 매개변수와 캡처가 동일한 이름을 가짐
}

람다 표현식은 변수를 캡처하지 않고 사용할 수 있습니다. 만약 해당 변수가

  • 비-지역 변수이거나 정적 또는 스레드 로컬 저장 기간 을 가지는 경우 (이 경우 변수는 캡처될 수 없음), 또는
  • 상수 표현식 으로 초기화된 참조인 경우.

람다 표현식은 변수가 다음 조건을 만족할 때 캡처하지 않고도 해당 변수의 값을 읽을 수 있습니다

  • const non-volatile 정수 또는 열거형 타입을 가지며 constant expression 으로 초기화되었거나,
  • constexpr이며 mutable 멤버가 없는 경우.

현재 객체( * this )는 캡처 기본값이 존재하는 경우 암시적으로 캡처될 수 있습니다. 암시적으로 캡처되면, 캡처 기본값이 = 인 경우에도 항상 참조로 캡처됩니다. 캡처 기본값이 = 일 때 * this 의 암시적 캡처는 deprecated되었습니다. (since C++20)

다음 조건 중 하나를 만족하는 람다 표현식만이 초기화자가 없는 capture-default 또는 capture 를 가질 수 있습니다:

(C++26부터)

이러한 람다 표현식의 경우, 도달 범위(reaching scope) 는 가장 안쪽의 enclosing function(및 해당 매개변수)까지 포함하는 enclosing scope들의 집합으로 정의됩니다. 여기에는 중첩된 블록 범위와 이 람다가 중첩된 경우 enclosing lambda들의 범위가 포함됩니다.

초기화자가 없는 캡처( this -캡처 제외)의 식별자 는 람다의 도달 범위(reaching scope) 에서 일반적인 비한정 이름 조회(unqualified name lookup) 를 사용하여 조회됩니다. 조회 결과는 도달 범위에서 선언된 자동 저장 기간을 가지는 변수 여야 하며 , 또는 해당 변수가 이러한 요구사항을 충족하는 구조化 바인딩(structured binding) (C++20부터) . 해당 엔티티는 명시적으로 캡처(explicitly captured) 됩니다.

초기화자를 갖는 캡처를 init-capture 라고 하며, 이는 auto 타입 지정자와 동일한 초기화자로 선언된 변수를 선언하고 명시적으로 캡처하는 것처럼 동작합니다. 이 변수의 선언 영역은 람다 표현식의 본문입니다(즉, 초기화자 내에서는 스코프에 속하지 않음). 단, 다음과 같은 예외가 있습니다:

  • 캡처가 복사에 의한 경우, 클로저 객체의 도입된 비정적 데이터 멤버는 해당 변수를 참조하는 또 다른 방법입니다;
    • 즉, 원본 변수는 실제로 존재하지 않으며, auto 를 통한 타입 추론과 초기화는 비정적 데이터 멤버에 적용됩니다;
  • 캡처가 참조에 의한 경우, 참조 변수의 수명은 클로저 객체의 수명이 종료될 때 끝납니다.

이는 다음과 같은 캡처로 이동 전용(move-only) 타입을 캡처하는 데 사용됩니다: 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 )를 명시적으로 캡처하지 않거나, 람다 본문에서 odr-usable 인 자동 변수 , 또는 해당 변수가 원자적 저장 기간을 가지는 structured binding (C++20부터) 을 명시적으로 캡처하지 않으면, 해당 엔티티가 표현식 내에서 potentially-evaluated 표현식에 사용될 때(비정적 클래스 멤버 사용 앞에 암시적 this - > 가 추가되는 경우 포함) 엔티티를 암시적으로 캡처합니다.

암시적 캡처를 결정하기 위한 목적으로, typeid 는 피연산자를 평가되지 않은 상태로 만드는 것으로 간주되지 않습니다.

엔티티들은 람다 본문의 인스턴스화 이후 폐기된 문장 내에서만 이름이 사용되더라도 암시적으로 캡처될 수 있습니다.

(since 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: #1을 호출하며, x를 캡처하지 않음
    auto g0 = [](auto a) { f(x); };  // 위와 동일
    auto l1 = [=]{ f(x); };          // OK: x를 캡처하고(P0588R1 이후) #1을 호출
                                     // 캡처는 최적화를 통해 제거될 수 있음
    auto g1 = [=](auto a) { f(x); }; // 위와 동일
    auto ltid = [=]{ typeid(x); };   // OK: x를 캡처함(P0588R1 이후)
                                     // x가 평가되지 않은 피연산자임에도 불구하고
                                     // 캡처는 최적화를 통해 제거될 수 있음
    auto g2 = [=](auto a)
    {
        int selector[sizeof(a) == 1 ? 1 : 2] = {};
        f(x, selector); // OK: 종속 표현식이므로 x를 캡처함
    };
    auto g3 = [=](auto a)
    {
        typeid(a + x);  // a + x가 평가되지 않은 피연산자인지 여부와 관계없이
                        // x를 캡처함
    };
    constexpr NoncopyableLiteralType w{42};
    auto l4 = []{ return w.n_; };      // OK: w는 odr-사용되지 않으며, 캡처가 필요하지 않음
    // auto l5 = [=]{ return w.n_; };  // 오류: w는 복사에 의해 캡처되어야 함
}

람다의 본문이 odr-uses 복사에 의해 캡처된 엔티티를 사용하는 경우, 클로저 타입의 멤버에 접근합니다. 엔티티를 odr-using하지 않는 경우, 접근은 원본 객체에 이루어집니다:

void f(const int*);
void g()
{
    const int N = 10;
    [=]
    { 
        int arr[N]; // odr-use가 아님: g의 const int N을 참조함
        f(&N); // odr-use: N이 캡처되도록 함(복사에 의해)
               // &N은 g의 N이 아닌 클로저 객체의 멤버 N의 주소임
    }();
}

람다가 참조로 캡처된 참조를 odr-사용하는 경우, 이는 캡처된 참조 자체가 아닌 원본 참조가 참조하는 객체를 사용하는 것입니다:

#include <iostream>
auto make_function(int& x)
{
    return [&] { std::cout << x << '\n'; };
}
int main()
{
    int i = 3;
    auto f = make_function(i); // f에서 x의 사용은 i에 직접 바인딩됩니다
    i = 5;
    f(); // OK: 5를 출력합니다
}

캡처 기본값 = 를 가진 람다 본문 내에서는, 캡처 가능한 모든 엔티티의 타입은 마치 캡처된 것처럼 처리됩니다(따라서 람다가 mutable 가 아닌 경우 const 한정자가 종종 추가됩니다). 비록 해당 엔티티가 평가되지 않은 피연산자에 있고 실제로 캡처되지 않은 경우에도(예: decltype 내에서) 이 규칙이 적용됩니다:

void f3()
{
    float x, &r = x;
    [=]
    { // x와 r은 캡처되지 않음 (decltype 피연산자 내 등장은 odr-use가 아님)
        decltype(x) y1;        // y1은 float 타입을 가짐
        decltype((x)) y2 = y1; // y2는 float const& 타입을 가짐. 왜냐하면 이 람다는
                               // mutable이 아니고 x는 lvalue이기 때문
        decltype(r) r1 = y1;   // r1은 float& 타입을 가짐 (변환은 고려되지 않음)
        decltype((r)) r2 = y2; // r2는 float const& 타입을 가짐
    };
}

람다에 의해 캡처된(암시적으로 또는 명시적으로) 모든 엔티티는 람다 표현식에 의해 odr-used됩니다(따라서 중첩된 람다에 의한 암시적 캡처는 둘러싸는 람다에서 암시적 캡처를 트리거합니다).

암시적으로 캡처된 모든 변수는 람다의 도달 범위(reaching scope) 내에서 선언되어야 합니다.

람다가 둘러싼 객체를 캡처하는 경우( this 또는 * this ), 가장 가까운 둘러싼 함수는 비정적 멤버 함수이거나 람다는 기본 멤버 초기화자 내에 있어야 합니다:

struct s2
{
    double ohseven = .007;
    auto f() // 다음 두 람다를 위한 가장 가까운 둘러싸는 함수
    {
        return [this]      // 둘러싸는 s2를 참조로 캡처
        {
            return [*this] // 둘러싸는 s2를 복사로 캡처 (C++17)
            {
                return ohseven;// OK
            }
        }();
    }
    auto g()
    {
        return [] // 아무것도 캡처하지 않음
        { 
            return [*this] {};// 오류: *this가 외부 람다 표현식에 의해 캡처되지 않음
        }();
    }
};

람다 표현식이 (또는 제네릭 람다의 함수 호출 연산자 특수화) (since C++14) ODR-사용하는 경우 * this 또는 자동 저장 기간을 가진 모든 변수는 반드시 람다 표현식에 의해 캡처되어야 합니다.

void f1(int i)
{
    int const N = 20;
    auto m1 = [=]
    {
        int const M = 30;
        auto m2 = [i]
        {
            int x[N][M]; // N과 M은 odr-사용되지 않음
                         // (캡처되지 않아도 문제없음)
            x[0][0] = i; // i는 m2에 의해 명시적으로 캡처됨
                         // 그리고 m1에 의해 암시적으로 캡처됨
        };
    };
    struct s1 // f1() 내부의 지역 클래스
    {
        int f;
        void work(int n) // 비정적 멤버 함수
        {
            int m = n * n;
            int j = 40;
            auto m3 = [this, m]
            {
                auto m4 = [&, j] // 오류: j는 m3에 의해 캡처되지 않음
                {
                    int x = n; // 오류: n은 m4에 의해 암시적으로 캡처되지만
                               // m3에 의해 캡처되지 않음
                    x += m;    // OK: m은 m4에 의해 암시적으로 캡처되고
                               // m3에 의해 명시적으로 캡처됨
                    x += i;    // 오류: i는 도달 범위를 벗어남
                               // (범위는 work()에서 끝남)
                    x += f;    // OK: this는 m4에 의해 암시적으로 캡처되고
                               // m3에 의해 명시적으로 캡처됨
                };
            };
        }
    };
}

클래스 멤버는 초기화자 없이 캡처에서 명시적으로 캡처될 수 없습니다 (위에서 언급한 바와 같이, variables 만이 capture-list 에 허용됩니다):

class S
{
    int x = 0;
    void f()
    {
        int i = 0;
    //  auto l1 = [i, x] { use(i, x); };      // 오류: x는 변수가 아님
        auto l2 = [i, x = x] { use(i, x); };  // OK, 복사 캡처
        i = 1; x = 1; l2(); // use(0,0) 호출
        auto l3 = [i, &x = x] { use(i, x); }; // OK, 참조 캡처
        i = 2; x = 2; l3(); // use(1,2) 호출
    }
};

람다가 암시적 복사 캡처를 사용하여 멤버를 캡처할 때, 해당 멤버 변수의 복사본을 만들지 않습니다: 멤버 변수 m 의 사용은 ( * this ) . m 표현식으로 처리되며, * this 는 항상 암시적으로 참조로 캡처됩니다:

class S
{
    int x = 0;
    void f()
    {
        int i = 0;
        auto l1 = [=] { use(i, x); }; // i의 복사본과 this 포인터의 복사본을 캡처합니다
                                      //
        i = 1; x = 1; l1();           // use(0, 1)을 호출합니다, 마치
                                      // i는 복사로, x는 참조로 전달된 것처럼
        auto l2 = [i, this] { use(i, x); }; // 위와 동일하며, 명시적으로 표현되었습니다
        i = 2; x = 2; l2();           // use(1, 2)을 호출합니다, 마치
                                      // i는 복사로, x는 참조로 전달된 것처럼
        auto l3 = [&] { use(i, x); }; // i를 참조로 캡처하고
                                      // this 포인터의 복사본을 캡처합니다
        i = 3; x = 2; l3();           // use(3, 2)을 호출합니다, 마치
                                      // i와 x 모두 참조로 전달된 것처럼
        auto l4 = [i, *this] { use(i, x); }; // *this의 복사본을 생성하며,
                                             // x의 복사본을 포함합니다
        i = 4; x = 4; l4();           // use(3, 2)을 호출합니다, 마치
                                      // i와 x 모두 복사로 전달된 것처럼
    }
};

람다 표현식이 기본 인수 에 나타나는 경우, 명시적 또는 암시적으로 아무것도 캡처할 수 없습니다 , 모든 캡처가 기본 인수에 나타나는 표현식의 제약 조건을 만족하는 초기화자를 갖는 경우는 제외 (C++14부터) :

void f2()
{
    int i = 1;
    void g1( int = [i] { return i; }() ); // 오류: 무언가를 캡처함
    void g2( int = [i] { return 0; }() ); // 오류: 무언가를 캡처함
    void g3( int = [=] { return i; }() ); // 오류: 무언가를 캡처함
    void g4( int = [=] { return 0; }() );       // OK: 캡처 없음
    void g5( int = [] { return sizeof i; }() ); // OK: 캡처 없음
    // C++14
    void g6( int = [x = 1] { return x; }() ); // OK: 1은 기본 인수에 나타날 수 있음
                                              //     
    void g7( int = [x = i] { return x; }() ); // 오류: i는 기본 인수에 나타날 수 없음
                                              //        
}

익명 공용체 멤버들은 캡처될 수 없습니다. 비트 필드 는 복사에 의해서만 캡처될 수 있습니다.

중첩된 람다 m2 가 바로 바깥쪽 람다 m1 에서도 캡처하는 대상을 캡처하는 경우, m2 의 캡처는 다음과 같이 변환됩니다:

  • 바깥쪽 람다 m1 이 복사로 캡처하는 경우, m2 는 원본 변수나 * this 가 아닌 m1 의 클로저 타입의 비정적 멤버를 캡처합니다; m1 이 mutable이 아닌 경우, 비정적 데이터 멤버는 const 한정된 것으로 간주됩니다.
  • 바깥쪽 람다 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();                             // m2()를 호출하고 123을 출력
    std::cout << a << b << c << '\n'; // 234를 출력
}

람다가 어떤 것을 캡처하는 경우, 함수 호출 연산자의 명시적 객체 매개변수(있는 경우)의 타입은 오직 다음 중 하나만 될 수 있습니다:

  • 클로저 타입,
  • 클로저 타입으로부터 공개적이고 명확하게 파생된 클래스 타입, 또는
  • 가능하게 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) 람다 표현식
__cpp_generic_lambdas 201304L (C++14) 제네릭 람다 표현식
201707L (C++20) 제네릭 람다를 위한 명시적 템플릿 매개변수 목록
__cpp_init_captures 201304L (C++14) 람다 초기화 캡처
201803L (C++20) 람다 초기화 캡처에서 팩 확장 허용
__cpp_capture_star_this 201603L (C++17) 값으로 * this 람다 캡처 [ = , * this ]
__cpp_constexpr 201603L (C++17) constexpr 람다
__cpp_static_call_operator 202207L (C++23) 캡처 없는 람다를 위한 static operator ( )

암시적 람다 캡처 규칙은 결함 보고서 P0588R1 에 의해 약간 변경되었습니다. 2023년 10월 기준으로, 일부 주요 구현체들이 이 DR을 완전히 구현하지 않았기 때문에, odr-use 를 감지하는 이전 규칙이 여전히 일부 경우에 사용됩니다.

P0588R1 이전의 구 규칙

캡처 캡처 기본값 이 있고 둘러싸는 객체( this 또는 *this )를 명시적으로 캡처하지 않거나, 람다 본문에서 ODR 사용 가능 한 자동 변수 , 또는 해당 변수가 원자적 저장 기간을 가지는 구조化 바인딩 (C++20부터) 을 명시적으로 캡처하지 않으면, 다음 조건에서 해당 개체를 암시적으로 캡처합니다:

  • 제네릭 람다의 템플릿 매개변수에 종속된 표현식 내에서 잠재적으로 평가되는 표현식에 이름이 사용된 경우, 또는
(C++14부터)

예제

이 예제는 람다를 제네릭 알고리즘에 전달하는 방법과 람다 표현식으로 생성된 객체들이 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';
    // 클로저의 타입은 이름을 지정할 수 없지만, auto로 추론할 수 있습니다
    // C++14부터, 람다는 기본 인자를 가질 수 있습니다
    auto func1 = [](int i = 6) { return i + 4; };
    std::cout << "func1: " << func1() << '\n';
    // 모든 호출 가능 객체와 마찬가지로, 클로저는 std::function에 캡처될 수 있습니다
    // (이것은 불필요한 오버헤드를 발생시킬 수 있습니다)
    std::function<int(int)> func2 = [](int i) { return i + 4; };
    std::cout << "func2: " << func2(6) << '\n';
    constexpr int fib_max {8};
    std::cout << "재귀적 람다 호출을 에뮬레이션:\n피보나치 수열: ";
    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 << "람다 재귀에 대한 대체 접근법:\n피보나치 수열: ";
    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의 람다 재귀 접근법:\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
`재귀적 람다` 호출 에뮬레이션:
피보나치 수: 0, 1, 1, 2, 3, 5, 8, 13
람다 재귀에 대한 대체 접근법:
피보나치 수: 0, 1, 1, 2, 3, 5, 8, 13

결함 보고서

다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.

DR 적용 대상 게시된 동작 올바른 동작
CWG 974 C++11 람다 표현식의 매개변수 목록에서 기본 인수가
허용되지 않았음
허용됨
CWG 1048
( N3638 )
C++11 반환 타입 추론이 단일 return 문만 포함하는
람다 본문에 대해서만 가능했음
반환 타입 추론이
개선됨
CWG 1249 C++11 둘러싸는 비-변경 가능 람다의 캡처된 멤버가
const 로 간주되는지 여부가 명확하지 않았음
const 로 간주됨
CWG 1557 C++11 클로저 타입의 변환 함수가 반환하는 함수 타입의
언어 연결이 지정되지 않았음
C++ 언어 연결을
가짐
CWG 1607 C++11 람다 표현식이 함수 및 함수 템플릿 시그니처에
나타날 수 있었음
허용되지 않음
CWG 1612 C++11 익명 유니온의 멤버를 캡처할 수 있었음 허용되지 않음
CWG 1722 C++11 캡처 없는 람다의 변환 함수에 대해 명시되지 않은
예외 사양을 가짐
변환 함수가
noexcept임
CWG 1772 C++11 람다 본문 내 __func__ 의 의미가 명확하지 않았음 클로저 클래스의
operator()를 참조함
CWG 1780 C++14 제네릭 람다의 클로저 타입 멤버를 명시적으로
인스턴스화하거나 특수화할 수 있는지 여부가 불명확했음
둘 다 허용되지 않음
CWG 1891 C++11 클로저가 삭제된 기본 생성자와 암시적 복사/이동
생성자를 가짐
기본 생성자 없음과 기본화된
복사/이동 생성자
CWG 1937 C++11 변환 함수의 결과를 호출하는 효과에 대해,
어떤 객체에서 operator ( ) 를 호출하는 것이
동일한 효과를 가지는지 지정되지 않았음
클로저 타입의 기본 생성된
인스턴스에서
CWG 1973 C++11 클로저 타입의 operator ( ) 매개변수 목록이
trailing 에 주어진 매개변수 목록을 참조할 수 있었음
params
참조 가능
CWG 2011 C++11 참조로 캡처된 참조에 대해, 캡처 식별자가
어떤 엔티티를 참조하는지 지정되지 않았음
원래 참조된 엔티티를
참조함
CWG 2095 C++11 함수에 대한 우측값 참조를 복사로 캡처하는
동작이 명확하지 않았음
명확해짐
CWG 2211 C++11 캡처가 매개변수와 동일한 이름을 가질 경우
동작이 지정되지 않았음
이 경우 프로그램이
올바르지 않음
CWG 2358 C++14 기본 인수에 나타나는 람다 표현식은 모든 캡처가
기본 인수에 나타날 수 있는 표현식으로 초기화되더라도
캡처 없어야 했음
캡처를 가진 이러한 람다
표현식 허용
CWG 2509 C++17 각 지정자가 지정자 시퀀스에서 여러 번
나타날 수 있었음
각 지정자는 지정자 시퀀스에서
최대 한 번만 나타날 수
있음
CWG 2561 C++23 명시적 객체 매개변수를 가진 람다가 원하지 않는
함수 포인터 타입으로의 변환 함수를 가질 수 있었음
이러한 변환 함수를
가지지 않음
CWG 2881 C++23 명시적 매개변수를 가진 operator ( ) 가 상속이
public이 아니거나 모호할 때 파생 클래스에 대해 인스턴스화될 수 있었음
올바르지 않게 됨
P0588R1 C++11 암시적 람다 캡처 규칙이 odr-use를 감지했음 감지가 단순화됨

참고 항목

auto 지정자 (C++11) 표현식에서 추론된 타입을 지정함
(C++11)
복사 생성 가능한 모든 호출 가능 객체의 복사 가능 래퍼
(클래스 템플릿)
주어진 호출 시그니처에서 한정자를 지원하는 모든 호출 가능 객체의 이동 전용 래퍼
(클래스 템플릿)

외부 링크

중첩 함수 - 다른 ( 외부 ) 함수 내에서 정의된 함수.