수색…


통사론

  • [ default-capture , capture-list ] ( argument-list ) 변경 가능한 throw-specification 속성 -> return-type { lambda-body } // 람다 지정자와 속성의 순서.
  • [ capture-list ] ( 인수 목록 ) { lambda-body } // 일반적인 λ 정의.
  • [=] ( argument-list ) { lambda-body } // 필요한 모든 지역 변수를 값으로 포착합니다.
  • [&] ( argument-list ) { lambda-body } // 참조로 필요한 모든 지역 변수를 캡처합니다.
  • [ capture-list ] { lambda-body } // 인수리스트와 지정자는 생략 가능합니다.

매개 변수

매개 변수 세부
기본 캡처 나열되지 않은 모든 변수를 캡처하는 방법을 지정합니다. = (값으로 캡처) 또는 & (참조로 캡처) 일 수 있습니다. 생략되면 목록에없는 변수는 람다 본문 에서 액세스 할 수 없습니다. 기본 캡처캡처 목록 앞에 와야합니다.
캡처 목록 로컬 변수가 람다 본문 내에서 액세스 가능하게되는 방법을 지정합니다. 접두사가없는 변수는 값으로 캡처됩니다. & 가 붙은 변수는 참조로 캡처됩니다. 클래스 메서드 내 this 메서드를 사용하면 모든 멤버를 참조로 액세스 할 수 있습니다. 목록에 기본 캡처 가 없으면 나열되지 않은 변수에 액세스 할 수 없습니다.
인수 목록 람다 함수의 인수를 지정합니다.
변하기 쉬운 (선택 사항) 일반적으로 값으로 캡처 한 변수는 const 입니다. mutable 을 지정하면 (자), 비 const가됩니다. 이러한 변수에 대한 변경 사항은 호출간에 유지됩니다.
던지기 명세 (선택 사항) 람다 함수의 예외 발생 동작을 지정합니다. 예 : noexcept 또는 throw(std::exception) .
속성들 (선택 사항) 람다 함수의 모든 속성. 예를 들어, lambda-body가 항상 예외를 throw하면 [[noreturn]] 사용할 수 있습니다.
-> 리턴 타입 (선택 사항) 람다 함수의 반환 유형을 지정합니다. 반환 유형을 컴파일러가 결정할 수없는 경우 필요합니다.
람다 람다 함수의 구현을 포함하는 코드 블록.

비고

C ++ 17 (현재의 초안)은 constexpr 람다 (기본적으로 컴파일시 평가할 수있는 람다)를 도입합니다. lambda는 constexpr 요구 사항을 만족하면 자동으로 constexpr 이지만 constexpr 키워드를 사용하여 지정할 수도 있습니다.

//Explicitly define this lambdas as constexpr
[]() constexpr {
    //Do stuff
}

람다 표현식이란 무엇입니까?

람다 표현식 은 간단한 함수 객체를 만드는 간결한 방법을 제공합니다. 람다 식은 결과 객체가 함수 객체처럼 작동하는 클로저 객체 라고하는 prvalue입니다.

'람다 식'이라는 이름은 람다 (Lambda) 미적분학 에서 비롯된 것으로, 1922 년 알론조 교회 (Alonzo Church)가 논리와 계산 능력에 관한 문제를 조사하기 위해 발명 한 수학적 형식주의입니다. 람다 미적분은 함수형 프로그래밍 언어 인 LISP 의 기초를 형성했습니다. 람다 미적분과 LISP와 비교할 때, C ++ 람다 식은 이름이없는 속성을 공유하고 주변 컨텍스트에서 변수를 캡처하지만 기능을 수행하고 함수를 반환 할 수있는 기능이 부족합니다.

람다 표현식은 종종 호출 가능한 객체를 취하는 함수의 인수로 사용됩니다. 이는 명명 된 함수를 만드는 것보다 간단 할 수 있습니다.이 함수는 인수로 전달 될 때만 사용됩니다. 이러한 경우 람다식이 일반적으로 선호되기 때문에 함수 개체를 인라인으로 정의 할 수 있습니다.

람다는 일반적으로 세 부분으로 구성됩니다. 캡처 목록 [] , 선택적 매개 변수 목록 () 및 본문 {} 모두가 비어있을 수 있습니다.

[](){}                // An empty lambda, which does and returns nothing

캡처 목록

[]캡처 목록 입니다. 기본적으로 둘러싸는 범위의 변수에는 람다가 액세스 할 수 없습니다. 변수를 포착 하면 복사본 이나 참조 람다 내부에서 변수에 액세스 할 수 있습니다. 캡처 된 변수는 람다의 일부가됩니다. 함수 인수와 달리 람다를 호출 할 때 전달할 필요는 없습니다.

int a = 0;                       // Define an integer variable
auto f = []()   { return a*9; }; // Error: 'a' cannot be accessed
auto f = [a]()  { return a*9; }; // OK, 'a' is "captured" by value
auto f = [&a]() { return a++; }; // OK, 'a' is "captured" by reference
                                 //      Note: It is the responsibility of the programmer
                                 //      to ensure that a is not destroyed before the
                                 //      lambda is called.
auto b = f();                    // Call the lambda function. a is taken from the capture list and not passed here.

매개 변수 목록

()매개 변수 목록 이며 일반 함수와 거의 같습니다. lambda가 인수를 취하지 않으면이 괄호를 생략 할 수 있습니다 (람다 mutable 을 선언해야하는 경우 제외). 이 두 람다는 같습니다.

auto call_foo  = [x](){ x.foo(); };
auto call_foo2 = [x]{ x.foo(); };
C ++ 14

매개 변수 목록은 실제 유형 대신 자리 표시 자 유형 auto 를 사용할 수 있습니다. 이렇게하면이 인수는 함수 템플리트의 템플리트 매개 변수처럼 작동합니다. 다음 람다는 제네릭 코드에서 벡터를 정렬 할 때 동등합니다.

auto sort_cpp11 = [](std::vector<T>::const_reference lhs, std::vector<T>::const_reference rhs) { return lhs < rhs; }; 
auto sort_cpp14 = [](const auto &lhs, const auto &rhs) { return lhs < rhs; }; 

기능 본체

{}본문 이며 일반 함수와 동일합니다.


람다 호출하기

람다 식의 결과 객체는 closure 이며 다른 함수 객체와 마찬가지로 operator() 사용하여 호출 할 수 있습니다.

int multiplier = 5;
auto timesFive = [multiplier](int a) { return a * multiplier; }; 
std::out << timesFive(2); // Prints 10

multiplier = 15;
std::out << timesFive(2); // Still prints 2*5 == 10

반환 유형

기본적으로 람다 식의 반환 형식이 추론됩니다.

[](){ return true; };

이 경우 리턴 유형은 bool 입니다.

다음 구문을 사용하여 반환 유형을 수동으로 지정할 수도 있습니다.

[]() -> bool { return true; };

가변 람다

람다에서 값에 의해 캡쳐 된 객체는 기본적으로 변경되지 않습니다. 이는 생성 된 클로저 객체의 operator() 가 기본적으로 const 이기 때문입니다.

auto func = [c = 0](){++c; std::cout << c;};  // fails to compile because ++c
                                              // tries to mutate the state of
                                              // the lambda.

수정 mutable 키워드는 mutable 키워드를 사용하면 근접한 객체의 operator() const 아닌 값으로 만들 수 있습니다.

auto func = [c = 0]() mutable {++c; std::cout << c;};

반환 유형과 함께 사용하는 경우 mutable 될 수 있습니다.

auto func = [c = 0]() mutable -> int {++c; std::cout << c; return c;};

람다의 유용성을 보여주는 예

C ++ 11 이전 :

C ++ 11
// Generic functor used for comparison
struct islessthan
{
    islessthan(int threshold) : _threshold(threshold) {}

    bool operator()(int value) const
    {
        return value < _threshold;
    }
private:
    int _threshold;
};

// Declare a vector
const int arr[] = { 1, 2, 3, 4, 5 };
std::vector<int> vec(arr, arr+5);

// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
std::vector<int>::iterator it = std::find_if(vec.begin(), vec.end(), islessthan(threshold));

C ++ 11 이후 :

C ++ 11
// Declare a vector
std::vector<int> vec{ 1, 2, 3, 4, 5 };

// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
auto it = std::find_if(vec.begin(), vec.end(), [threshold](int value) { return value < threshold; });

반환 유형 지정

단일 return 문이있는 람다 또는 여러 개의식이 동일한 return 문이 있으면 컴파일러에서 반환 유형을 추론 할 수 있습니다.

// Returns bool, because "value > 10" is a comparison which yields a Boolean result
auto l = [](int value) {
    return value > 10;
}

다른 유형의 여러 return 문이있는 lambdas의 경우 컴파일러에서 반환 유형을 추론 할 수 없습니다.

// error: return types must match if lambda has unspecified return type
auto l = [](int value) {
    if (value < 10) {
        return 1;
    } else {
        return 1.5;
    }
};

이 경우 반환 유형을 명시 적으로 지정해야합니다.

// The return type is specified explicitly as 'double'
auto l = [](int value) -> double {
    if (value < 10) {
        return 1;
    } else {
        return 1.5;
    }
};

이 규칙은 auto 유형 공제 규칙과 일치합니다. 명시 적으로 지정된 반환 형식이없는 Lambdas는 참조를 반환하지 않으므로 참조 형식을 원할 경우 명시 적으로 지정해야합니다.

auto copy = [](X& x) { return x; };       // 'copy' returns an X, so copies its input
auto ref  = [](X& x) -> X& { return x; }; // 'ref' returns an X&, no copy

값으로 캡처

캡처 목록에 변수의 이름을 지정하면 람다가 값으로 변수 이름을 캡처합니다. 이것은 람다를 위해 생성 된 클로저 타입이 변수의 복사본을 저장한다는 것을 의미합니다. 또한 변수의 유형을 복사 가능으로 만들어야합니다 .

int a = 0;

[a]() {
    return a;   // Ok, 'a' is captured by value
};
C ++ 14
auto p = std::unique_ptr<T>(...);

[p]() {         // Compile error; `unique_ptr` is not copy-constructible
    return p->createWidget(); 
};

C ++ 14부터는 그 자리에서 변수를 초기화 할 수 있습니다. 이렇게하면 이동 유형 만 람다에서 캡처 할 수 있습니다.

C ++ 14
auto p = std::make_unique<T>(...);

[p = std::move(p)]() {
    return p->createWidget(); 
};

람다는 이름으로 변수를 제공 할 때 값으로 변수를 캡처하지만 기본적으로 람다 본문 내에서 이러한 변수를 수정할 수는 없습니다. 이것은 클로저 타입이 람다 본문을 operator() const 의 선언에 operator() const 입니다.

const 는 클로저 유형의 멤버 변수에 액세스하고 클로저의 멤버 인 캡처 된 변수에 적용됩니다 (반대의 모든 모양).

int a = 0;

[a]() {
    a = 2;      // Illegal, 'a' is accessed via `const`

    decltype(a) a1 = 1; 
    a1 = 2; // valid: variable 'a1' is not const
};

const 를 제거하려면 람다에 mutable 키워드를 지정해야합니다.

int a = 0;

[a]() mutable {
    a = 2;      // OK, 'a' can be modified
    return a;
};

a 는 값에 의해 캡쳐 되었기 때문에 람다를 호출하여 수행 한 수정은 a 영향을 미치지 않습니다. 값 a 그것이 생성되었을 때, λ로 복사하고, 그래서 람다의 사본 a 외부로부터 분리 변수. a

int a = 5 ; 
auto plus5Val = [a] (void) { return a + 5 ; } ; 
auto plus5Ref = [&a] (void) {return a + 5 ; } ; 

a = 7 ; 
std::cout << a << ", value " << plus5Val() << ", reference " << plus5Ref() ;
// The result will be "7, value 10, reference 12"

일반 캡처

C ++ 14

Lambdas는 변수가 아닌 표현식을 캡처 할 수 있습니다. 이렇게하면 람다가 이동 전용 유형을 저장할 수 있습니다.

auto p = std::make_unique<T>(...);

auto lamb = [p = std::move(p)]() //Overrides capture-by-value of `p`.
{
  p->SomeFunc();
};

그러면 외부 p 변수가 p 라고도하는 람다 캡처 변수로 이동합니다. lamb 이제 make_unique 에 의해 할당 된 메모리를 소유합니다. 클로저에는 복사 할 수없는 유형이 포함되어 있으므로 lamb 이 복사 할 수 없다는 의미입니다. 그러나 그것은 옮길 수 있습니다 :

auto lamb_copy = lamb; //Illegal
auto lamb_move = std::move(lamb); //legal.

이제 lamb_move 가 메모리를 소유합니다.


std::function<> 에서는 저장된 값을 복사 할 수 있어야합니다. 자신 만의 이동 std::function 필요로하는 std::function 를 작성하거나 람다를 shared_ptr 래퍼에 넣을 수 있습니다.

auto shared_lambda = [](auto&& f){
  return [spf = std::make_shared<std::decay_t<decltype(f)>>(decltype(f)(f))]
  (auto&&...args)->decltype(auto) {
    return (*spf)(decltype(args)(args)...);
  };
};
auto lamb_shared = shared_lambda(std::move(lamb_move));

우리의 이동 전용 람다 (lambda)를 가져 와서 상태를 공유 포인터로 채운 다음 복사 할 수 있는 람다를 반환 한 다음 std::function 또는 유사한 방식으로 저장합니다.


일반화 된 캡처는 변수 유형에 auto 유형 공제를 사용합니다. 기본적으로 이러한 캡처를 값으로 선언하지만 참조도 될 수 있습니다.

int a = 0;

auto lamb = [&v = a](int add) //Note that `a` and `v` have different names
{
  v += add; //Modifies `a`
};

lamb(20); //`a` becomes 20.

일반화 캡처는 외부 변수를 전혀 캡처 할 필요가 없습니다. 임의의 표현식을 포착 할 수 있습니다.

auto lamb = [p = std::make_unique<T>(...)]()
{
    p->SomeFunc();
}

이것은 람다의 외부에 선언 할 필요없이 람다가 가질 수 있고 수정할 수있는 임의의 값을주는 데 유용합니다. 물론, 람다가 작업을 완료 한 후에 변수에 액세스하지 않으려는 경우에만 유용합니다.

참조로 캡처

로컬 변수의 이름 앞에 & 를 붙이면 변수는 참조로 캡처됩니다. 개념적으로, 이것은 람다의 클로저 타입이 람다 범위 외부에서 해당 변수에 대한 참조로 초기화되는 참조 변수를 갖게됨을 의미합니다. 람다 본문에서 변수를 사용하면 원래 변수를 참조하게됩니다.

// Declare variable 'a'
int a = 0;

// Declare a lambda which captures 'a' by reference
auto set = [&a]() {
    a = 1;
};

set();
assert(a == 1);

키워드 mutable 있기 때문에, 필요하지 않은 자체가 아닌 a const .

물론, 참조에 의한 캡쳐는 람다 캡쳐 한 변수의 범위를 벗어나지 않아야 함을 의미합니다. 따라서 함수를 호출하는 함수를 호출 할 수는 있지만 참조 범위를 벗어난 람다를 저장 하는 함수를 호출하면 안됩니다. 그리고 람다를 돌려 보내면 안됩니다.

기본 캡처

기본적으로 캡처 목록에 명시 적으로 지정되지 않은 로컬 변수는 람다 본문에서 액세스 할 수 없습니다. 그러나 람다 본문에서 명명 된 변수를 암시 적으로 캡처 할 수 있습니다.

int a = 1;
int b = 2;

// Default capture by value
[=]() { return a + b; }; // OK; a and b are captured by value

// Default capture by reference
[&]() { return a + b; }; // OK; a and b are captured by reference

명시 적 캡처는 암시 적 기본 캡처와 함께 수행 할 수 있습니다. 명시 적 캡처 정의가 기본 캡처보다 우선합니다.

int a = 0;
int b = 1;

[=, &b]() {
    a = 2; // Illegal; 'a' is capture by value, and lambda is not 'mutable'
    b = 2; // OK; 'b' is captured by reference
};

일반 람다

c ++ 14

람다 함수는 임의의 타입의 인자를 취할 수있다. 이렇게하면 람다가 좀 더 일반화 될 수 있습니다.

auto twice = [](auto x){ return x+x; };

int i = twice(2); // i == 4
std::string s = twice("hello"); // s == "hellohello"

이것은 클로저 타입의 operator() 가 템플릿 함수에 오버로드되도록함으로써 C ++에서 구현됩니다. 다음 유형은 위의 람다 클로저와 동등한 동작을합니다.

struct _unique_lambda_type
{
  template<typename T>
  auto operator() (T x) const {return x + x;}
};

일반 람다의 모든 매개 변수가 generic 일 필요는 없습니다.

[](auto x, int y) {return x + y;}

여기서 x 는 첫 번째 함수 인수를 기반으로 추론되지만 y 는 항상 int 입니다.

일반 람다는 auto& 대한 일반적인 규칙을 사용하여 참조로 인수를 취할 수 있습니다. 일반적인 매개 변수로 간주되면 auto&& , 이것은이다 전달 참조 인수에 통과 아닌에 를 rvalue 참조가 :

auto lamb1 = [](int &&x) {return x + 5;};
auto lamb2 = [](auto &&x) {return x + 5;};
int x = 10;
lamb1(x); // Illegal; must use `std::move(x)` for `int&&` parameters.
lamb2(x); // Legal; the type of `x` is deduced as `int&`.

람다 함수는 가변적 일 수 있으며 인수를 완벽하게 전달할 수 있습니다.

auto lam = [](auto&&... args){return f(std::forward<decltype(args)>(args)...);};

또는:

auto lam = [](auto&&... args){return f(decltype(args)(args)...);};

auto&& 유형의 변수만으로 "제대로"작동합니다.

일반적인 lambdas를 사용하는 강력한 이유는 구문을 방문하기위한 것입니다.

boost::variant<int, double> value;
apply_visitor(value, [&](auto&& e){
  std::cout << e;
});

여기서 우리는 다형성 방식으로 방문합니다. 다른 문맥에서 우리가 전달하는 타입의 이름은 흥미롭지 않습니다 :

mutex_wrapped<std::ostream&> os = std::cout;
os.write([&](auto&& os){
  os << "hello world\n";
});

std::ostream& 의 유형을 반복하면 여기에 노이즈가 있습니다. 변수를 사용할 때마다 변수의 유형을 언급하는 것과 같을 것입니다. 여기서는 방문자를 만들지 만 다형성이없는 방문자는 만들지 않습니다. autofor(:) 루프에서 auto 를 사용할 때와 같은 이유로 사용됩니다.

함수 포인터로 변환

람다의 캡쳐리스트가 비어 있다면, 람다는 동일한 인수를 취하고 동일한 리턴 타입을 반환하는 함수 포인터로 암시 적 변환을합니다 :

auto sorter = [](int lhs, int rhs) -> bool {return lhs < rhs;};

using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter; // implicit conversion

이러한 변환은 단항 더하기 연산자를 사용하여 적용 할 수도 있습니다.

func_ptr sorter_func2 = +sorter; // enforce implicit conversion

이 함수 포인터를 호출하면 람다에서 operator() 를 호출하는 것과 똑같이 동작합니다. 이 함수 포인터는 소스 람다 클로저의 존재에 의존하지 않습니다. 따라서 람다 폐쇄보다 오래 지속될 수 있습니다.

이 기능은 주로 C ++ 함수 객체 대신 함수 포인터를 처리하는 API가있는 람다 (lambda)를 사용하는 데 유용합니다.

C ++ 14

함수 포인터로의 변환은 빈 캡처 목록이있는 일반 람다에서도 가능합니다. 필요하다면 템플릿 인수 공제가 올바른 전문 분야를 선택하는 데 사용됩니다.

auto sorter = [](auto lhs, auto rhs) { return lhs < rhs; };
using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter;  // deduces int, int
// note however that the following is ambiguous
// func_ptr sorter_func2 = +sorter;

클래스 람다와 이것의 캡쳐

클래스의 멤버 함수에서 평가 된 람다 식은 암시 적으로 그 클래스의 친구입니다.

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    // definition of a member function
    void Test()
    {
        auto lamb = [](Foo &foo, int val)
        {
            // modification of a private member variable
            foo.i = val;
        };
        
        // lamb is allowed to access a private member, because it is a friend of Foo
        lamb(*this, 30);
    }
};

그러한 람다는 그 클래스의 친구 일뿐만 아니라 클래스 내에 선언 된 클래스와 동일한 액세스 권한을가집니다.

Lambdas는 외부 함수가 호출 된 객체 인스턴스를 나타내는 this 포인터를 캡처 할 수 있습니다. 캡처 목록에 다음을 추가 this 됩니다.

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    void Test()
    {
        // capture the this pointer by value
        auto lamb = [this](int val)
        {
            i = val;
        };
        
        lamb(30);
    }
};

this 캡쳐되면, 람다는 마치 그것을 포함하는 클래스에있는 것처럼 그것의 포함하는 클래스의 멤버 이름을 사용할 수 있습니다. 따라서 암묵적인 this-> 가 그러한 멤버들에게 적용됩니다.

this 값은 값으로 캡처되지만 형식 값은 캡처하지 않습니다. 이것은 값에 의해 포착 this 포인터이다. 따라서, 람다는 소유하지 않은 this . 람다가 그것을 생성 한 객체의 수명 동안 살아 있다면, 람다는 무효가 될 수 있습니다.

이것은 또한 람다가 변경 mutable 하다고 선언되지 않고 this 수정할 수 있음을 의미합니다. 객체가 가리키고있는 것이 아니라 const 인 포인터입니다. 즉, 외부 멤버 함수 자체가 const 함수가 아니면.

또한, 기본 캡처 절을 모두 알고 있어야 [=][&] , 또한 캡처 this 암시. 그리고 그들은 포인터의 값으로 그것을 캡쳐합니다. 실제로, 지정하면 오류가 this 기본이 주어 졌을 때 캡처 목록에.

C ++ 17

Lambda는 람다가 생성 될 때 생성 된 this 객체의 복사본을 캡처 할 수 있습니다. 이것은 *this 를 캡처 목록에 추가하여 수행됩니다.

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    void Test()
    {
        // capture a copy of the object given by the this pointer
        auto lamb = [*this](int val) mutable
        {
            i = val;
        };
        
        lamb(30); // does not change this->i
    }
};

펑터를 사용하여 람다 함수를 C ++ 03에 이식

C ++의 람다 함수는 펑터 작성에 매우 간결한 구문을 제공하는 구문 설탕입니다. 이와 같이, 람다 함수를 펑터 (functor)로 변환함으로써 C ++ 03에서 동일한 기능을 얻을 수 있습니다 (훨씬 더 장황 함에도 불구하고).

// Some dummy types:
struct T1 {int dummy;};
struct T2 {int dummy;};
struct R {int dummy;};

// Code using a lambda function (requires C++11)
R use_lambda(T1 val, T2 ref) {
  // Use auto because the type of the lambda is unknown.
  auto lambda = [val, &ref](int arg1, int arg2) -> R {
    /* lambda-body */
    return R();
  };
  return lambda(12, 27);
}

// The functor class (valid C++03)
// Similar to what the compiler generates for the lambda function.
class Functor {
  // Capture list.
  T1 val;
  T2& ref;

public:
  // Constructor
  inline Functor(T1 val, T2& ref) : val(val), ref(ref) {}

  // Functor body
  R operator()(int arg1, int arg2) const {
    /* lambda-body */
    return R();
  }
};

// Equivalent to use_lambda, but uses a functor (valid C++03).
R use_functor(T1 val, T2 ref) {
  Functor functor(val, ref);
  return functor(12, 27);
}

// Make this a self-contained example.
int main() {
  T1 t1;
  T2 t2;
  use_functor(t1,t2);
  use_lambda(t1,t2);
  return 0;
}

람다 함수가 mutable 하다면 펑터의 call-operator를 const가 아닌 것으로 만듭니다. 즉 :

R operator()(int arg1, int arg2) /*non-const*/ {
  /* lambda-body */
  return R();
}

재귀 람다

유클리드의 gcd() 를 람다 (lambda gcd() 로 쓰고 싶다고합시다. 함수로서 다음과 같습니다.

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a%b);
}

그러나 람다는 재귀 적이 될 수 없으며, 스스로를 호출 할 방법이 없습니다. 람다는 이름을 갖고 있지 않고, 사용 this 람다의 체내 것은 캡처 지칭 this (멤버 함수의 본체에서 생성 된 람다 가정은 그렇지 않으면 에러이다). 그러면이 문제를 어떻게 해결할 수 있을까요?

std::function

우리는 람다가 아직 구축되지 않은 std::function 대한 참조를 포착하도록 할 수 있습니다 :

std::function<int(int, int)> gcd = [&](int a, int b){
    return b == 0 ? a : gcd(b, a%b);
};

이 방법은 효과가 있지만 드물게 사용해야합니다. 그것은 천천히 (우리는 지금 직접 함수 호출 대신 타입 삭제를 사용하고 있습니다.), 허약합니다 ( gcd 복사하거나 gcd 가 되돌아 오는 것은 람다가 원래의 객체를 참조하기 때문에 중단 될 것입니다). 그리고 그것은 일반 람다에서 작동하지 않을 것입니다.

두 개의 스마트 포인터 사용 :

auto gcd_self = std::make_shared<std::unique_ptr< std::function<int(int, int)> >>();
*gcd_self = std::make_unique<std::function<int(int, int)>>(
  [gcd_self](int a, int b){
    return b == 0 ? a : (**gcd_self)(b, a%b);
  };
};

이렇게하면 오버 헤드가 많은 간접 참조가 추가되지만 복사 / 반환 될 수 있으며 모든 복사본은 상태를 공유합니다. 그것은 당신이 람다를 반환하게하고, 그렇지 않으면 위의 솔루션보다 덜 허약합니다.

Y-combinator 사용

짧은 유틸리티 구조체의 도움으로 다음과 같은 모든 문제를 해결할 수 있습니다.

template <class F>
struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template <class... Args>
    decltype(auto) operator()(Args&&... args) const {
        // we pass ourselves to f, then the arguments.
        // the lambda should take the first argument as `auto&& recurse` or similar.
        return f(*this, std::forward<Args>(args)...);
    }
};
// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
    return {std::forward<F>(f)};
}
// (Be aware that in C++17 we can do better than a `make_` function)

우리는 우리의 gcd 를 다음과 같이 구현할 수 있습니다 :

auto gcd = make_y_combinator(
  [](auto&& gcd, int a, int b){
    return b == 0 ? a : gcd(b, a%b);
  }
);

y_combinator 는 람다 미적분의 개념으로 정의 될 때까지 스스로 이름을 지정할 필요없이 재귀를 가질 수 있습니다. 이것은 정확히 람다가 가진 문제입니다.

"재귀"를 첫 번째 인수로 사용하는 람다를 만듭니다. 재귀를 원할 때, 재귀 할 인수를 전달합니다.

그런 다음 y_combinator 는 해당 함수를 인수와 함께 호출하지만 적절한 "재귀"개체 (즉, y_combinator 자체)를 첫 번째 인수로 사용하여 해당 함수를 호출하는 함수 개체를 반환합니다. y_combinator 라고하는 나머지 인수를 람다에게도 전달합니다.

간단히 말해서 :

auto foo = make_y_combinator( [&](auto&& recurse, some arguments) {
  // write body that processes some arguments
  // when you want to recurse, call recurse(some other arguments)
});

심각한 제한이나 심각한 오버 헤드없이 람다에서 재귀가 발생합니다.

인라인 매개 변수 팩 압축 풀기에 람다 사용

C ++ 14

매개 변수 팩을 푸는 것은 전통적으로 원하는 때마다 도우미 함수를 작성해야합니다.

이 장난감의 예 :

template<std::size_t...Is>
void print_indexes( std::index_sequence<Is...> ) {
  using discard=int[];
  (void)discard{0,((void)(
    std::cout << Is << '\n' // here Is is a compile-time constant.
  ),0)...};
}
template<std::size_t I>
void print_indexes_upto() {
  return print_indexes( std::make_index_sequence<I>{} );
}

print_indexes_upto 는 인덱스의 매개 변수 팩을 만들고 압축을 풀기를 원합니다. 이를 수행하기 위해서는 보조 기능을 호출해야합니다. 생성 한 매개 변수 팩의 압축을 풀 때마다 사용자 지정 도우미 함수를 만들어야합니다.

이것은 lambdas로 피할 수 있습니다.

매개 변수 팩을 다음과 같이 람다 호출 집합에 압축을 풀 수 있습니다.

template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f){
    using discard=int[];
    (void)discard{0,(void(
      f( index<Is> )
    ),0)...};
  };
}

template<std::size_t N>
auto index_over(index_t<N> = {}) {
  return index_over( std::make_index_sequence<N>{} );
}
C ++ 17

fold 표현식을 사용하면 index_over() 를 다음과 같이 단순화 할 수 있습니다.

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f){
    ((void)(f(index<Is>)), ...);
  };
}

이렇게하면 매개 변수 팩을 수동으로 언팩해야하는 것을 다른 코드의 두 번째 오버로드로 대체하여 매개 변수 팩을 "인라인"으로 언팩 할 수 있습니다.

template<class Tup, class F>
void for_each_tuple_element(Tup&& tup, F&& f) {
  using T = std::remove_reference_t<Tup>;
  using std::tuple_size;
  auto from_zero_to_N = index_over< tuple_size<T>{} >();

  from_zero_to_N(
    [&](auto i){
      using std::get;
      f( get<i>( std::forward<Tup>(tup) ) );
    }
  );
}

index_over 의해 람다에 전달 된 auto istd::integral_constant<std::size_t, ???> 입니다. std::size_t 대한 constexpr 변환은 this 상태에 의존하지 않으므로 위의 std::get<i> 전달할 때와 같이 컴파일 타임 상수로 사용할 수 있습니다.

맨 위의 장난감 예제로 돌아가려면 다음과 같이 다시 작성하십시오.

template<std::size_t I>
void print_indexes_upto() {
  index_over(index<I>)([](auto i){
    std::cout << i << '\n'; // here i is a compile-time constant
  });
}

훨씬 더 짧으며, 그것을 사용하는 코드에 로직을 유지합니다.

함께 할 라이브 예제 .



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow