(Modern C++ : 17~) Lambda

Posted by : at

Category : Cpp


결론부터 정리

C++20의 lambda 추가기능

  • 람다표현식에서 템플릿 사용 가능
  • 평가 되지 않은 표현식에서 람다 표현식 사용가능
  • 캠쳐하지 않은 람다 표현식에서 디폴트 생성자와 대입연사자 사용가능
  • 암시적인 this, 캡쳐가 deprecated 됨
  • parameter pack 캡쳐 가능

말로 설명하면 무슨말인지 모르니 아래에 설명참고


람다표현식에서 템플릿 사용 가능

#include <iostream>

auto add1 = [](int a, int b) { return a + b; };
auto add2 = [](auto a, auto b) { return a + b; };
// generic lambda expression -> C++14부터 가능

int main()
{
  std::cout << add1(1, 2) << std::endl;       // 3
  std::cout << add1(1.1, 2.2) << std::endl;   // 3 -> error!

  std::cout << add2(1, 2) << std::endl;       // 3
  std::cout << add2(1.1, 2.2) << std::endl;   // 3.3
  std::cout << add2(1, 2.2) << std::endl;     // 3.2 -> 이걸 에러내고 싶다면??
  
  // 하고싶은것? -> 입력을 같은 타입만 받게 사용하고 싶다
}
auto add3 = [](auto a, decltype(a) b) { return a + b; };

int main()
{
    std::cout << add3(1, 2) << std::endl;       // 3
    std::cout << add3(1.1, 2.2) << std::endl;   // 3.3
    std::cout << add3(1, 2.2) << std::endl;     // error!
}

이것을 람다표현식에서 템플릿로 처리

#include <iostream>

auto add1 = [](auto a, auto b) { return a + b; };       // C++14
auto add2 = []<typename T>(T a, T b) { return a + b; }; // C++20

int main()
{
    std::cout << add1(1, 2) << std::endl;       // 3
    std::cout << add1(1.1, 2.2) << std::endl;   // 3.3
    std::cout << add1(1, 2.2) << std::endl;     // 3.2

    std::cout << add2(1, 2) << std::endl;       // 3
    std::cout << add2(1.1, 2.2) << std::endl;   // 3.3
    std::cout << add2(1, 2.2) << std::endl;     // error!
}

평가 되지 않은 표현식에서 람다 표현식 사용가능

#include <iostream>
#include <memory>

struct Freer
{
  inline void operator()(void* p) const noexcept
  {
    std::cout << "free" << std::endl;
    free(p);
  }
};

int main()
{
  std::unique_ptr<int> up1(new int);    // 안전하게 delete 가 불린다.

  //std::unique_ptr<int> up2(static_cast<int*>(malloc(100)));  // free를 통해야 delete가 된다.
  std::unique_ptr<int, Freer> up2(static_cast<int*>(malloc(100)));  // 소멸자를 지정해 줘야한다.
}

이걸 좀 람다표현식으로 간단하게 쓸 수 없나?

std::unique_ptr<int, [](int* p){free(p);}> up2(static_cast<int*>(malloc(100)));
// 이렇게 표현하고 싶은데 그냥 람다식은 안들어가고 자료형이 들어가야한다.

std::unique_ptr<int, decltype([](int* p){free(p);})> up2(static_cast<int*>(malloc(100)));
// 이게 C++20부터는 선언가능
#include <iostream>
#include <memory>

int add(int a, int b) { return a + b; }

int main()
{
  std::cout << sizeof(int) << std::endl;        // 4
  std::cout << sizeof(add(1,2)) << std::endl;   // 4 (리턴타임을 의미)

  decltype(add(1,2));   // int n : 표현식 결과의 타입

  std::cout << sizeof( [](int a, int b) { return a+b; }) << std::endl;  // 이건 뭐가 나올까?
  // C++17까지는 error
  // C++20은 동작한다.

  std::cout << sizeof( [](int a, int b) { return a+b; }) << std::endl;    // 1
  std::cout << sizeof( [](int a, int b) { return a+b; }(1,2)) << std::endl; // 4

}

캠쳐하지 않은 람다 표현식에서 디폴트 생성자와 대입연사자 사용가능

#include <iostream>

int main()
{
  int v1 = 10;
  auto f1 = [](int a, int b){ return a + b; }

  //                        C++11 ~ C++17           C++20
  decltype(f1) f2;        // Error                  ok (디폴트 생성자)
  decltype(f1) f3 = f1;   // ok                     ok (복사 생성자)
  f3 = f1;                // Error                  ok (대입 연산자)
}
#include <iostream>

int main()
{
  int v1 = 10;

  // 값을 받아 버린다면?
  auto f1 = [v1](int a, int b){ return a + b; }

  //                        C++11 ~ C++17           C++20
  decltype(f1) f2;        // Error                  Error (디폴트 생성자)
  decltype(f1) f3 = f1;   // ok                     ok (복사 생성자)
  f3 = f1;                // Error                  Error (대입 연산자)
}

암시적인 this, 캡쳐가 deprecated 됨

#include <iostream>
#include <functional>

struct Sample
{
  int value = 0;
  auto foo()
  {
    int n = 10;

    // 멤버함수에서 [=]의 의미는 [=] 뿐만아니라 this까지 capture
    auto f = [=](int a) { return a + n + value; };  
    // value라는 멤버데이터는 this가 capture되기에 접근이 가능
    std::cout << sizeof(f) << std::endl;
    return f;
  }
};

std::function<int(int)> f;  // 전역으로 function pointer 선언함.

void goo()
{
  Sample s;
  f = s.foo();
}

int main() 
{ 
  goo(); 
  std::cout << f(10) << std::endl;    
  // C++17 - 컴파일은 되지만 쓰레기 값이 나온다. - goo에서 생성된 Sample은 소멸된지 오래!
  // C++20 - warning이 나타난다 - 암묵적 this를 쓰지말라고 경고나옴
}

parameter pack 캡쳐 가능

#include <iostream>

// Capture Parameter pack by value.
template<typename ... Args> auto f1(Args&&... args)
{
    return [...args = std:forward<Args>(args)](){ (std::cout << ... << args); };
}

// Capture Parameter pack by reference
template<typename ... Args> auto f2(Args&&... args)
{
    return [&...args = std::forward<Args>(args)](){ (std::cout << ... << args); };
}

int main()
{
    f1(1,2,3,4,5)();

    int a = 1, b = 2, c = 3;
    f2(a,b,c)();
}

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links