2013-05-14 9 views
5

大規模なクラスをリファクタリングしようとしています。コピーペーストコードが膨大なので、Bigとしましょう。このコピー・ペースト・コードの多くは、switchcaseに存在し、関連するタイプのみが異なることになります。コードは、実行時にのみ値が分かっているクラスのメンバー変数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)); 
} 

このほとんどことを除いて、動作します:

  1. それビルドおよびVisual C++ 9(2008)の下で正常に動作しますが、GCC 4.8の下で、それはlookup()の関数テンプレートのオーバーロードでコンパイルエラーになり。
  2. go()でサポートしたい関数テンプレートパラメータの新しい番号ごとに、新しい関数テンプレートのオーバーロードlookup()を書き込む必要があります。
  3. 使い方が煩雑で混乱します。ここで

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つあり:

  1. は、なぜこれがGCCの下で構築するのに失敗している、と私はそれをどのように修正するのですか?
  2. これを実行する方が優れていますか(つまり、面倒で紛らわしくありません)。

コードはVisual C++ 9(2008)でコンパイルできる必要があるため、C++ 11固有のものは使用できません。

case a: return &FunctionWrapper<a>::template go<T>; 
//         ^^^^^^^^ 
case b: return &FunctionWrapper<b>::template go<T>; 
//         ^^^^^^^^ 

これは、テンプレートの名前としてスコープ解決演算子(::)を、以下のものを解析するようコンパイラーに指示します:go以来

答えて

6

は、テンプレートの依存名である、あなたはtemplateディスアンビギュエータを使用する必要があります、その後の角カッコがテンプレート引数の区切り文字として使用されます。

これはGCCの下でビルドできないのですが、どうすれば修正できますか?

GCCは、標準に準拠した、とMSVCは、インスタンス化時まで名前の検索を遅延させ、それゆえ、goは、テンプレートの名前であることを知っていながら、two-phase name lookupを実行しているため。インスタンス化する前に

Tが何であるかを知ることは不可能であるため、この情報は、利用できない、とgoは、メンバ関数テンプレートの名前ではなく、むしろのように、主なテンプレートが与えられたTに特化することができデータメンバー。

私はMSVCがとにかくtemplate曖昧さ回避ツールをサポートすると期待しているので、GCC/Clang/whatever-conforms-to-the-standardとMSVCの両方でプログラムをコンパイルするはずです。

+0

お返事ありがとうございます。私の質問の第二の部分に関してアドバイスをお持ちですか?このスキームは(あなたの助けを借りて)機能しますが、私はそれには本当に満足していません。私は仮想関数を代わりに使用することを検討しましたが、C++がサポートしていない仮想関数テンプレートが必要であることがわかったとき、私はレンガの壁にぶつかりました。 – Spire

+0

@ Spire:私は設計を分析する時間を取らず、あなたのプログラムが実際に行うことを告白しました。私はその2つの間違いを見つけ、答えを投稿すると思っていました。残念ながら、今私はそれを勉強する時間がありません(私のプログラムでも殺すには醜いバグがあります) –

関連する問題