285

テンプレート テンプレート パラメーター (テンプレートをパラメーターとして受け取るテンプレート) を使用してポリシー ベースのクラス設計を行う C++ の例をいくつか見てきました。この技術には他にどのような用途がありますか?

4

10 に答える 10

236

次のように、型が別のテンプレートに依存するテンプレートであるパラメーターを渡すには、テンプレート テンプレート構文を使用する必要があると思います。

template <template<class> class H, class S>
void f(const H<S> &value) {
}

はテンプレートですが、このH関数で のすべての特殊化を処理したかったのHです。

: 私は長年 c++ をプログラミングしてきましたが、これが必要になったのは一度だけです。これはめったに必要とされない機能だと思います (もちろん、必要なときに便利です!)。

良い例を考えてみましたが、正直なところ、ほとんどの場合、これは必要ありませんが、例を考えてみましょう。を持たstd::vector ないふりをしましょうtypedef value_type

では、ベクトル要素に対して適切な型の変数を作成できる関数をどのように作成しますか? これはうまくいくでしょう。

template <template<class, class> class V, class T, class A>
void f(V<T, A> &v) {
    // This can be "typename V<T, A>::value_type",
    // but we are pretending we don't have it

    T temp = v.back();
    v.pop_back();
    // Do some work on temp

    std::cout << temp << std::endl;
}

:std::vectorには、type と allocator の 2 つのテンプレート パラメーターがあるため、両方を受け入れる必要がありました。幸いなことに、型推論により、正確な型を明示的に書き出す必要はありません。

次のように使用できます。

f<std::vector, int>(v); // v is of type std::vector<int> using any allocator

またはさらに良いことに、次を使用できます。

f(v); // everything is deduced, f can deal with a vector of any type!

更新: この不自然な例でさえ、例示的ではありますが、c++11 の導入により、もはや驚くべき例ではありませんauto。これで、同じ関数を次のように書くことができます。

template <class Cont>
void f(Cont &v) {

    auto temp = v.back();
    v.pop_back();
    // Do some work on temp

    std::cout << temp << std::endl;
}

これは、私がこのタイプのコードを書きたい方法です。

于 2008-10-17T20:51:02.213 に答える
179

実際、テンプレート テンプレート パラメータのユースケースはかなり明白です。C++ stdlib には、標準コンテナー型のストリーム出力演算子を定義しないという大きな穴があることがわかったら、次のように記述します。

template<typename T>
static inline std::ostream& operator<<(std::ostream& out, std::list<T> const& v)
{
    out << '[';
    if (!v.empty()) {
        for (typename std::list<T>::const_iterator i = v.begin(); ;) {
            out << *i;
            if (++i == v.end())
                break;
            out << ", ";
        }
    }
    out << ']';
    return out;
}

次に、 vector のコードはまったく同じで、 forward_list も同じであることがわかります。実際には、多数のマップタイプでも同じです。これらのテンプレート クラスには、メタ インターフェイス/プロトコル以外に共通点はなく、テンプレート テンプレート パラメーターを使用すると、それらすべての共通性を捉えることができます。ただし、テンプレートの作成に進む前に、参照を確認して、シーケンス コンテナーが 2 つのテンプレート引数 (値の型とアロケーター) を受け入れることを思い出してください。アロケーターはデフォルトですが、テンプレート operator<<:

template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...

これは、標準プロトコルに準拠する現在および将来のすべてのシーケンス コンテナーに対して自動的に機能します。マップをミックスに追加するには、4 つのテンプレート パラメータを受け入れることに注意するためにリファレンスをのぞき見する必要があるため、上記の operator<< の別のバージョンと 4 引数のテンプレート テンプレート パラメータが必要です。また、std:pair は、以前に定義したシーケンス タイプに対して 2 引数 operator<< でレンダリングしようとすることもわかります。そのため、std::pair だけに特殊化を提供します。

ところで、可変個引数テンプレートを許可する C+11 では (したがって、可変個引数テンプレートのテンプレート引数を許可する必要があります)、単一の operator<< を使用してすべてを支配することができます。例えば:

#include <iostream>
#include <vector>
#include <deque>
#include <list>

template<typename T, template<class,class...> class C, class... Args>
std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
{
    os << __PRETTY_FUNCTION__ << '\n';
    for (auto const& obj : objs)
        os << obj << ' ';
    return os;
}

int main()
{
    std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
    std::cout << vf << '\n';

    std::list<char> lc { 'a', 'b', 'c', 'd' };
    std::cout << lc << '\n';

    std::deque<int> di { 1, 2, 3, 4 };
    std::cout << di << '\n';

    return 0;
}

出力

std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = float, C = vector, Args = <std::__1::allocator<float>>]
1.1 2.2 3.3 4.4 
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = char, C = list, Args = <std::__1::allocator<char>>]
a b c d 
std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = int, C = deque, Args = <std::__1::allocator<int>>]
1 2 3 4 
于 2013-01-14T02:44:19.563 に答える
74

AndreiAlexandrescuによる「ModernC++Design-Generic Programming and DesignPatternsApplied」から抜粋した簡単な例を次に示します。

彼は、ポリシーパターンを実装するために、テンプレートテンプレートパラメータを持つクラスを使用します。

// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
   ...
};

彼は次のように説明しています。 通常、ホストクラスは、ポリシークラスのテンプレート引数をすでに知っているか、簡単に推測できます。上記の例では、WidgetManagerは常にWidgetタイプのオブジェクトを管理するため、CreationPolicyのインスタンス化でユーザーにWidgetを再度指定するように要求することは冗長であり、潜在的に危険です。この場合、ライブラリコードはポリシーを指定するためにテンプレートテンプレートパラメーターを使用できます。

その結果、クライアントコードは「WidgetManager」をよりエレガントな方法で使用できるようになります。

typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;

テンプレートテンプレート引数がない定義が必要とする、より面倒でエラーが発生しやすい方法の代わりに、次のようにします。

typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
于 2008-10-18T11:03:51.510 に答える
21

これは、私のCUDA 畳み込みニューラル ネットワーク ライブラリからの別の実用的な例です。次のクラス テンプレートがあります。

template <class T> class Tensor

実際にはn次元の行列操作を実装しています。子クラス テンプレートもあります。

template <class T> class TensorGPU : public Tensor<T>

同じ機能を GPU で実装します。どちらのテンプレートも、float、double、int などのすべての基本型で動作します。また、クラス テンプレート (簡略化) もあります。

template <template <class> class TT, class T> class CLayerT: public Layer<TT<T> >
{
    TT<T> weights;
    TT<T> inputs;
    TT<int> connection_matrix;
}

ここでテンプレート テンプレート構文を使用する理由は、クラスの実装を宣言できるためです。

class CLayerCuda: public CLayerT<TensorGPU, float>

これは float 型と GPU 型の重みと入力の両方を持ちますが、connection_matrix は CPU (TT = Tensor を指定) または GPU (TT = TensorGPU を指定) のいずれかで常に int になります。

于 2011-07-17T19:13:15.827 に答える
12

CRTP を使用して、一連の子テンプレートの「インターフェイス」を提供しているとします。親と子の両方が、他のテンプレート引数でパラメトリックです。

template <typename DERIVED, typename VALUE> class interface {
    void do_something(VALUE v) {
        static_cast<DERIVED*>(this)->do_something(v);
    }
};

template <typename VALUE> class derived : public interface<derived, VALUE> {
    void do_something(VALUE v) { ... }
};

typedef interface<derived<int>, int> derived_t;

「int」の重複に注意してください。これは、実際には両方のテンプレートに指定された同じ型パラメーターです。この重複を避けるために、DERIVED のテンプレート テンプレートを使用できます。

template <template <typename> class DERIVED, typename VALUE> class interface {
    void do_something(VALUE v) {
        static_cast<DERIVED<VALUE>*>(this)->do_something(v);
    }
};

template <typename VALUE> class derived : public interface<derived, VALUE> {
    void do_something(VALUE v) { ... }
};

typedef interface<derived, int> derived_t;

他のテンプレート パラメーターを派生テンプレートに直接提供することを排除していることに注意してください。「インターフェース」はまだそれらを受け取ります。

これにより、派生テンプレートからアクセスできる型パラメーターに依存する「インターフェイス」に typedef を構築することもできます。

上記の typedef は、指定されていないテンプレートに対して typedef できないため、機能しません。ただし、これは機能します (また、C++1 にはテンプレート typedef のネイティブ サポートがあります)。

template <typename VALUE>
struct derived_interface_type {
    typedef typename interface<derived, VALUE> type;
};

typedef typename derived_interface_type<int>::type derived_t;

残念ながら、私がまだ学んでいない別のトリックがない限り、派生テンプレートのインスタンス化ごとに 1 つの derived_interface_type が必要です。

于 2012-10-09T18:47:52.477 に答える
10

これは私が遭遇したものです:

template<class A>
class B
{
  A& a;
};

template<class B>
class A
{
  B b;
};

class AInstance : A<B<A<B<A<B<A<B<... (oh oh)>>>>>>>>
{

};

次のように解決できます。

template<class A>
class B
{
  A& a;
};

template< template<class> class B>
class A
{
  B<A> b;
};

class AInstance : A<B> //happy
{

};

または(作業コード):

template<class A>
class B
{
public:
    A* a;
    int GetInt() { return a->dummy; }
};

template< template<class> class B>
class A
{
public:
    A() : dummy(3) { b.a = this; }
    B<A> b;
    int dummy;
};

class AInstance : public A<B> //happy
{
public:
    void Print() { std::cout << b.GetInt(); }
};

int main()
{
    std::cout << "hello";
    AInstance test;
    test.Print();
}
于 2014-05-29T10:17:45.507 に答える
5

これは、私が使用したものから一般化されたものです。これは非常に単純な例であり、デフォルトの引数とともに実用的な使用例を示しているため、投稿しています。

#include <vector>

template <class T> class Alloc final { /*...*/ };

template <template <class T> class allocator=Alloc> class MyClass final {
  public:
    std::vector<short,allocator<short>> field0;
    std::vector<float,allocator<float>> field1;
};
于 2015-05-19T23:13:57.500 に答える
4

pfalcon が提供する可変個引数テンプレートを使用したソリューションでは、可変個引数の特殊化の貪欲な性質のために、std::map の ostream 演算子を実際に特殊化するのが難しいことがわかりました。ここに私のために働いたわずかな改訂があります:

#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <map>

namespace containerdisplay
{
  template<typename T, template<class,class...> class C, class... Args>
  std::ostream& operator <<(std::ostream& os, const C<T,Args...>& objs)
  {
    std::cout << __PRETTY_FUNCTION__ << '\n';
    for (auto const& obj : objs)
      os << obj << ' ';
    return os;
  }  
}

template< typename K, typename V>
std::ostream& operator << ( std::ostream& os, 
                const std::map< K, V > & objs )
{  

  std::cout << __PRETTY_FUNCTION__ << '\n';
  for( auto& obj : objs )
  {    
    os << obj.first << ": " << obj.second << std::endl;
  }

  return os;
}


int main()
{

  {
    using namespace containerdisplay;
    std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
    std::cout << vf << '\n';

    std::list<char> lc { 'a', 'b', 'c', 'd' };
    std::cout << lc << '\n';

    std::deque<int> di { 1, 2, 3, 4 };
    std::cout << di << '\n';
  }

  std::map< std::string, std::string > m1 
  {
      { "foo", "bar" },
      { "baz", "boo" }
  };

  std::cout << m1 << std::endl;

    return 0;
}
于 2015-02-19T01:52:44.753 に答える