C ++中模板模板参数有什么用处?

我已经看到一些使用模板模板参数(即以模板为参数的模板)的C ++示例来执行基于策略的类devise。 这种技术还有什么其他用途?

我认为你需要使用模板模板语法来传递一个参数的types是一个模板依赖于另一个模板,如下所示:

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

在这里, H是一个模板,但是我想要这个函数来处理H所有特化。

注意 :我已经编写了多年的c ++,只需要一次。 我发现这是一个很less需要的function(当然当你需要的时候很方便!)。

我一直在想好的例子,说实话,大多数时候这是没有必要的,但让我们来devise一个例子。 假设std::vector 没有 typedef value_type

那么如何编写一个可以为向量元素创build正确types的variables的函数呢? 这将工作。

 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,所以我们不得不接受它们两个。 幸运的是,由于types推导,我们不需要明确写出确切的types。

你可以这样使用:

 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 。 现在相同的function可以写成:

 template <class Cont> void f(Cont &v) { auto temp = v.back(); v.pop_back(); // Do some work on temp std::cout << temp << std::endl; } 

这是我更喜欢写这种types的代码。

实际上,模板模板参数的用例是相当明显的。 一旦你知道C ++ stdlib有没有为标准容器types定义stream输出操作符的漏洞,你可以继续写下如下代码:

 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是相同的,实际上,即使对于多种地图types,它也是一样的。 这些模板类除了元接口/协议外没有任何共同之处,使用模板模板参数可以捕获所有这些模板的通用性。 在继续编写模板之前,有必要检查一个引用来回忆一下容器接受2个模板参数 – 值types和分配器。 虽然分配器是拖欠的,我们仍然应该在我们的模板运算符<<:

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

瞧,这将自动工作的所有现在和将来顺序容器遵守标准协议。 要将地图添加到混合中,需要注意的是它们接受4个模板参数,所以我们需要使用另一个版本的<<上面的4-arg模板模板参数。 我们也会看到std:pair试图用我们之前定义的序列types的2-arg操作符<<来渲染,所以我们只提供一个专用于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>>] abcd std::ostream &operator<<(std::ostream &, const C<T, Args...> &) [T = int, C = deque, Args = <std::__1::allocator<int>>] 1 2 3 4 

这是一个简单的例子,摘自Andrei Alexandrescu的“Modern C ++ Design – Generic Programming and Design Patterns Applied”

他使用具有模板模板参数的类来实现策略模式:

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

他解释说: 通常,主机类已经知道,或者可以轻易地推断出策略类的模板参数。 在上面的例子中,WidgetManager总是pipe理Widgettypes的对象,所以要求用户在CreationPolicy的实例化中再次指定Widget是多余的并且可能是危险的。在这种情况下,库代码可以使用模板模板参数来指定策略。

其效果是客户端代码可以更好地使用“WidgetManager”:

 typedef WidgetManager<MyCreationPolicy> MyWidgetMgr; 

而不是更麻烦,容易出错的方式,缺乏模板模板参数的定义将需要:

 typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr; 

这是我的CUDA卷积neural network库的另一个实例。 我有以下类模板:

 template <class T> class Tensor 

这实际上是实现了n维matrix的操纵。 还有一个子类模板:

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

它在GPU中实现相同的function。 这两个模板可以使用所有基本types,如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> 

它将同时具有浮点型和GPU型的权重和input,但是在CPU上(通过指定TT = Tensor)或在GPU上(通过指定TT = TensorGPU),connection_matrix将始终为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'的重复,它实际上是为两个模板指定的相同的types参数。 您可以使用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; 

请注意,您正在消除直接向派生模板提供其他模板参数; “界面”仍然收到他们。

这也可以让你在“接口”中build立依赖于types参数的typedef,这些types参数可以从派生模板中访问。

上面的typedef不起作用,因为你不能typedef到一个未指定的模板。 然而,这样做(和C + + 11有本地支持模板typedefs):

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

不幸的是,每个派生模板的实例化都需要一个derived_interface_type,除非还有另外一个我还没学过的技巧。

这是我碰到的:

 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) { ba = 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提供的可变参数模板的解决scheme中,由于可变参数的贪婪性,我发现很难真正专门化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; }; 

它提高了代码的可读性,提供了额外的types安全性并节省了一些编译器的工作量。

假设你想打印一个容器的每个元素,你可以使用下面的代码,没有模板模板参数

 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'; } 

假设你传入一个整数print_container(3) 。 对于前一种情况,模板将由编译器实例化,该编译器会抱怨for循环中的c的使用,后者将根本不会实例化模板,因为找不到匹配的types。

一般来说,如果你的模板类/函数被devise为处理模板类作为模板参数,那么最好清楚一点。