람다와 관련하여 재밌는 제보가 있어서 테스트 코드 작성해봤습니다.
람다식 앞에 +를 붙이면 함수 포인터 타입으로 인식된다고 합니다.
테스트 코드
- 각 코드가 어떤 용도로 작성하게 되었는지 밑에서 조금 더 자세히 적어볼겠습니다.
#include <iostream>
#include <functional>
#include <typeinfo>
#include <algorithm>
void Hello()
{
std::cout << "Hello"<< std::endl;
}
struct FunctorTest
{
void operator()()
{
std::cout << "FunctorTest" << std::endl;
}
};
template<typename Fn>
void CallableTest(Fn fn)
{
fn();
}
int main()
{
using namespace std;
void (*FnPtr)() = Hello;
FnPtr();
{
FnPtr = []() {
std::cout << "Lambda" <<std::endl;
};
FnPtr();
FnPtr = +[]() {
std::cout << "Lambda +Version" << std::endl;
};
FnPtr();
}
//FnPtr = FunctorTest(); //Error
std::function<void()> FnPtr2 = FunctorTest();
auto LambdaTest = +[]() {}; //+가 있으면 함수 포인터로
LambdaTest = []() {};
auto LambdaTest2 = []() {}; //+가 없으면 class lambda
//LambdaTest2 = []() {}; //Error
auto t = FunctorTest();
t();
cout << typeid(FnPtr).name() << endl;
cout << typeid(FnPtr2).name() << endl;
cout << typeid(LambdaTest).name() << endl;
cout << typeid(LambdaTest2).name() << endl;
cout << typeid(t).name() << endl;
CallableTest([](){
cout << "CallableTest1" << std::endl;
});
CallableTest(FnPtr);
CallableTest(FnPtr2);
CallableTest(FunctorTest());
return 0;
}
1. 기본 함수 포인터 문법
- 요렇게 Hello라는 함수의 시그니처에 맞게 끔 함수 포인터 작성하여 받아서 사용하게 되면 void Hello() 함수를 호출할 수 있습니다.
void (*FnPtr)() = Hello;
FnPtr();
2. 함수 포인터에 람다 담기
- 오늘 글 쓰게 된 이유인데 람다 앞에 + 기호를 더 붙여서 람다를 람다가 아닌 함수포인터 타입으로 받을 수 있게 끔 하는 문법이라고 합니다.
- 일단 기본적으로 함수 포인터에 람다가 시그니처만 맞다면 잘 담을 수 있습니다.
- 캡처는 아무것도 작성하지 않아야 합니다.
{
FnPtr = []() {
std::cout << "Lambda" <<std::endl;
};
FnPtr();
FnPtr = +[]() {
std::cout << "Lambda +Version" << std::endl;
};
FnPtr();
}
3. functor 담기
- 일단 일반 함수 포인터 타입에는 functor를 담을 수 없습니다. 기본적으로 functor가 우측에 오게 되면 구조체를 인스턴스화 하겠다 정도로 생각해야 할 것입니다. 당연히 함수 포인터 타입에 구조체를 담을 수 없습니다.
- 만능의 std::function은 functor도 담아버립니다. 내부적으로 어떻게 동작하는지는 알아보려고 했는데 너무 어렵더라구요. std::function은 맘 편하게 Callable은 다 담을 수 있다 라고 생각하면 될 것 같습니다.
//FnPtr = FunctorTest(); //Error
std::function<void()> FnPtr2 = FunctorTest();
4. auto로 람다 담기
- 요 부분이 오늘 알아낸 점인데 첫 번째 LambdaTest는 auto로 타입을 추론하길 함수 포인터 타입으로 추론합니다. 그래서 밑에서 다시 람다를 담으려고 할 때 허용해주는 모습입니다.
- LambdaTest2는 auto가 추론하길 class lambda[]()->void 뭐 이렇게 추론하더라구요. LambdaTest2는 다른 함수 포인터도 람다도 담을 수 없는 타입이 되어 버렸습니다.
auto LambdaTest = +[]() {}; //+가 있으면 함수 포인터로
LambdaTest = []() {};
auto LambdaTest2 = []() {}; //+가 없으면 class lambda
//LambdaTest2 = []() {}; //Error
5. auto로 functor를 담게 되면 ?
- 그냥 구조체 타입으로 추론되는 모습입니다.
auto t = FunctorTest();
t();
6. 각각의 typeid ?
- typeinfo 헤더를 추가하면 쓸 수 있는 typeid(조사할타입).name()
- 순서대로 1. 함수 포인터 2. std::function 3. 함수 포인터 4. 람다 5. 구조체
- 3번의 경우 +를 붙인 람다식은 auto로 받은 경우인데 함수 포인터 타입으로 읽혀진 걸 확인할 수 있습니다.
cout << typeid(FnPtr).name() << endl;
cout << typeid(FnPtr2).name() << endl;
cout << typeid(LambdaTest).name() << endl;
cout << typeid(LambdaTest2).name() << endl;
cout << typeid(t).name() << endl;
7. 람다, 함수 포인터, std::function, functor 구분짓지 않고 함수 인자로 받아서 호출
- 요렇게 구성해두면 인자없는 람다, 함수 포인터, std::function, functor 다 호출할 수 있습니다.
- 시그니처 안 맞췄을 때 assert내는 법은 모르겠습니다.
template<typename Fn>
void CallableTest(Fn fn)
{
fn();
}
CallableTest([](){
cout << "CallableTest1" << std::endl;
});
CallableTest(FnPtr);
CallableTest(FnPtr2);
CallableTest(FunctorTest());