web-dev-qa-db-ja.com

C ++静的ポリモーフィズム(CRTP)および派生クラスからのtypedefの使用

私は Wikipediaの記事 を読んで、静的(読み取り:コンパイル時)ポリモーフィズムを行うためのC++での不思議な繰り返しのテンプレートパターンについて説明します。派生型に基づいて関数の戻り型を変更できるように、それを一般化したいと思いました。 (これは、基本型がテンプレートパラメーターからの派生型を知っているため、可能であるように思われます)。残念ながら、次のコードはMSVC 2010を使用してコンパイルできません(現在、gccに簡単にアクセスできないため、まだ試していません)。誰もがなぜ知っていますか?

template <typename derived_t>
class base {
public:
    typedef typename derived_t::value_type value_type;
    value_type foo() {
        return static_cast<derived_t*>(this)->foo();
    }
};

template <typename T>
class derived : public base<derived<T> > {
public:
    typedef T value_type;
    value_type foo() {
        return T(); //return some T object (assumes T is default constructable)
    }
};

int main() {
    derived<int> a;
}

ところで、追加のテンプレートパラメータを使用する回避策がありますが、それは好きではありません---継承チェーンに多くのタイプを渡すと、非常に冗長になります。

template <typename derived_t, typename value_type>
class base { ... };

template <typename T>
class derived : public base<derived<T>,T> { ... };

編集:

この状況でMSVC 2010が表示するエラーメッセージはerror C2039: 'value_type' : is not a member of 'derived<T>'

g ++ 4.1.2( codepad.org 経由)はerror: no type named 'value_type' in 'class derived<int>'

65
Samuel Powell

derivedは、その基本クラスリストでbaseのテンプレート引数として使用すると不完全です。

一般的な回避策は、特性クラステンプレートを使用することです。これがあなたの例です。これは、トレイトを通じて派生クラスの型と関数の両方を使用する方法を示しています。

// Declare a base_traits traits class template:
template <typename derived_t> 
struct base_traits;

// Define the base class that uses the traits:
template <typename derived_t> 
struct base { 
    typedef typename base_traits<derived_t>::value_type value_type;
    value_type base_foo() {
        return base_traits<derived_t>::call_foo(static_cast<derived_t*>(this));
    }
};

// Define the derived class; it can use the traits too:
template <typename T>
struct derived : base<derived<T> > { 
    typedef typename base_traits<derived>::value_type value_type;

    value_type derived_foo() { 
        return value_type(); 
    }
};

// Declare and define a base_traits specialization for derived:
template <typename T> 
struct base_traits<derived<T> > {
    typedef T value_type;

    static value_type call_foo(derived<T>* x) { 
        return x->derived_foo(); 
    }
};

あなただけの専門にする必要がありますbase_traitsテンプレート引数に使用するすべての型の場合derived_t of baseそして、各専門分野がbaseに必要なすべてのメンバーを提供することを確認してください。

60
James McNellis

特性を使用することの小さな欠点の1つは、派生クラスごとに1つを宣言する必要があることです。次のように、より冗長で冗長な回避策を記述できます。

template <template <typename> class Derived, typename T>
class base {
public:
    typedef T value_type;
    value_type foo() {
        return static_cast<Derived<T>*>(this)->foo();
    }
};

template <typename T>
class Derived : public base<Derived, T> {
public:
    typedef T value_type;
    value_type foo() {
        return T(); //return some T object (assumes T is default constructable)
    }
};

int main() {
    Derived<int> a;
}
8
matovitch

C++ 14では、typedefを削除し、関数autoの戻り値の型の控除を使用できます。

template <typename derived_t>
class base {
public:
    auto foo() {
        return static_cast<derived_t*>(this)->foo();
    }
};

これは、base::fooの戻り値の型の推定がderived_tが完了するまで遅延されるため機能します。

6
Oktalist

ボイラープレートをあまり必要としない型の特性の代替手段は、typedef(またはusing)を保持するラッパークラス内に派生クラスをネストし、そのラッパーをテンプレート引数として基本クラスに渡すことです。

template <typename Outer>
struct base {
    using derived = typename Outer::derived;
    using value_type = typename Outer::value_type;
    value_type base_func(int x) {
        return static_cast<derived *>(this)->derived_func(x); 
    }
};

// outer holds our typedefs, derived does the rest
template <typename T>
struct outer {
    using value_type = T;
    struct derived : public base<outer> { // outer is now complete
        value_type derived_func(int x) { return 5 * x; }
    };
};

// If you want you can give it a better name
template <typename T>
using NicerName = typename outer<T>::derived;

int main() {
    NicerName<long long> obj;
    return obj.base_func(5);
}
2
Alkis

これは基本的にあなたが見つけて気に入らない回避策であることは知っていますが、私はそれを文書化し、基本的にはこの問題に対する現在の解決策であるとも言いたかったのです。

私はこれをしばらくの間行う方法を探していましたが、良い解決策を見つけられませんでした。それが不可能であるという事実が、最終的にはboost::iterator_facade<Self, different_type, value_type, ...>多くのパラメータが必要です。

もちろん、次のようなものが機能することを望みます。

template<class CRTP> 
struct incrementable{
    void operator++(){static_cast<CRTP&>(*this).increment();}
    using ptr_type = typename CRTP::value_type*; // doesn't work, A is incomplete
};

template<class T>
struct A : incrementable<A<T>>{
    void increment(){}
    using value_type = T;
    value_type f() const{return value_type{};}
};

int main(){A<double> a; ++a;}

これが可能であれば、派生クラスのすべての特性を暗黙的に基本クラスに渡すことができます。同じ効果が得られることがわかったイディオムは、特性を基本クラスに完全に渡すことです。

template<class CRTP, class ValueType> 
struct incrementable{
    void operator++(){static_cast<CRTP&>(*this).increment();}
    using value_type = ValueType;
    using ptr_type = value_type*;
};

template<class T>
struct A : incrementable<A<T>, T>{
    void increment(){}
    typename A::value_type f() const{return typename A::value_type{};}
//    using value_type = typename A::value_type;
//    value_type f() const{return value_type{};}
};

int main(){A<double> a; ++a;}

https://godbolt.org/z/2G4w7d

欠点は、派生クラスの特性は修飾されたtypenameまたはusingによってreenabledでアクセスする必要があることです。

0
alfC