我已经看到一些c++使用模板模板参数(即将模板作为参数的模板)来进行基于策略的类设计的示例。这种技术还有什么其他用途?
当前回答
假设您正在使用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参数的代码
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,后者将根本不实例化模板,因为没有找到匹配的类型。
一般来说,如果你的模板类/函数被设计成将模板类作为模板形参处理,最好把它弄清楚。
这是我的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)。
假设您正在使用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();
}
在使用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;
}
推荐文章
- 为什么这个结合赋值和相等检查的if语句返回true?
- cplusplus.com给出的错误、误解或坏建议是什么?
- 找出质数最快的算法是什么?
- 访问Handlebars.js每次循环范围之外的变量
- c++枚举类可以有方法吗?
- 格式化IO函数(*printf / *scanf)中的转换说明符%i和%d之间的区别是什么?
- 将析构函数设为私有有什么用?
- main()中的Return语句vs exit()
- 为什么c#不提供c++风格的'friend'关键字?
- 在函数的签名中添加关键字
- 我如何在Visual Studio中预处理后看到C/ c++源文件?
- 为什么在标准容器中使用std::auto_ptr<>是错误的?
- 用比较double和0
- 保护可执行文件不受逆向工程的影响?
- 在c++中字符串前面的“L”是什么意思?