我已经看到一些c++使用模板模板参数(即将模板作为参数的模板)来进行基于策略的类设计的示例。这种技术还有什么其他用途?
当前回答
实际上,模板模板参数的使用情况是相当明显的。一旦你了解到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;
}
Then you'd figure out that code for vector is just the same, for forward_list is the same, actually, even for multitude of map types it's still just the same. Those template classes don't have anything in common except for meta-interface/protocol, and using template template parameter allows to capture the commonality in all of them. Before proceeding to write a template though, it's worth to check a reference to recall that sequence containers accept 2 template arguments - for value type and allocator. While allocator is defaulted, we still should account for its existence in our template operator<<:
template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...
瞧,这将为所有遵循标准协议的当前和未来序列容器自动工作。要将map添加到mix中,可以查看一下reference,并注意到它们接受4个模板参数,因此我们需要上述操作符<<的另一个版本,带有4个参数的template template param。我们还会看到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
其他回答
假设您正在使用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;
请注意,您取消了直接向派生模板提供其他模板参数;“接口”仍然接收它们。
这还允许您在依赖于类型参数的“接口”中构建类型defs,可以从派生的模板中访问。
上面的typedef不起作用,因为你不能对一个未指定的模板进行typedef。然而,这是可行的(c++ 11对模板类型defs有原生支持):
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) { 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();
}
这是我的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>
它将在GPU上具有float类型的权重和输入,但connection_matrix将始终是int,无论是在CPU上(通过指定TT=Tensor)还是在GPU上(通过指定TT=TensorGPU)。
下面是一个简单的例子,摘自Andrei Alexandrescu的《现代c++设计-泛型编程和设计模式的应用》:
他使用一个带有模板参数的类来实现策略模式:
// 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;
实际上,模板模板参数的使用情况是相当明显的。一旦你了解到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;
}
Then you'd figure out that code for vector is just the same, for forward_list is the same, actually, even for multitude of map types it's still just the same. Those template classes don't have anything in common except for meta-interface/protocol, and using template template parameter allows to capture the commonality in all of them. Before proceeding to write a template though, it's worth to check a reference to recall that sequence containers accept 2 template arguments - for value type and allocator. While allocator is defaulted, we still should account for its existence in our template operator<<:
template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...
瞧,这将为所有遵循标准协议的当前和未来序列容器自动工作。要将map添加到mix中,可以查看一下reference,并注意到它们接受4个模板参数,因此我们需要上述操作符<<的另一个版本,带有4个参数的template template param。我们还会看到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
推荐文章
- 是否需要手动关闭ifstream?
- 为什么函数指针定义可以使用任意数量的&号或星号* ?
- 为什么我必须通过this指针访问模板基类成员?
- 什么是可重入函数?
- 什么是栈展开?
- 我如何读整个文件到性病::字符串在c++ ?
- 如何在c++中使用枚举
- 为什么512x512矩阵的转置比513x513矩阵的转置慢得多?
- 如何在underscore.js模板中使用if语句?
- 通过引用传递数组
- 为什么非const引用不能绑定到临时对象?
- sizeof(某个指针)总是等于4吗?
- 在c++中使用数组或std::vector,性能差距是什么?
- 什么是std::decay ?什么时候应该使用它?
- Cout不是STD的成员