テンプレート メタプログラミングを使用して、Variant および Functor (ジェネリック ファンクター) データ型を作成しています。特定の引数タイプに対して特定の方法で引数を処理する必要があるという興味深い問題があります。理想的には、ある種のコンパイル時の条件演算子を使用して、条件が満たされた場合はメソッド A で、条件が満たされない場合はメソッド B で特定の引数を処理したいと考えています。
高レベルの問題の要約:
- バリアントの内部値によって関数ポインターの呼び出しにバリアントを渡すか、予想される引数の型がバリアント型であるかどうかに応じて、バリアント自体を呼び出しに渡す必要があります。
詳細:
Functor を呼び出すとき、関数の引数をシミュレートするために Variant の配列が使用されます。Functor のオーバーロードされたコンストラクターの 1 つの例を次に示します。
Variant operator()( Variant arg0, Variant arg1, Variant arg2 );
Variant は、渡した任意のタイプのデータで構築できます。このコードに到達するまでは問題ありません (これは、3 つの引数を必要とするシグネチャの特定のファンクター呼び出しヘルパー クラスです)。
template <typename R, typename T0, typename T1, typename T2>
Variant StaticFnCall3( MultiFnPtr fn, Variant& arg0, Variant& arg1, Variant& arg2 )
{
return reinterpret_cast<typename VoidToType<R>::type(*)(T0, T1, T2)>(fn.StaticFn)( arg0.GetValue<T0>( ), arg1.GetValue<T1>( ), arg2.GetValue<T2>( ) );
}
各 Functor には関数ポインターが格納され、関数ポインターは MultiFnPtr (マルチ関数ポインター) という共用体に格納されます。上記のように、Functor が呼び出されると、union は適切なシグネチャ タイプに型キャストされます。Functor に渡された各 Variant は、GetValue メソッドによって Variant 内に保持されている値に変換されます。これは、呼び出し中に Functor に渡された各バリアントの内部データをそれぞれの値に変換していることを意味します。変換する値の型は、テンプレート化された StaticFnCall を MultiFnPtr の署名に一致させることから推測されます。
GetValue の実装は次のとおりです。
template <typename TYPE>
const TYPE& VariantBase::GetValue( void ) const
{
return *reinterpret_cast<TYPE *>(data);
}
問題は、パラメータ型の 1 つとして Variant を取る Functor 内に関数シグネチャをラップしようとしていることです。これは、Functor が呼び出されたときに Variant が Variant を取る引数に渡される限り問題ありません。ただし、バリアントを取る引数に任意の型を渡す必要があります。次に、GetValue を使用して任意の型を Variant * に変換します。これにより、代わりに Variant のコンストラクターを使用して Variant を作成し、呼び出される関数ポインターに渡すことを希望する場合、その任意の型のデータが文字通り Variant として解釈されます。 Functor内。
対応するテンプレート タイプが Variant の場合に GetValue を使用する代わりに、StaticFnCall の関数ポインターに値を直接渡す方法を考え出そうとしています。std::enable_if と sfinae を調べましたが、解決策をまとめるのに苦労しています。私が達成しようとしているものの擬似コードの例を次に示します。
template <typename R, typename T0, typename T1, typename T2>
Variant StaticFnCall3( MultiFnPtr fn, Variant& arg0, Variant& arg1, Variant& arg2 )
{
return reinterpret_cast<typename VoidToType<R>::type(*)(T0, T1, T2)>(fn.StaticFn)( (IF_IS_VARIANT) ? arg0 : arg0.GetValue<T0>( ), (IF_IS_VARIANT) ? arg1 : arg1.GetValue<T1>( ), (IF_IS_VARIANT) ? arg2 : arg2.GetValue<T2>( ) );
}
編集:
したがって、テンプレート化されたグローバル関数を使用し、テンプレートの特殊化を使用して、2 つの方法のいずれかで引数を処理できることがわかりました。ただし、関数がインライン化されていない限り、グローバル関数は分岐を引き起こすため、これはコンパイル時の解決策ではありません。
template<typename T>
const T ArgHandle( const RefVariant& arg )
{
return arg.GetValue<T>( );
}
template<>
const Variant ArgHandle<Variant>( const RefVariant& arg )
{
return Variant( arg );
}
関数 ArgHandle はコンパイル時にオーバーロードの解決を行うため、関数呼び出しなしで希望する動作を実現する何らかの方法があると思います。使用する:
#define ARG( NUM ) \
ArgHandle<T##NUM>( arg##NUM )
template <typename R, typename T0, typename T1, typename T2>
Variant StaticFnCall3( MultiFnPtr fn, RefVariant& arg0, RefVariant& arg1, RefVariant& arg2 )
{
return reinterpret_cast<typename VoidToType<R>::type(*)(T0, T1, T2)>(fn.StaticFn)( ARG( 0 ), ARG( 1 ), ARG( 2 ) ) );
}