我看过一些使用C++的模板模板参数(即以模板为参数的模板)来进行策略设计的例子。这种技术还有哪些用途?
我看过一些使用C++的模板模板参数(即以模板为参数的模板)来进行策略设计的例子。这种技术还有哪些用途?
我认为您需要使用模板模板语法来传递一个参数,其类型是依赖于另一个模板的模板,就像这样:
template <template<class> class H, class S>
void f(const H<S> &value) {
}
H
是一个模板,但我希望这个函数能处理所有H
的特化。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
有两个模板参数,类型和分配器,因此我们必须接受它们两个。幸运的是,由于类型推断,我们不需要显式地写出精确的类型。
您可以像这样使用:
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)
- pfalconf<vector,int>
而不是 f<vector<int>>
。 - bobobobof<vector,int>
表示 f<ATemplate,AType>
,而 f<vector<int>>
表示 f<AType>
。 - user362515H
” 是模板类型。 H
不是类型,vector
也不是类型。 它们是模板,而不是类型。 具体而言,vector
是一个类模板(而不是函数模板),而 vector<int>
则是一种模板类(即特定类型的类)。 - Aaron McDaid实际上,模板模板参数的用法非常明显。一旦你了解到C++标准库存在一个巨大的漏洞,即没有为标准容器类型定义流输出运算符,你就可以编写类似以下内容的代码:
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;
}
那么你会发现,对于向量的代码来说只是一样的,对于前向列表也是一样的,实际上,即使是众多的映射类型也都是一样的。这些模板类除了元接口/协议之外没有任何共同点,使用模板模板参数可以在所有这些模板类中捕获共性。 在编写模板之前,最好检查一下参考文献以回忆一下序列容器接受2个模板参数-用于值类型和分配器。虽然分配器已经有默认值,但我们仍然应该考虑到它在我们的模板operator<<中的存在:
template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...
#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也支持此功能。有时这是一个非常方便的功能(正如您所看到的)。 - WhozCraig这里是一个简单的例子,摘自Andrei Alexandrescu的'现代C++设计 - 泛型编程与设计模式实践':
他使用带有模板模板参数的类来实现策略模式:
// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
...
};
typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;
与没有模板模板参数的定义所需的更加繁琐且容易出错的方式不同:
typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
以下是我 CUDA卷积神经网络库 的另一个实际示例。
我有以下类模板:
template <class T> class Tensor
它实际上实现了n维矩阵操作。还有一个子类模板:
template <class T> class TensorGPU : public Tensor<T>
该模板实现了与CPU相同的功能,但是它是在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上进行计算。但是,连接矩阵始终为int类型,可以在CPU上指定TT = Tensor或者在GPU上指定TT = TensorGPU来实现。
这就是我遇到的问题:
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();
}
假设您正在使用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"的重复使用,实际上是指定给两个模板的同一类型参数。您可以使用模板模板来避免此重复:
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;
请注意,您正在消除直接提供其他模板参数给派生模板的方法;“接口”仍然接收它们。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,除非有我还没学过的其他技巧。
derived
如何在没有其模板参数的情况下使用,即行typedef typename interface<derived, VALUE> type;
。 - Carltontypedef
。此外,您可以通过在DERIVED类型中使用标准构造(例如value_type
)来避免第一个示例中的重复int
。 - rubenvbtypedef
问题。但是我认为第2点是有效的...是的,那可能是做同样事情的更简单的方法。 - Mark McKenna这是我刚刚使用的内容概括而来的。我将其发布出来,因为它是一个非常简单的例子,并且演示了一个实际的用例以及默认参数:
#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';
}
print_container(3)
。在前一种情况下,模板将由编译器实例化,编译器会抱怨循环中使用了 c
,后者根本不会实例化模板,因为找不到匹配的类型。print_container
函数的效用?如果有人编写了一个适用于基于范围的for
循环的类,但该类的性质只适用于特定类型,他们将无法将其与print_container
一起使用,即使该函数的编写方式本来可以正常工作。 - flarn2006#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;
}
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()
函数,该函数从任何旧版本返回最新版本的类型(对于可能存储一段时间但需要使用今天最新工具处理的日志非常有用)。