람다와 관련하여 재밌는 제보가 있어서 테스트 코드 작성해봤습니다.

람다식 앞에 +를 붙이면 함수 포인터 타입으로 인식된다고 합니다.

 

테스트 코드

  • 각 코드가 어떤 용도로 작성하게 되었는지 밑에서 조금 더 자세히 적어볼겠습니다.
#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

LambdaTest는 함수포인터 타입으로 추론
LambdaTest2는 람다로 추론

 

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());

 

+ Recent posts