大規模なクラスをリファクタリングしようとしています。コピーペーストコードが膨大なので、Big
としましょう。このコピー・ペースト・コードの多くは、switch
case
に存在し、関連するタイプのみが異なることになります。コードは、実行時にのみ値が分かっているクラスのメンバー変数enum
に基づいて切り替えられます。テンプレートパラメータを使用したC++関数のディスパッチ
この問題を修正しようとすると、というstatic
関数を使用して型付き関数を検索するDispatcher
クラスが必要です。実際の作業を行う関数は常にgo()
と呼ばれ、ラッパークラステンプレート(その唯一のパラメーターはランタイムenum
の値が現在オンになっています)で定義する必要があります。 go()
関数は、テンプレート関数自体であってもなくてもよい。
ここにコードの蒸留版があります。長さのために私の謝罪が、これは私が重要な文脈を失うことなくそれを得ることができるほど短かった。
#include <cassert>
class Big
{
public:
enum RuntimeValue { a, b };
Big(RuntimeValue rv) : _rv(rv) { }
bool equals(int i1, int i2)
{
return Dispatcher<Equals, bool(int, int)>::lookup(_rv)(i1, i2);
}
template<typename T>
bool isConvertibleTo(int i)
{
return Dispatcher<IsConvertibleTo, bool(int)>::lookup<T>(_rv)(i);
}
private:
template<RuntimeValue RV>
struct Equals
{
static bool go(int i1, int i2)
{
// Pretend that this is some complicated code that relies on RV
// being a compile-time constant.
return i1 == i2;
}
};
template<RuntimeValue RV>
struct IsConvertibleTo
{
template<typename T>
static bool go(int i)
{
// Pretend that this is some complicated code that relies on RV
// being a compile-time constant.
return static_cast<T>(i) == i;
}
};
template<template<RuntimeValue> class FunctionWrapper, typename Function>
struct Dispatcher
{
static Function * lookup(RuntimeValue rv)
{
switch (rv)
{
case a: return &FunctionWrapper<a>::go;
case b: return &FunctionWrapper<b>::go;
default: assert(false); return 0;
}
}
template<typename T>
static Function * lookup(RuntimeValue rv)
{
switch (rv)
{
case a: return &FunctionWrapper<a>::go<T>;
case b: return &FunctionWrapper<b>::go<T>;
default: assert(false); return 0;
}
}
// And so on as needed...
template<typename T1, typename T2>
static Function * lookup(RuntimeValue rv);
};
RuntimeValue _rv;
};
int main()
{
Big big(Big::a);
assert(big.equals(3, 3));
assert(big.isConvertibleTo<char>(123));
}
このほとんどことを除いて、動作します:
- それビルドおよびVisual C++ 9(2008)の下で正常に動作しますが、GCC 4.8の下で、それは
lookup()
の関数テンプレートのオーバーロードでコンパイルエラーになり。 go()
でサポートしたい関数テンプレートパラメータの新しい番号ごとに、新しい関数テンプレートのオーバーロードlookup()
を書き込む必要があります。- 使い方が煩雑で混乱します。ここで
GCCの下で発生するエラーです:
Big.cpp: In static member function 'static Function* Big::Dispatcher<FunctionWrapper, Function>::lookup(Big::RuntimeValue)':
Big.cpp(66,65) : error: expected primary-expression before '>' token
case a: return &FunctionWrapper<a>::go<T>;
^
Big.cpp(66,66) : error: expected primary-expression before ';' token
case a: return &FunctionWrapper<a>::go<T>;
^
Big.cpp(67,65) : error: expected primary-expression before '>' token
case b: return &FunctionWrapper<b>::go<T>;
^
Big.cpp(67,66) : error: expected primary-expression before ';' token
case b: return &FunctionWrapper<b>::go<T>;
^
私の質問は2つあり:
- は、なぜこれがGCCの下で構築するのに失敗している、と私はそれをどのように修正するのですか?
- これを実行する方が優れていますか(つまり、面倒で紛らわしくありません)。
コードはVisual C++ 9(2008)でコンパイルできる必要があるため、C++ 11固有のものは使用できません。
case a: return &FunctionWrapper<a>::template go<T>;
// ^^^^^^^^
case b: return &FunctionWrapper<b>::template go<T>;
// ^^^^^^^^
これは、テンプレートの名前としてスコープ解決演算子(::
)を、以下のものを解析するようコンパイラーに指示します:go
以来
お返事ありがとうございます。私の質問の第二の部分に関してアドバイスをお持ちですか?このスキームは(あなたの助けを借りて)機能しますが、私はそれには本当に満足していません。私は仮想関数を代わりに使用することを検討しましたが、C++がサポートしていない仮想関数テンプレートが必要であることがわかったとき、私はレンガの壁にぶつかりました。 – Spire
@ Spire:私は設計を分析する時間を取らず、あなたのプログラムが実際に行うことを告白しました。私はその2つの間違いを見つけ、答えを投稿すると思っていました。残念ながら、今私はそれを勉強する時間がありません(私のプログラムでも殺すには醜いバグがあります) –