2008-10-12

has_xxxの実装方法

BOOST_MPL_HAS_XXX_TRAIT_DEF

これは数ある変態的なBoostのメタ関数の中でも、実に変わったもので、次のように使う。

BOOST_MPL_HAS_XXX_TRAIT_DEF(xxx)

struct a { int xxx ; } ;
struct b { void xxx() ; } ;
struct c { typedef int xxx ; } ;
struct d { struct xxx ; } ;

BOOST_STATIC_ASSERT(( has_xxx<a>::value )) ;// Error
BOOST_STATIC_ASSERT(( has_xxx<b>::value )) ;// Error
BOOST_STATIC_ASSERT(( has_xxx<c>::value )) ;// OK
BOOST_STATIC_ASSERT(( has_xxx<d>::value )) ;// OK

実に不思議な挙動をする。メタ関数has_xxxは、引数の型がxxxというnested type nameを持っているかどうかの判断する。一体どうやって実装しているのであろうか。

実は、それほど難しい実装でもない。

#include <boost/mpl/bool.hpp>


namespace hito {

namespace detail {


template< typename T >
class has_xxx_impl {

    typedef char yes_type ;
    typedef struct { char c[8]; } no_type ;

    template< typename U >
    static yes_type check( U *, typename U::xxx * = NULL ) ;


    template< typename U >
    static no_type check(...) ;
public:
    static bool const value = sizeof( check<T>( NULL ) ) == sizeof( yes_type ) ;
} ;

}// namespace detail

template<typename T>
struct has_xxx : boost::mpl::bool_< detail::has_xxx_impl<T>::value > {};

}// namespace hito

実装はこれだけだ。ようするにSFINAEとオーバーロードを利用している。これは規格的に正しい。こんな実装もある。

#include <boost/mpl/bool.hpp>


namespace hito {

namespace detail {


template < typename T > struct sfinae_helper { typedef void type ; } ;

template < typename T, typename U = void >
struct has_xxx_impl
{
    static bool const value = false ;
} ;

template< typename T>
struct has_xxx_impl< T, typename sfinae_helper< typename T::xxx >::type >
{
    static bool const value = true ;
} ;

}// namespace detail

template<typename T>
struct has_xxx : boost::mpl::bool_< detail::has_xxx_impl<T>::value > {};

}// namespace hito

これはオーバーロードを使わないテクニックだ。これはVC7.1のためのworkaroundらしい。

さて、これはネストされたある型名の有無を判断するメタ関数だ。具体的な型についてはどうでもいい。もちろん、必要とあらば具体的な型でのみtrueを返す実装もできるが、それは話の枝葉だ。もし、型名ではなく、メンバ変数やメンバ関数そのものの有無を判断したい場合はどうすればいいのだろうか。つまり、上の使用例でいう、a,bのクラスがコンパイルエラーにならないようなメタ関数を実装するにはどうすればいいのだろうか。それには、次のような二つの実装が考えられる。

//オーバーロードあり
template< typename T >
class has_xxx_impl {

    typedef char yes_type ;
    typedef struct { char c[8]; } no_type ;

    template< typename U >
    static yes_type check( U *, int U::* = &T::xxx ) ;


    template< typename U >
    static no_type check(...) ;
public:
    static const bool value = sizeof( check<T>( NULL ) ) == sizeof( yes_type ) ;
} ;

//オーバーロードなし
template < typename T, int T::* > struct sfinae_helper { typedef void type ; } ;

template < typename T, typename U = void >
struct has_xxx_impl
{
    static bool const value = false ;
} ;

template< typename T>
struct has_xxx_impl< T, typename sfinae_helper< T, &T::xxx >::type >
{
    static bool const value = true ;
} ;

面白いことに、どちらもVC9では、どちらも期待通りの動作をする。すなわち、aのstatic_assertが通る。bのstatic_assertを通すには、シグネチャをvoid (T::*)(void) とすればよい。

規格ではどうなるのかよく分からない。

ちなみに、C++0xでは、decltypeとSFINAEとOverload Resolutionで、同じことができる。

4 comments:

Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...

コメント失礼します、数年経ってますが、SFINAEについて調べていたので参考にさせて頂きました。

ところで、最後のメンバ変数・メンバ関数の有無を調べる方法ですが、
VC9(2008 Express)や、gcc4.1.2(codepad)で試した結果、
「xxxというメンバ変数を持っていない型」を渡すと残念ながらコンパイルエラーになりました。
(オーバーロードなしの方法は通りました)

以後の記事で訂正済みだったりした場合はご容赦くださいm(_ _)m

江添亮 said...

まあ、いずれにしても、C++11ではdecltypeですべて解決できます。

Anonymous said...

>decltypeで
2010/01/23のエントリにありますね、拝見させて頂きました。勉強になります。