수색…
통사론
- [ 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(); };
매개 변수 목록은 실제 유형 대신 자리 표시 자 유형 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 이전 :
// 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 이후 :
// 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
};
auto p = std::unique_ptr<T>(...);
[p]() { // Compile error; `unique_ptr` is not copy-constructible
return p->createWidget();
};
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"
일반 캡처
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
};
일반 람다
람다 함수는 임의의 타입의 인자를 취할 수있다. 이렇게하면 람다가 좀 더 일반화 될 수 있습니다.
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&
의 유형을 반복하면 여기에 노이즈가 있습니다. 변수를 사용할 때마다 변수의 유형을 언급하는 것과 같을 것입니다. 여기서는 방문자를 만들지 만 다형성이없는 방문자는 만들지 않습니다. auto
는 for(:)
루프에서 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)를 사용하는 데 유용합니다.
함수 포인터로의 변환은 빈 캡처 목록이있는 일반 람다에서도 가능합니다. 필요하다면 템플릿 인수 공제가 올바른 전문 분야를 선택하는 데 사용됩니다.
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
기본이 주어 졌을 때 캡처 목록에.
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)
});
심각한 제한이나 심각한 오버 헤드없이 람다에서 재귀가 발생합니다.
인라인 매개 변수 팩 압축 풀기에 람다 사용
매개 변수 팩을 푸는 것은 전통적으로 원하는 때마다 도우미 함수를 작성해야합니다.
이 장난감의 예 :
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>{} );
}
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 i
은 std::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
});
}
훨씬 더 짧으며, 그것을 사용하는 코드에 로직을 유지합니다.
함께 할 라이브 예제 .