C++11부터 제공
익명의 함수(객체)를 만드는 문법
lambda expression 기본 모양
[]: lambda Introducer
[captures]<tparams> requires (params)
specs requires { body }
lambda expression 활용
capture local variable
lambda expression 안에서 지역변수에 접근하려면 반드시 자역변수를 캡쳐해야함
lambda expression return type
int main()
{
auto f1 = [](int a, int b) -> int { return a + b; }; // 후위 반환 타입 표기
auto f2 = [](int a, int b){ return a + b; }; // 타입 추론
auto f3 = [](int a, int b){ if (a == 1) return a; return b; };
// auto f4 = [](int a, double b){ if (a == 1) return a; return b; }; // 타입 추론 불가
auto f4 = [](int a, double b) -> double { if (a == 1) return a; return b; };
}
Lambda Expression: Closure를 만드는 표현식
C++의 Lambda Expression: 범위 밖의 변수를 캡쳐할 수 있는 이름 없는 함수 객체
#include <algorithm>
int main()
{
std::vector<int> v = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10};
// std::sort( v.begin(), v.end(), [](int a, int b) { return a < b;} );
class CompilerGeneratedName
{
public:
inline auto operator()(int a, int b) const
{
return a < b;
}
// ......
};
std::sort(v.begin(), v.end(), CompilerGeneratedName{}); // 최종적으로 임시 객체가 만들어짐
}
람다 표현식 자리가 임시 객체로 바뀌는데 그 임시 객체를 Closure Object라 불림
Closure Object: lambda expression이 만드는 unnamed function object
int main()
{
auto f1 = [](int a, int b) { return a + b;}; // ok
// class CompilerGeneratedNameA{}; // 컴파일러에서 클래스를 만들고 괄호 연산자 재정의
// auto f1 = CompilerGeneratedNameA{}; // 임시 객체가 대입
// lvalue reference
// auto& f2 = [](int a, int b) { return a + b;}; // error
// forwarding reference
auto&& f3 = [](int a, int b) { return a + b;}; // ok
// const lvalue reference
const auto& f4 = [](int a, int b) { return a + b;}; // ok
}
int main()
{
int v1 = 10, v2 = 20;
auto f = [v1, v2](int a) { return a + v1 + v2;};
/*
class CompilerGeneratedName
{
int v1;
int v2;
public:
CompilerGeneratedName(int& v1, int& v2) : v1{v1}, v2{v2} {}
inline auto operator()(int a) const
{
return a + v1 + v2;
// ...
}
};
auto f = CompilerGeneratedName{v1, v2};
*/
std::cout << f(5) << std::endl; // 35
std::cout << sizeof(f) << std::endl; // 8
}
int main()
{
int v1 = 10, v2 = 20;
// auto f = [v1, v2](int a) { v1 = a; v2 = a;}; // 컴파일 에러
auto f = [v1, v2](int a) mutable { v1 = a; v2 = a;}; // mutable 람다 표현식
/*
class CompilerGeneratedName
{
int v1;
int v2;
public:
CompilerGeneratedName(int& v1, int& v2) : v1{v1}, v2{v2} {}
inline auto operator()(int a) // const 함수로 만들지 않음
{
v1 = a; // 지역 변수가 아닌 멤버 변수를 수정
v2 = a;
}
};
auto f = CompilerGeneratedName{v1, v2};
*/
f(3);
std::cout << v1 << std::endl; // 10
std::cout << v2 << std::endl; // 20
}
int main()
{
int v1 = 10, v2 = 20;
// auto f = [v1, v2](int a) { v1 = a; v2 = a;}; // error
// auto f = [v1, v2](int a) mutable { v1 = a; v2 = a;}; // 복사본 변경
auto f = [&v1, &v2](int a) { v1 = a; v2 = a;};
/*
class CompilerGeneratedName
{
int& v1;
int& v2;
public:
CompilerGeneratedName(int& v1, int& v2) : v1{v1}, v2{v2} {}
inline auto operator()(int a) const
{
v1 = a; // const 함수지만 v1이 가리키는 곳을 변경하는 것이 아닌
v2 = a; // v1이 가리키는 곳의 값을 변경하는 것
// 즉, v1 자체인 참조가 변경되는 것이 아니므로 에러 아님
}
};
auto f = CompilerGeneratedName{v1, v2};
*/
f(3);
std::cout << v1 << std::endl; // 3
std::cout << v2 << std::endl; // 3
}
int main()
{
int v1 = 10, v2 = 10;
auto f1 = [v1, &v2]() {}; // v1은 값으로, v2는 참조로 캡쳐
auto f2 = [=]() {}; // 모두 값으로 캡쳐
auto f3 = [&]() {}; // 모두 참조로 캡쳐
auto f4 = [=, &v1]() {}; // v1만 참조로 캡쳐
auto f5 = [&, v1]() {}; // v1만 값으로 캡쳐
// auto f6 = [&, &v1]() {}; // error
// x에 v1을 값으로 캡쳐, y에 v2를 참조로 캡쳐
auto f7 = [x = v1, &y = v2](int a) { y = a + x; };
std::string s = "hello";
auto f8 = [ msg = std::move(s) ]() {};
std::cout << s << std::endl; // ""
}
람다 표현식을 함수 포인터에 담을 수 있음
int main()
{
auto f1 = [](int a, int b) { return a + b;};
int(*f2)(int, int) = [](int a, int b) { return a + b;};
std::cout << f2(1, 2) << std::endl;
}
람다 표현식의 결과는 이름 없는 함수 객체(임시 객체)인데 어떻게 함수 포인터로 암시적 형변환 되는 건가
-> 컴파일러가 만드는 클래스에 함수 포인터로 변환될 수 있는 변환 연산자 함수를 제공
int main()
{
// int(*f)(int, int) = [](int a, int b) { return a + b;};
class CompilerGeneratedName
{
public:
// 멤버 함수 포인터가 일반 함수 포인터로 반환될 수 없음
inline int operator()(int a, int b) const // () 연산자 함수는 static 멤버 함수가 될 수 없음
{
return a + b;
}
static int _invoke(int a, int b) // static 함수는 const 멤버 함수가 될 수 없음
{
return a + b;
}
using FP = int(*)(int, int);
operator FP() const { return &CompilerGeneratedName::_invoke; }
};
int(*f)(int, int) = CompilerGeneratedName{};
// CompilerGeneratedName{}.operator int(*)(int, int);
std::cout << f(1,2) << std::endl; // 3
}
int main()
{
int v1 = 10;
int(*f1)(int, int) = [ ](int a, int b) { return a + b;}; // ok
// int(*f2)(int, int) = [v1](int a, int b) { return a + b + v1;}; // error
class CompilerGeneratedName
{
int v1;
public:
CompilerGeneratedName(int& v1) : v1(v1) {}
inline int operator()(int a, int b) const { return a + b + v1; } // ok
// static int _invoke(int a, int b) { return a + b + v1; } // error
};
}
static 멤버 함수 내에선 멤버 변수에 접근할 수 없음
컴파일러가 static 멤버 함수를 만들 수 없어 함수 포인터로의 변환 함수를 만들 수 없음!
int main()
{
auto add = [](auto a, auto b) { return a + b;};
/*
template<class T1, class T2>
inline auto operator()(T1 a, T2 b) const { return a + b; }
*/
std::cout << add(1, 1) << std::endl; // inline auto operator()(int a, int b) const { ... }
std::cout << add(1.1, 1.2) << std::endl; // inline auto operator()(double a, double b) const { ... }
std::cout << add(1, 1.4) << std::endl; // inline auto operator()(int a, double b) const { ... }
}
auto add(auto a, auto b) // 결국 템플릿으로 변환
{
return a + b;
}
int main()
{
std::cout << add(1, 1) << std::endl;
std::cout << add(1.1, 1.2) << std::endl;
std::cout << add(1, 1.4) << std::endl;
}
lambda expression을 만들 때 template 문법 사용 가능 (C++20)
int main()
{
int n1 = 10;
int n2 = 20;
double d1 = 1.1;
double d2 = 2.2;
// auto swap = [](int& a, int& b) { auto tmp = a; a = b; b = tmp;};
// auto swap = [](auto& a, auto& b) { auto tmp = a; a = b; b = tmp;};
auto swap = []<typename T>(T& a, T& b) { auto tmp = a; a = b; b = tmp;};
swap(n1, n2); // 같은 타입끼리 스왑 괜찮음
swap(d1, d2);
// swap(n1, d2); // int, double 서로 다른 타입인데 스왑하면 문제 발생!
}
generic lambda expression은 인자가 각각 독립적인 템플릿 인자를 사용하게 됨
같은 타입을 사용하도록 제한하기 위해 template lambda expression을 사용