2011-09-05 16 views
6

私は自分のクラスを親クラスのテンプレートパラメータとして使用しています。その親クラスはテンプレート引数(ただしsizeof ())。子クラスを基本クラスのテンプレートパラメータとして、ネストされた名前指定子として使用する

そして、コンパイラは私に与える:

エラー:不完全型「の発動:: workerClass {別名のMyClass}」ネストされた名前指定

で使用されるしかしクラスがうまくファイルで定義されているが。基本クラスのインスタンス生成の時点で子クラスがインスタンス化されていないため、CRTPでそのようなことが起こり、問題はないからです。

私がテンプレート引数で子クラスを使用する理由は、子クラスが特定の関数を持っているか持っていない場合に、別の関数呼び出しを行うことです。

ここでエラーがラインで行わ

/* Structure similar to boost's enable if, to use 
    SFINAE */ 
template <int X=0, class U = void> 
struct test { 
    typedef U type; 
}; 

enum Commands { 
    Swim, 
    Fly 
}; 

/* Structure used for template overloading, 
    as no partial function template specialization available */ 
template<Commands T> 
struct Param { 

}; 

template <class T> 
class Invoker 
{ 
public: 
    typedef T workerClass; 

    workerClass *wc() { 
     return static_cast<workerClass*>(this); 
    } 

    template <Commands command> 
    void invoke() { 
     invoke2(Param<command>()); 
    } 

    /* If the child class has those functions, call them */ 
    /* Needs template paramter Y to apply SFINAE */ 
    template<class Y=int> 
    typename test<sizeof(Y)+sizeof(decltype(&workerClass::fly))>::type 
    invoke2(Param<Fly>) { 
     wc()->fly(); 
    } 

    template<class Y=int> 
    typename test<sizeof(Y)+sizeof(decltype(&workerClass::swim))>::type 
    invoke2(Param<Swim>) { 
     wc()->shoot(); 
    } 

    template<Commands command> 
    void invoke2(Param<command>) { 
     /* Default action */ 
     printf("Default handler for command %d\n", command); 
    } 
}; 

template <class T, class Inv = Invoker<T> > 
class BaseClass : public Inv 
{ 
public: 
    template<Commands command> 
    void invoke() { 
     Inv::template invoke<command>(); 
    } 
}; 

class MyClass : public BaseClass<MyClass> 
{ 
public: 
    void swim() { 
     printf("Swimming like a fish!\n"); 
    } 

    /* void fly(); */ 
}; 


void testing() { 
    MyClass foo; 
    foo.invoke<Fly>(); /* No 'void fly()' in MyClass, calls the default handler */ 
    foo.invoke<Swim>(); /* Should print the swimming message */ 
} 

をテストするための最小限のコードです:

typename test<sizeof(Y)+sizeof(decltype(&workerClass::fly))>::type 

ので、これをサポートする任意のコンパイラがある、またはこれを明示的と​​して標準で指定されたが、テンプレートの無効な使用ですか?私はこれをやっているやり方を変えなければならないのか、道を見つけなければならないのか? CRTPは、コードが有効であると私に期待していますが、わかりません。

これは本当に不可能な場合、なぜ正確に、そしてなぜCRTPが機能するのですか?

+4

さて、問題は、少なくとも明確である:あなたは自己参照再帰的定義を作っている: 'BaseClassのは'ので 'decltypeの完全な' MyClass'が必要です'式ですが、' MyClass'は基本クラスとして完全な 'BaseClass 'を必要とします。 –

+1

間接指定の追加層の背後にある特殊化を非表示にして、それらが基本クラス定義の一部として評価されないようにする必要があります。それらをプライベートネストされた型に入れて、すべて正常に動作します。 – ildjarn

+0

CRTPは厳密にこのような理由から難題です。あなたが直面している問題は簡単に理解できます。基本テンプレートはクラス宣言の一部として、まだ完成していない時点でインスタンス化されます。ベーステンプレートが追加される可能性があります。そのため、コンパイラは、ベーステンプレートを最初に処理することなく、派生したタイプがどのようになるかを知ることができません。派生型の関数宣言の意味) –

答えて

2

解決策は、指摘されているように、別のレベルの間接参照を追加することでした。その後、

template <typename X, class U = void> 
struct test { 
    typedef U type; 
}; 

、代わりに行く取得からそれを指定するので、テンプレートパラメータとして子クラスを渡します:

タイプを受け入れるようにテスト機能を変更することによって行われ

template<class Y=workerClass> 
    typename test<decltype(&Y::fly)>::type 
    invoke2(Param<Fly>) { 
     wc()->fly(); 
    } 

    template<class Y=workerClass> 
    typename test<decltype(&Y::swim)>::type 
    invoke2(Param<Swim>) { 
     wc()->swim(); 
    } 

このようにして、ネストした指定子は、関数が呼び出され、クラス評価ではなく評価されるときにのみ評価され、その時までに子クラスはすでに評価されています。デフォルトのテンプレート引数を渡す可能性をプラスして、テンプレートパラメータなしで関数を呼び出すことができます。

テンプレートは現在もはるかに読みやすくなっています。サンプルコードは、現在だけで正常に動作し、:

class MyClass : public BaseClass<MyClass> 
{ 
public: 
    void swim() { 
     printf("Swimming like a fish!\n"); 
    } 

    /* void fly(); */ 
}; 


void testing() { 
    MyClass foo; 
    foo.invoke<Fly>(); /* No 'void fly()' in MyClass, calls the default handler */ 
    foo.invoke<Swim>(); /* Should print the swimming message */ 
} 
+0

これは非常に興味深いものです。誰もこれに興味深いユースケースを提供できますか? – Zhro

+0

@Zhro私が使ったやり方は、仮想テーブルによって引き起こされる(無視できる)オーバーヘッドなしで仮想関数のようなシステムを作ることです。 – coyotte508

関連する問題