몇 일 전에 연이어서 C++로 일종의 지연된 함수호출 객체를 만드는 코드에 관해 포스팅했었다. (#1, #2, #3 참조)
마지막 포스팅에서도 해결 못했던 문제가 아래 코드에 나와있는 템플릿 함수를 사용할 때,
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 ); }
void Foo::Bar( int&, int, int ) 같은 함수를 func 자리에 전달할 경우에 생기는 문제였다. C++에서 템플릿 함수의 인자 T를 다음과 같은 타입으로 추정한다. (From IBM linux Compiler page; 해석법†)
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& 로 해석될 여지를 가지고 있기 때문에‡ "모호한 타입: 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로 전달받고 있기도하고;
*
†여기서 T, U, V는 템플릿 인자, 10은 임의의 정수 상수, i는 타입이 아닌 템플릿 인자를 나타낸다.
TT는 템플릿의 템플릿 인자(…), ( )로 둘러쌓인 것들은 함수의 매개변수 리스트가 된다. 그리고 <i>는 템플릿 인자 목록이지만 적어도 하나는 타입 인자가 아닐 것 (여기서 사용한 i의 의미), <C>는 다른 템플릿 매개변수로 넘겨지는 것과 연관이 없는 템플릿 인자 목록.
‡ 정확히는 [ T = int ] 로 해석하는 상황.
항목 42: typename의 두가지 의미를 제대로 파악하자….
내가 C++에 조예가 깊어서 글을 남기는 것이 아니라, Effecitve C++ 을 공부하는 사람들이 이 글을 보고, 도움이 되었으면 하는 생각과, 혹시 내가 틀린것이 있다면 지적해 주시지 않을까 란 생각…