回答:
次のように、テンプレートテンプレートの構文を使用して、タイプが別のテンプレートに依存するテンプレートであるパラメーターを渡す必要があると思います。
template <template<class> class H, class S>
void f(const H<S> &value) {
}
これH
はテンプレートですが、この関数でのすべての特殊化を処理する必要がありましたH
。
注:私は長年c ++をプログラミングしており、これが必要になったのは1回だけです。私はそれがめったに必要とされない機能であることがわかります(もちろんそれが必要なときに便利です!)。
私は良い例を考え、正直に言うと、ほとんどの場合これは必要ありませんが、例を考えてみましょう。のは、それはふりをしてみましょうstd::vector
しない持っていますtypedef value_type
。
それでは、vectors要素に適切な型の変数を作成できる関数をどのように記述しますか?これはうまくいくでしょう。
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
は、タイプとアロケータの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;
}
これが、このタイプのコードを作成する方法です。
template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
f<vector,int>
ありそうでないのかf<vector<int>>
。
f<vector,int>
意味するf<ATemplate,AType>
、f<vector<int>>
意味するf<AType>
実際、テンプレートテンプレートパラメータのユースケースはかなり明白です。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つのテンプレート引数を受け入れることを思い出してください。アロケーターはデフォルトで設定されていますが、テンプレートオペレーターでの存在を考慮する必要があります<<:
template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...
これは、標準プロトコルに準拠している現在および将来のすべてのシーケンスコンテナーに対して自動的に機能します。ミックスにマップを追加するには、4つのテンプレートパラメーターを受け入れることに注意するために参照をのぞいてみる必要があるため、4引数のテンプレートテンプレートパラメーターを使用した上記のoperator <<の別のバージョンが必要になります。std:pairは、以前に定義したシーケンスタイプに対して2引数の演算子<<でレンダリングされることもわかるので、std :: pairだけに特化したものを提供します。
ところで、可変テンプレートを許可する(したがって可変テンプレートテンプレート引数を許可する)C + 11を使用すると、単一の演算子<<ですべてをルール化することが可能になります。例えば:
#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
__PRETTY_FUNCTION__
ます。これは、特に、テンプレートパラメータの説明をプレーンテキストで報告します。clangもそれを行います。時々最も便利な機能です(ご覧のとおり)。
Andrei Alexandrescuによる「Modern C ++ Design-Generic Programming and Design Patterns Applied」からの簡単な例を以下に示します。
彼はポリシーパターンを実装するために、テンプレートテンプレートパラメータを持つクラスを使用します。
// 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;
これが私の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になります。
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 ++ 11はテンプレートの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が必要です。
derived
ますが、テンプレート引数なしでテンプレートクラスをどのように使用できるかわかりません。つまり、行typedef typename interface<derived, VALUE> type;
template <typename>
。ある意味では、テンプレートパラメータは「メタタイプ」を持つと考えることができます。テンプレートパラメータの通常のメタタイプは、通常のタイプでtypename
埋める必要があることを意味します。template
メタタイプは、それがテンプレートを参照して充填する必要があることを意味します。 derived
は、1つのtypename
メタタイプパラメータを受け入れるテンプレートを定義するため、法案に適合し、ここで参照できます。理にかなっていますか?
typedef
です。また、DERIVED型int
などの標準構成を使用することにより、最初の例での重複を回避できますvalue_type
。
typedef
ブロック2から問題を回避できると言ってC ++ 11を参照しましたが、ポイント2は有効だと思います...ええ、それはおそらく同じことを行うためのより簡単な方法でしょう。
これは私が遭遇したものです:
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();
}
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;
}
これは、私が使用したものから一般化したものです。これは非常に単純な例であり、デフォルトの引数とともに実際の使用例を示しているため、ここに投稿します。
#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;
};
コードの可読性が向上し、型の安全性が向上し、コンパイラーの労力が一部節約されます。
コンテナーの各要素を印刷する場合は、テンプレートテンプレートパラメーターなしで次のコードを使用できます。
template <typename T> void print_container(const T& c)
{
for (const auto& v : c)
{
std::cout << v << ' ';
}
std::cout << '\n';
}
またはテンプレートテンプレートパラメーター
template< template<typename, typename> class ContainerType, typename ValueType, typename AllocType>
void print_container(const ContainerType<ValueType, AllocType>& c)
{
for (const auto& v : c)
{
std::cout << v << ' ';
}
std::cout << '\n';
}
整数sayを渡すと仮定しますprint_container(3)
。前者の場合、c
forループでの使用について文句を言うコンパイラーによってテンプレートがインスタンス化され、一致する型が見つからないため、後者はテンプレートをまったくインスタンス化しません。
一般的に、テンプレートクラス/関数がテンプレートクラスとしてテンプレートクラスを処理するように設計されている場合は、それを明確にすることをお勧めします。
バージョン付きのタイプに使用します。
などのテンプレートでバージョン管理されているタイプMyType<version>
がある場合は、バージョン番号を取得できる関数を作成できます。
template<template<uint8_t> T, uint8_t Version>
Foo(const T<Version>& obj)
{
assert(Version > 2 && "Versions older than 2 are no longer handled");
...
switch (Version)
{
...
}
}
そのため、型ごとにオーバーロードを設定する代わりに、渡される型のバージョンに応じてさまざまなことを実行できます。一般的な方法でを取り込んでMyType<Version>
返す変換関数を使用することもできます。また、古いバージョンからタイプの最新バージョンを返す関数をMyType<Version+1>
再帰することもできToNewest()
ます(しばらく前に保存された可能性のあるログに非常に役立ちます)ただし、今日の最新ツールで処理する必要があります)。