75

ウィキペディアの記事で、静的 (コンパイル時) ポリモーフィズムを行うための 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>'

4

5 に答える 5

76

derivedbase基本クラス リストでテンプレート引数として使用する場合は不完全です。

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

// 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に使用する型を特殊化baseし、各特殊化が必要なすべてのメンバーを提供するようにするだけbaseです。

于 2011-05-15T05:16:29.580 に答える
11

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

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

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

于 2016-08-12T13:30:30.110 に答える
3

より少ないボイラープレートを必要とする型特性の代替は、派生クラスを、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);
}
于 2017-11-13T12:23:45.350 に答える
0

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

これを行う方法をしばらく探していましたが、良い解決策が見つかりませんでした。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

于 2019-01-16T07:04:36.037 に答える