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로 전달받고 있기도하고;


  1. 여기서 T, U, V는 템플릿 인자, 10은 임의의 정수 상수, <i>는 타입이 아닌 템플릿 인자를 나타낸다. TT는 템플릿의 템플릿 인자(…), ( )로 둘러쌓인 것들은 함수의 매개변수 리스트가 된다. 그리고 <i>는 템플릿 인자 목록이지만 적어도 하나는 타입 인자가 아닐 것 (여기서 사용한 i의 의미), <C>는 다른 템플릿 매개변수로 넘겨지는 것과 연관이 없는 템플릿 인자 목록. ↩︎

  2. 정확히는 [ T = int ] 로 해석하는 상황. ↩︎