5

私は大規模なクラスをリファクタリングしている最中です-それを呼びましょうBig-それには大量のコピーアンドペーストコードがあります。このコピー アンド ペースト コードの多くはswitch cases に存在し、関係する型のみが異なることになります。enumコードは、実行時にのみ値がわかっているクラスのメンバー変数に基づいて切り替えています。

これを修正する私の試みは、と呼ばれるDispatcher関数を介して適切に型指定された関数を検索するクラスを持つことです。実際の作業を行う関数は常に呼び出され、ラッパー クラス テンプレートで定義する必要があります (その唯一のパラメーターは、現在オンになっているランタイム値です)。関数は、テンプレート関数自体である場合とそうでない場合があります。staticlookup()go()enumgo()

これは、コードの要約バージョンです。長々と申し訳ありませんが、これは重要な文脈を失わずにできる限り短いものでした。

#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. lookup()でサポートしたい新しい数の関数テンプレート パラメーターごとに、新しい関数テンプレート オーバーロードを記述する必要がありますgo()
  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 固有のものは使用できません。

4

2 に答える 2

7

はテンプレートの従属名であるため、曖昧さ回避ツールgoを使用する必要があります。template

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

これは、スコープ解決演算子 ( ::) の後に続くものをテンプレートの名前として解析し、その後の角かっこをテンプレート引数の区切り文字として解析するようにコンパイラに指示します。

これが GCC でのビルドに失敗するのはなぜですか? どうすれば修正できますか?

GCC は標準に準拠しており、2 フェーズの名前検索を実行するため、MSVC はインスタンス化時まで名前検索を遅らせるため、それgoがテンプレートの名前であることを認識します。

インスタンス化の前に、この情報は利用できません。これは、何が何であるかを知ることが不可能Tであり、プライマリ テンプレートが所定の名前に特化されている可能性があるTためgo、メンバー関数テンプレートの名前ではなく、データ メンバーの名前になる可能性があるためです。

とにかく、 MSVC は明確化ツールをサポートすることを期待してtemplateいるので、それを追加すると、GCC/Clang/whatever-conforms-to-the-Standard と MSVC の両方でプログラムがコンパイルされるはずです。

于 2013-05-14T20:23:22.057 に答える