오늘도 역시 뉴스그룹에서 하나 가져왔습니다. ((c.l.c.m:Question on type deduction))
정말 template 프로그래밍은 하나 하나 퍼즐 퀴즈같습니다. :-)
Question
void augment(int& outNumber)
{
++outNumber;
}
template<typename R, typename A1>
R exec(R (*pfunc)(A1), A1 arg1 )
{
return pfunc(arg1);
}
int main()
{
int number = 10;
exec(augment, number); // <-- compiler error
}
위의 코드에서 main() 안의 첫번째 exec 함수 호출을 보면 R 타입과 A1 타입을 컴파일러가 유추하게 되는데 augment 인자 타입이 void (*)(int&) 이므로 R은 void, A1은 int& 가 되어 다음과 같이 instantiation될 것으로 생각됩니다 void exec(void (*pfunc)(int&), int& arg1)
하지만 예상과는 달리 컴파일 에러가 발생하는군요. 이제 질문입니다. - 왜 위와 같이 컴파일 에러가 발생할까요?
- 해결 방법은 무엇일까요?
Answer
먼저 이유를 살펴보겠습니다. 컴파일러는 exec 함수를 찾기 위해 먼저 각각의 인자에 대해 type deduction을 수행합니다. exec(augment, number) 함수 호출에서 컴파일러는 augment 인자는 void (*)(int&) 타입이라는 것을 찾아내고 number 인자는 int 라는 것을 찾아냅니다. 여기서 중요한 점은 number는 int 이지 int& 가 아니라는 점입니다. 이렇게 찾아낸 정보를 가지고 적당한 함수를 찾아보게 됩니다. 컴파일러는 위의 template 함수인 exec 를 찾아내게 되고 자신이 유추한 타입을 대입하여 올바른지 확인합니다. 하지만 이 경우에는 첫번째 인자와 두번째 인자가 A1 타입에 대해 동의하지 않고 있다는 것을 알아내죠. 첫번째 인자를 적용해보면 A1은 int& 가 되어야 하는데 두번째 인자를 적용해보면 int가 되어야 하니까요. 따라서 컴파일러는 에러를 발생시키게 됩니다. 그럼 이 문제를 해결하기 위해 어떤 방법을 사용해야 할까요. 물론 다음과 같이 타입을 명시적으로 써주면 되나 깔끔한 방법이 아니죠.exec<void, int&>(augment, number); // compiles OK
다음 코드를 보시죠. template <typename T>
struct SelfType
{
typedef T type;
};
void augment(int& outNumber)
{
++outNumber;
}
template<typename R, typename A1>
R exec(R (*pfunc)(A1), typename SelfType<A1>::type arg1 )
{
return pfunc(arg1);
}
int main()
{
int number = 10;
exec(augment, number); // <-- compiler OK
}
위의 코드에서는 SelfType이라는 단순한 클래스를 하나 사용하여 두번째 인자를 dependent name으로 만들었음을 알 수 있습니다. 이렇게 하면 컴파일 에러가 발생하지 않게 됩니다. 이 방법은 다음 내용을 이용합니다. Function template argument deduction doesn't work where the function parameter type is a dependent name.즉, dependent name인 함수 인자에 대해서는 type deduction이 일어나지 않습니다. 따라서 위의 코드에서 두번째 인자의 타입은 첫번째 인자에 의해 결정된 타입인 int&가 그대로 사용되게 됩니다.
정말 template 프로그래밍은 하나 하나 퍼즐 퀴즈같습니다. :-)
저런 테크닉이 많이 적용된 곳이 boost::function/boost::bind이지요.
ReplyDeleteboost::bind는 변수저장공간 문제때문에, reference를 binding시킬때는 boost::ref를 쓰더라는...
template은 하면 할수록 빠져드는 매력이 있는것 같습니다. :)
그렇군요. 그러고보니 boost 라이브러리는 아직 사용만 하지 코드를 깊이 들여다본적은 없네요. 주로 In-depth 시리즈 책들을 통해서 이렇게 구현되었겠거니 하고 짐작만... :-)
ReplyDelete그리고 주로 쓰는 boost 라이브러리중의 하나가 function 입니다. (물론 젤 자주 쓰는 건 shared_ptr) lambda 나 bind 와 같이 쓰면 뽀대 나는 코드를 작성할 수 있다는... -_-;
function이 lambda, bind랑 쿵짝을 하게되면 브라보죠. 허나.. function은 성능면에서 떨어지는 경우가 종종 있더군요. 주의해서 사용해야 할듯. 흑;
ReplyDelete다행히 제가 하는 것들은 성능에 그렇게 많이 민감하지 않아서요. 가끔 성능 문제로 string 대신 char[] 을 쓰거나 vector 대신 그냥 array를 써야 한다고 하는 분들이 아직도 주변에 있는데 전 일단 쉽게 가고 성능 문제는 발생하기 전에는 생각하지 않는답니다. ;-)
ReplyDelete근데 function의 call 성능이야 기껏해야 한두번의 indirect 호출인데 어지간히 작은 함수가 아니고서는 큰 문제가 발생하진 않을 것 같은데 아니던가요?
실은 컴파일러가 최적화를 하는 과정에서 불필요한 스택을 다 날리기 때문에... function이 갖는 오버헤드는 stack1개 만큼이긴 합니다. (그래도 그게 문제가 된적이 있었다는... 흑; 이럴떄 참 우울합니다 T_T)
ReplyDeleteboost::bind는 거의 오버헤드가 없는듯. 후후.
이 포스트 매우 실용적이며 매우 재미있습니다. 기억두어야 겠습니다.
ReplyDelete이렇게 오래된 글에 스팸 아닌 댓글이 달리니 기분이 좋네요. :)
ReplyDelete