Lambda Function

2020. 11. 30. 23:11컴퓨터 수업/C++

람다(Lambda의 의미란?)

수학적으로 어떤 중간과정 변수를 의미한다.

 

 

 


함수 포인터

함수도 인자로 주고받을 수 있다. 이때 꼭 함수 포인터라고 하지 않아도 좋지만,

포인터로 유사 pass by reference를 구현할 수도 있다는 것을 알았으면 좋겠다.

 

Call by Value

#include <iostream>

using namespace std;

int sum(int x, int y) { return x + y; }
int mult(int x, int y) { return x * y; }
int abcd(int a, int b, int c) { return a + b + c; }

int evaluate(int(f)(int, int), int x, int y) {
    return f(x, y);
}

int main() {
    cout << evaluate(sum, 2, 3) << endl;
    cout << evaluate(mult, 2, 3) << endl;
    //cout << evaluate(&abcd,2,3)<<endl; // error
}

evaluate는 인자로 int (f) (int, int) 를 받는데

이는 (반환형) (변수(함수)명) ( 파라미터 1, 파라미터 2 )인 variable과 유사하게 기능하는 function이다. 즉, arbitrary function이 되는 것이다.

 

Call by Reference ( 유사 reference이며, 포인터 변수를 이용할 것)

#include <iostream>

using namespace std;

int sum(int x, int y) { return x + y; }
int mult(int x, int y) { return x * y; }
int abcd(int a, int b, int c) { return a + b + c; }

int evaluate(int(*f)(int, int), int x, int y) {
    return (*f)(x, y);
}

int main() {
    cout << evaluate(&sum, 2, 3) << endl;
    cout << evaluate(&mult, 2, 3) << endl;
    //cout << evaluate(&abcd,2,3)<<endl; // error
}

 

 

 

당연하게도 &를 이용한 Call by reference도 제대로 기능한다.

 

 

 

 

 


람다 펑션

키워드는 "로컬"이다. 로컬 하게 임시로 사용하고자 하는 함수가 필요하거나,

closure를 이용한, Generic 한 변수 호출이 필요하고자 하면... -> 이건 사실 감이 잘 안 오네.

 

 

참고로 lambda function을 이용해야 하거나, 함수를 인수로 넘겨야 되는 상황이 오면,

함수는 포인터 변수로 주고받는 것이 안전하다.

예를 들어,

void(f)(int x, int y){cout << x * y << endl;};

은 에러가 난다. f가 포인터 변수 형식이 아니기 때문이다.

 

 

기본형

#include <iostream>

using namespace std;

int sum(int x, int y) { return x + y; }
int mult(int x, int y) { return x * y; }
int abcd(int a, int b, int c) { return a + b + c; }

int evaluate(int(*f)(int, int), int x, int y) {
    return (*f)(x, y);
}

int main() {

    // lambda 함수 : 이름없이 일회용으로 사용되는 함수
    // - form: [](입력변수) -> 리턴타입 {본문}
    // ex) sum(): [](int x, int y) -> int { return x + y; }

    //실제 사용
    cout << evaluate([](int x, int y)-> int{return x + y;}, 2 , 3) << endl;
}

// lambda 함수 : 이름 없이 일회용으로 사용되는 함수
// - form: [ ](입력 변수)입력변수 -> 리턴타입 {본문}
// ex) sum(): [ ](int x, int y) -> int { return x + y; }

 

Simplified form

#include <iostream>

using namespace std;

int sum(int x, int y) { return x + y; }
int mult(int x, int y) { return x * y; }
int abcd(int a, int b, int c) { return a + b + c; }

int evaluate(int(*f)(int, int), int x, int y) {
    return (*f)(x, y);
}

int main() {
    // simplified lambda 함수표현 : [] (입력변수) {본문}
    // ex) mult(): [](int x, int y){ return x * y;}
    //실제 사용

    cout << evaluate([](int x, int y){return x*y;},2,3) << endl;
}

여기서 짚고 넘어가야 할 것이 있다.

blossomwhale.tistory.com/94

반환형 추론에서 decltype이라는 키워드를 사용해 본 적이 있는데, 이는 반환형이 어떤지 모르는 템플릿 함수에 대해서 사용하였다. 예시는 다음과 같다.

template<class T, class U>
auto sum(T t, U u)->decltype(t+u){
    return t + u;
}

짚어야 할 것)

->는 어떻게 사용되는 것인가?

 

함수는 포인터 변수로 담을 수 있다. 즉 1-depth 까지는 존재하는 변수로 이루어져 있는 것이다.

그런데, 그 함수에 접근하는 operator가 -> 이다.

따라서 밑의 decltype에서 ->라는 operator는 sum이라는 함수에 접근하여 (어쩌면 class와 유사하게 동작한다, 물론 c++은 그렇지 않지만 파이썬에선 그러니) 반환 type을 declare 한다.

 

마찬가지로 위의 Simplified 하지 않은 lambda function도 마찬가지다.

 [ ] (int x, int y)라는 임의의 함수에 접근하여 (->)

그 값 자체를 int {return x + y ;}라는 값으로 바꾼 것이다.

 

 

 

람다 펑션의 생성과 호출을 동시에

[](int x, int y){cout << x * y << endl;}(2,3);

 

 

 

람다 펑션을 anonymous struct나 class라고 생각하고, 변수에 대입.

int(*f)(int,int) = [](int x, int y){ return x*y;};

줄이기

auto f = [](int x, int y){ return x*y;};

그리고 그 사용

cout << evaluate(f,2,3) << endl;
cout << f(2,3) << endl;

 

 

 

 

 


Closure [ ]

Summary

[ ]   : closure 외부 변수를 lambda 함수 내부로 전달

[a] : 변수 a를 pass by value로 lambda 함수 내부에 전달

[&a] : 변수 a를 pass by reference로 lambda 함수 내부에 전달

[=] : 모든 외부 변수를 pass by value로 전달

[&] : 모든 외부변수를 pass by reference로 전달 

 

전반적인 사용법

1. 일반 사용법

int main() {
    int a = 10, b = 20;

    [](int x){
        cout << "x = " << x << endl;
    }(10);
    cout << "main::a = " << a << endl;

    return 0;
}

 

2. [a] 사용법

a는 읽기 전용으로 불러왔기 때문에, 수정할 수 없다. (check red_underLine)

 

3. [&a] 사용법

int main() {
    int a = 10, b = 20;

    [&a](int x){
        cout << "x = " << x << endl;
        a = 1000;
        cout << "a = " << a << endl;
    }(10);
    cout << "main::a = " << a << endl;

    return 0;
}

 

 

4. [&a, b] 사용법

int main() {
    int a = 10, b = 20;

    [&a, b](int x){
        cout << "x = " << x << endl;
        a = 1000;
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
    }(10);
    cout << "main::a = " << a << endl;

    return 0;
}

 

 

 

 

 

발생하는 문제.

closure에 인수를 집어넣어 lambda function을 만들게 되면,

lambda function은 더 이상 함수 포인터형 변수가 아니다. 이때부터는 객체 포인터를 갖게 된다.

C++의 단점 중 하나로 불리는데, 파이썬의 경우에는 일반 변수가 없고 모두 객체이다.

그러나 C++의 경우는 일반 변수와 포인터 변수, 그리고 포인터 객체 등 모두 나누어져 있기 때문에 이런 것을 보완할 방법이 필요하다.

따라서, evaluate의 인자 중 arbitrary function pointer를 받는 곳을 수정할 필요가 있는데

이를 C++의 STL 중 하나인 <functional> 이 해결해 준다.

이런 해결점을 포함한 코드는 다음과 같다.

 

#include <iostream>
#include <functional>
using namespace std;
int evaluate(function<int(int)> f, int x) {
   return f(x);
}

int main() {
    int a = 10, b = 20;

    cout << evaluate([a](int x){return x*a;},2);

    return 0;
}

'컴퓨터 수업 > C++' 카테고리의 다른 글

#include <algorithm>  (0) 2020.12.01
템플릿★  (0) 2020.11.19
템플릿(임시)  (0) 2020.11.19
virtual , 업/다운 캐스팅, 정적타입바인딩, 추상클래스 ★  (0) 2020.11.10
C++ 상속  (0) 2020.11.05