C++ template 함수의 타입 추론
몇 일 전에 연이어서 C++로 일종의 지연된 함수호출 객체를 만드는 코드에 관해 포스팅했었다. (#1, #2, #3 참조)
마지막 포스팅에서도 해결 못했던 문제가 아래 코드에 나와있는 템플릿 함수를 사용할 때,
void Foo::Bar(int&, int, int)
같은 함수를 func 자리에 전달할 경우에 생기는 문제였다.
template<typename T, typename R , typename A0 , typename A1>
Closure* MakeClosure(T& obj, R (T::*func)(A0 a0 , A1 a1), A0 a0, A1 a1) {
return new Closure2<T, R , A0 , A1> (obj, func , a0 , a1);
}
C++에서 템플릿 함수의 인자 T를 다음과 같은 타입으로 추정한다. (From IBM linux Compiler page; 해석법1)
T, const T, volatile T, T&, T*, T[10], A<T>, C(*)(T), T(*)(), T(*)(U),
T C::*, C T::*, T U::*, T (C::*)(), C (T::*)(), D (C::*)(T), C (T::*)(U),
T (C::*)(U), T (U::*)(), T (U::*)(V), E[10][i], B<i>, TT<T>, TT<i>, TT<C>
위에서 사용한 Foo::Bar()
함수는 MakeClosure
템플릿 함수에서 T
, T&
로 해석될 여지를 가지고 있기 때문에2 “모호한 타입: int/int& 일 수 있음” 이란 이유로 컴파일이 안되었다. 해결책을 놓고 좀 고민했는데, 이미 해답을 알고있던 분이 있더라.
발견한 해답은 아주 단순한 형태.
Dependent type (특정 템플릿에 의존적인 타입)의 경우에는 컴파일러가 해석하려 시도하지 않고_ 문장 그대로 이해_하기 때문에 이를 이용해서 타입을 _강제_하는 것. 즉 이런 코드를 넣어서 문제를 회피했다.
/// DependentType<T>::type is a 'dependent type'
template <typename T> struct DependentType { typedef T type; };
이제 MakeClosure에서 그냥 A0, A1, … 대신 DependentType<A0>::type, DependentType<A1>::type …
을 사용하면 문제 해결. 최종적인 코드는 밑에.
///@file Closure.h : implments Closure template class
#include <boost/preprocessor.hpp>
#include <boost/type_traits/remove_reference.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
using boost::shared_ptr;
using boost::function;
class Closure abstract : private boost::noncopyable
/// abstract base for closure implemenataion
{
public:
virtual ~Closure() { }
virtual void Execute() = 0;
};
/// DependentType<T>::type is a 'dependent type'
template <typename T> struct DependentType { typedef T type; };
#define ClosureMemberDecl( z, n, unused ) typename \
boost::remove_reference<A##n>::type m_A##n;
#define ClosureMemberEnum( z, n, unused ) A##n a##n
#define ClosureMemberEnumDependent( z, n, unused ) typename \
DependentType< A##n >::type a##n
#define ClosureMemberInit( z, n, unused ) m_A##n( a##n )
// Create Closure0, Closure1, ...
#define ClosureClassDecl( z, n ) \
template< typename T, typename R BOOST_PP_COMMA_IF(n)\
BOOST_PP_ENUM_PARAMS( n, typename A ) > \
class Closure##n : public Closure \
{\
protected:\
typedef boost::function<R (T& BOOST_PP_COMMA_IF(n)\
BOOST_PP_ENUM_PARAMS( n, A ) )> FT;\
T& m_Obj;\
FT m_Function;\
BOOST_PP_REPEAT( n, ClosureMemberDecl, ~ )\
public:\
Closure##n( T& obj, FT func BOOST_PP_COMMA_IF(n) \
BOOST_PP_ENUM( n, ClosureMemberEnum, ~ ) ) \
: m_Obj(obj), m_Function(func) BOOST_PP_COMMA_IF(n)\
BOOST_PP_ENUM( n, ClosureMemberInit, ~ ) { } \
virtual ~Closure##n() { }\
virtual void Execute() { \
m_Function( m_Obj BOOST_PP_COMMA_IF( n ) \
BOOST_PP_ENUM_PARAMS( n, m_A ) );\
}\
};
// Create ClosureHelperDecl for corresponding Closure class
#define ClosureHelperDecl( z, n ) \
template< typename T, typename R BOOST_PP_COMMA_IF(n)\
BOOST_PP_ENUM_PARAMS( n, typename A ) >\
boost::shared_ptr<Closure> MakeClosure( T& obj, \
R (T::*func)( BOOST_PP_ENUM( n, ClosureMemberEnum, ~ ) ) \
BOOST_PP_COMMA_IF(n)\
BOOST_PP_ENUM( n, ClosureMemberEnumDependent, ~ ) \
) \
{\
return boost::shared_ptr<Closure>( new Closure##n < T, R BOOST_PP_COMMA_IF(n) \
BOOST_PP_ENUM_PARAMS( n, A )>\
( obj, func BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS( n, a ) ) ); \
}
#define ClosureDecl( z, n, unused ) \
ClosureClassDecl( z, n ) \
ClosureHelperDecl( z, n )
// 최대인자수를 다음 매크로의 첫인자로
BOOST_PP_REPEAT( 5, ClosureDecl, ~ )
#undef ClosureDecl
#undef ClosureClassDecl
#undef ClosureMemberInit
#undef ClosureMemberEnum
#undef ClosureMemberDecl
이 상태로 구현해서 현재 작성 중인 코드에서 잘 사용하고 있다. 다만 미리 알려둘 주의 사항은, 첫번째 템플릿 인자인 T가 persistent-object 여야 한다 라는 것. 맨 첫 포스팅의 가정에서도 말하고 있는 것이지만, 호출할 객체가 사라져버리면 말짱 꽝이다. 복사하는게 의미 없는 상황을 가정하고 있어서 첫 인자(특정 멤버함수를 호출할 객체)를 참조자; reference로 전달받고 있기도하고;