テンプレートとマクロを組み合わせると、次のことが可能になります。
- 同様の関数が基本クラスで宣言されていない場合は、静的にアサートします。
- 派生クラス内で関数を宣言します。
このアプローチでは、マクロは、基本クラスの特定のメンバー関数の存在をテストする型特性テンプレートを定義します。基本クラスに指定されたメンバー関数がある場合、特性のvalue
メンバーは になりますtrue
。次にvalue
、型特性の が静的アサート内で使用されます。
#define TRAITNAME_HELPER( PREFIX, FN, LN ) PREFIX ## FN ## LN
#define TRAITNAME( FN, LN ) TRAITNAME_HELPER( has_fn, FN, LN )
#define OVERRIDE_IMPL( CLASS, RETURN_TYPE, FN, ARG_TYPES, LN ) \
/* Type trait used to determine if \
* RETURN_TYPE T::FN( ARG_TYPES ) exists. */ \
template < typename T > \
struct TRAITNAME( FN, LN ) \
{ \
/* Type that expects a value for the specific type. For \
* example, type_check< int, 4 >. */ \
template < typename U, U > struct type_check {}; \
\
/* Use type_check expect a specific \
* pointer-to-member-function on T. */ \
template < typename U > \
static std::true_type \
check( type_check< RETURN_TYPE (T::*)ARG_TYPES, \
&U::FN >* = 0 ); \
\
template < typename U > \
static std::false_type check( ... ); \
\
/* Determine which check function was resolved for T. */ \
typedef decltype( check< T >( 0 ) ) type; \
static constexpr decltype(type::value) value = type::value; \
}; \
static_assert( TRAITNAME( FN, LN )< CLASS >::value, \
"" #RETURN_TYPE " " #FN #ARG_TYPES \
" is not defined in " #CLASS "." ); \
RETURN_TYPE FN ARG_TYPES
#define OVERRIDE( CLASS, RETURN_TYPE, FN, ARG_TYPES ) \
OVERRIDE_IMPL( CLASS, RETURN_TYPE, FN, ARG_TYPES, __LINE__ )
- OVERRIDE_IMPL マクロの末尾にセミコロンがないため、メンバー関数をクラス内で宣言または定義できます。
- オーバーロードされたメソッドをサポートするには、追加レベルのマクロが必要です。
__LINE__
ユニークな型特性を作成するために使用しています。
次のBase
クラスで:
template < typename TDerived >
struct Base
{
bool CheckSomeFlag();
bool CheckSomeFlag(int, int);
};
Dervied
クラスが次のように定義されている場合:
struct Derived : public Base< Derived >
{
OVERRIDE( Base< Derived >, bool, CheckSomeflag, () );
};
その後、コンパイルは次のエラー ( demo )で失敗します。
error: static assertion failed: "bool CheckSomeflag() is not defined in Base< Derived >."
ただし、型が正しい場合は、次のようにコンパイルされます。
struct Derived : public Base< Derived >
{
OVERRIDE( Base< Derived >, bool, CheckSomeFlag, () );
OVERRIDE( Base< Derived >, bool, CheckSomeFlag, (int a, int b) )
{
return ( a > b );
}
};
bool Derived::CheckSomeFlag() { return true; }
このアプローチには、次の 2 つの欠点があります。
- 戻り値の型は完全に一致する必要があります。余分な作業をしなくても、これにより、共変の戻り値の型が使用されなくなります。ただし、これは CRTP パターンでは望ましい動作である可能性があります。
- マクロ構文は、関数の型を覆い隠します。たとえば、 a を
bool(int,int)
返し、bool
2 つのint
引数を持つ関数の型として使用できる代わりに、マクロに 2 つの別個の引数として渡す必要があります( ..., bool, ..., (int, int) )
。これは、マクロが期待する引数の順序を変更することで軽減できますが、関数の通常の宣言に一致する順序を選択します: return-type identifier(args)
.