Lambda

C++11부터 Lambda 표현식이 추가되었다.
단순히 보면 Lambda 표현식은 Functor의 약어이다.
[](double x){ return sin(x)+cos(x); }
람다 표현식은 펑터를 정의할 뿐만 아니라, 즉시 개체를 생성한다.
(함수를 인수로 즉시 전달 가능)
fin_diff([](double x){ return sin(x)+cos(x); }, 1., 0.001);
람다 표현식을 재사용하기 위해서 변수에 저장할 수도 있다.
auto sc_l=[](double x){ return sin(x)+cos(x); };
람다 표현식에서 return 타입을 선언하지 않는 경우, 컴파일러에서 리턴타입을 추론한다.
추론할 수 없거나 명시적으로 선언하는 방식을 선언할 경우, 후행 인수로 제공할 수 있다.
[](double x)->double {return sin(x)+cos(x); }; //
람다 표현식을 Functor에서 사용하였던 derivative 코드에서 펑터대신 사용가능
auto d7_psc_l=make_nth_derivative<7>([](double x){ return sin(2.5*x)+cos(x); }, 0.0001);
캡처
lambda-표현식은 자체 매개변수 혹은 이전에 캡처된(Captured) 매개변수만 사용할 수 있다.
double phi=2.5;
auto sin_phi=[](double x){ return sin(phi*x); }; // error
위 코드와 같이 람다 표현식은 같은 스코프에서도 변수나 상수에 접근 할 수 없다.
    값에 의한 캡처
변수를 사용하고 싶다면, 해당 변수를 캡처해 주어야 한다.
double phi=2.5, xi=0.2;
auto sc=[phi, xi](doube x){ return sin(phi*x)+cos(x)*xi; };
값으로 캡처한 람다는 매개변수를 복사해서 사용하지만, 매개변수로 전달된 함수와 달리 값을 수정할 수는 없다.

람다 함수는 내부적으로 아래와 같이 펑터 클래스로 정의된다.
struct lamda_f{
lamda_f(double phi, double xi): phi(phi), xi(xi) {}
double operator()(double x) const{
return sin(phi*x)+cos(x)*xi;
}
const double phi, xi;
};
결과적으로 캡처된 이후, 변수를 수정하더라도, 람다에는 영향을 미치지 않는다.
double phi=2.5, xi=0.2;
auto px=[phi, xi](double x){ retunr sin(phi*x)+cos(x)*xi; };
phi=3.5; xi=1.2;
a=fin_diff(px, 1., 0.001); // phi=2.5 xi=0.2 .
캡처될 값을 수정하기 위해서는 람다를 mutable로 한정해 주어야 한다.
// auto l_inc=[phi](double x){ phi+=0.6; return phi; }; // error
auto l_mut=[phi](double x) mutable { phi+=0.6; return phi; };
mutable로 한정해준 람다는 아래와 같이 operator()를 const 한정하지 않는다.
struct l_mut_f{
double operator()(double x); // const
// ...
double phi, xi; // const
};
    레퍼런스의 의한 캡처
변수를 레퍼런스로 캡처할 수 있다.
double phi=2.5, xi=0.2;
auto pxr=[&phi, &xi](double x){ return sin(phi*x)+cos(x)*xi; };
phi=3.5; xi=1.2;
a=fin_diff(pxr, 1., 0.001); // phi=3.5 xi=1.2 .
내부적으로 펑터 클래스에서도 변수가 레퍼런스로 선언된다.
struct lambda_ref_type{
lambda_ref_type(double& phi, double& xi): phi(phi), xi(xi) {}
double operator()(double x) const{
return sin(phi*x)+cos(x)*xi;
}
double& phi;
double& xi;
};
레퍼런스 문법은 참조된 값을 수정할 수 있다.
Matrix
template <typename Matrix>
typename Matrix::value_type frobenius_norm(const Matrix& A){
using std::abs; using std::sqrt;
using value_type=typename Matrix::value_type;
value_type ss=0;
on_each_nonzero(A, [&ss](value_type x){ ss+=abs(x)*abs(x); });
// return return void
return sqrt(ss);
}
캡처 기능
- [=]: 복사를 통해 모든 변수를 캡처.
- [&]: 레퍼런스를 통해 모든 변수를 캡처
- [=, &a, &b, &c]: 복사를 통해 모든 변수를 캡처하되 a, b, c는 레퍼런스를 통해 캡처
- [&, a, b, c]: 레퍼런스를 통해 모든 변수를 캡처하되, a, b, c는 복사를 통해 캡처
모든 변수를 캡처하는 기능은, 부실 레퍼런스의 위험을 증가시키고
정적 변수나 멤버 변수를 무시하기 때문에 사용하지 않는 것이 좋다.
일반화된 캡처(Init Capture)
일반화된 캡처는 변수를 클로저로 옮기고, 컴텍스트 변수나 표현식에 새로운 이름을 부여할 수 있다.

make_unique이 헬베르트 행렬을 unique_ptr로 반환하는 함수인 경우,
auto F=make_unique<Mat>(Mat{ {1., 0.5}, {0.5, 1./3} });
// F data unique_ptr
포인터로 가르키는 레퍼런스를 캡처할 수 있지만, 클로저가 포인터보다 오래 살 경우,
스테일(Stale) 레퍼런스가 될 수 있다. 또한 unique_ptr은 복사가 불가능 하다.
(행렬이 클로저만큼 살아있는지 확인하기 위해서 데이터를 클로저가 소유한 unique_ptr로 옮긴다.)
auto apply_hilbert=[F=move(F)](const Vec& x){ return Vec(*F*x); };
일반화된 캡처를 이용하면, 기본 변수에 새로운 이름을 추가하고, 표현식을 계산하고 계산 결과를
이름과 연결한다.
int x=4;
auto y=[&r=x, x=x+1](){
r+=2;
return x+2;
}();
// int (nullary)
위의 코드에서 r은 컨텍스트의 x를 가르키는 레퍼런스이며,
지역 값 x는 외부에 있는 x+1로 초기화 한다.

일반화된 캡처는 var=expr 형식이다
(단, var과 expr는 다른 스코프에서 정의된다. 양쪽에 같은 이름을 써도 다른 스코프의 변수를 의미)
int x=4;
auto y=[&y=x, x=r+1](){ ... }; // error: () r
제네릭 람다(Generic Lambda)
C++11에서는 명시적으로 리턴타입을 선어해 주어야 했다.(후행 선언)
C++14부터는 이러한 제한이 해제되었다.
template <typename C>
void reverse_sort(C& c){
sort(begin(c), end(c), [](auto x, auto y){ return x>y; };);
}
인수는 함수 템플릿(template-typename) 표기법으로 선언하지 않고,
auto 키워드를 사용해서 선언한다.

제네릭 람다를 이용한 frobenius_norm 단순화
template <typename Matrix>
inline auto frobenius_norm(const Matrix& A){
using std::abs; using std::sqrt;
decltype(abs(A[0][0])) ss=0;
on_each_nonzero(A, [&ss](auto x){ss+=abs(x)*abs(x)});
return sqrt(ss);
}