C++ 模板模板参数有哪些用途?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/213761/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are some uses of template template parameters?
提问by Ferruccio
I've seen some examples of C++ using template template parameters (that is templates which take templates as parameters) to do policy-based class design. What other uses does this technique have?
我见过一些 C++ 使用模板模板参数(即以模板为参数的模板)进行基于策略的类设计的示例。这种技术还有什么其他用途?
采纳答案by Evan Teran
I think you need to use template template syntax to pass a parameter whose type is a template dependent on another template like this:
我认为您需要使用模板模板语法来传递一个参数,其类型是依赖于另一个模板的模板,如下所示:
template <template<class> class H, class S>
void f(const H<S> &value) {
}
Here, H
is a template, but I wanted this function to deal with all specializations of H
.
这H
是一个模板,但我希望这个函数能够处理H
.
NOTE: I've been programming c++ for many years and have only needed this once. I find that it is a rarely needed feature (of course handy when you need it!).
注意:我已经使用 C++ 编程多年,并且只需要一次。我发现这是一个很少需要的功能(当然在你需要的时候很方便!)。
I've been trying to think of good examples, and to be honest, most of the time this isn't necessary, but let's contrive an example. Let's pretend that std::vector
doesn'thave a typedef value_type
.
我一直在想一些好的例子,老实说,大多数时候这不是必需的,但让我们设计一个例子。让我们假设std::vector
不有typedef value_type
。
So how would you write a function which can create variables of the right type for the vectors elements? This would work.
那么你将如何编写一个函数来为向量元素创建正确类型的变量呢?这会奏效。
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;
}
NOTE: std::vector
has two template parameters, type, and allocator, so we had to accept both of them. Fortunately, because of type deduction, we won't need to write out the exact type explicitly.
注意:std::vector
有两个模板参数,类型和分配器,所以我们必须同时接受它们。幸运的是,由于类型推导,我们不需要明确写出确切的类型。
which you can use like this:
你可以这样使用:
f<std::vector, int>(v); // v is of type std::vector<int> using any allocator
or better yet, we can just use:
或者更好的是,我们可以使用:
f(v); // everything is deduced, f can deal with a vector of any type!
UPDATE: Even this contrived example, while illustrative, is no longer an amazing example due to c++11 introducing auto
. Now the same function can be written as:
更新:即使是这个人为的例子,虽然具有说明性,但由于 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;
}
which is how I'd prefer to write this type of code.
这就是我更喜欢编写此类代码的方式。
回答by pfalcon
Actually, usecase for template template parameters is rather obvious. Once you learn that C++ stdlib has gaping hole of not defining stream output operators for standard container types, you would proceed to write something like:
实际上,模板模板参数的用例是相当明显的。一旦您了解到 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<<:
然后你会发现 vector 的代码是一样的,因为 forward_list 是一样的,实际上,即使对于多种地图类型,它仍然是一样的。除了元接口/协议之外,这些模板类没有任何共同点,并且使用模板模板参数可以捕获所有这些模板类的共同点。不过,在继续编写模板之前,值得检查参考以回忆序列容器接受 2 个模板参数 - 用于值类型和分配器。虽然分配器是默认的,但我们仍然应该在我们的模板 operator<< 中考虑它的存在:
template<template <typename, typename> class Container, class V, class A>
std::ostream& operator<<(std::ostream& out, Container<V, A> const& v)
...
Voila, that will work automagically for all present and future sequence containers adhering to the standard protocol. To add maps to the mix, it would take a peek at reference to note that they accept 4 template params, so we'd need another version of the operator<< above with 4-arg template template param. We'd also see that std:pair tries to be rendered with 2-arg operator<< for sequence types we defined previously, so we would provide a specialization just for std::pair.
瞧,这将自动适用于遵守标准协议的所有当前和未来的序列容器。要将地图添加到混合中,需要看一下参考,注意到它们接受 4 个模板参数,因此我们需要上面带有 4-arg 模板模板参数的 operator<< 的另一个版本。我们还会看到 std:pair 尝试使用 2-arg operator<< 为我们之前定义的序列类型呈现,因此我们将提供一个专门用于 std::pair 的特殊化。
Btw, with C+11 which allows variadic templates (and thus should allow variadic template template args), it would be possible to have single operator<< to rule them all. For example:
顺便说一句,使用 C+11 允许可变参数模板(因此应该允许可变参数模板模板参数),可以使用单个 operator<< 来统治它们。例如:
#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;
}
Output
输出
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
回答by yoav.aviram
Here is a simple example taken from 'Modern C++ Design - Generic Programming and Design Patterns Applied'by Andrei Alexandrescu:
这是从Andrei Alexandrescu 的“现代 C++ 设计 - 应用的通用编程和设计模式”中获取的一个简单示例:
He uses a classes with template template parameters in order to implement the policy pattern:
他使用带有模板模板参数的类来实现策略模式:
// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
...
};
He explains: Typically, the host class already knows, or can easily deduce, the template argument of the policy class. In the example above, WidgetManager always manages objects of type Widget, so requiring the user to specify Widget again in the instantiation of CreationPolicy is redundant and potentially dangerous.In this case, library code can use template template parameters for specifying policies.
他解释说: 通常,宿主类已经知道或可以轻松推断出策略类的模板参数。在上面的例子中,WidgetManager 总是管理 Widget 类型的对象,因此在 CreationPolicy 的实例化中要求用户再次指定 Widget 是多余的并且有潜在危险。在这种情况下,库代码可以使用模板模板参数来指定策略。
The effect is that the client code can use 'WidgetManager' in a more elegant way:
效果是客户端代码可以以更优雅的方式使用“WidgetManager”:
typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;
Instead of the more cumbersome, and error prone way that a definition lacking template template arguments would have required:
而不是缺少模板模板参数的定义需要的更麻烦且容易出错的方式:
typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
回答by Mikhail Sirotenko
Here's another practical example from my CUDA Convolutional neural network library. I have the following class template:
这是我的CUDA 卷积神经网络库中的另一个实际示例。我有以下类模板:
template <class T> class Tensor
which is actually implements n-dimensional matrices manipulation. There's also a child class template:
这实际上实现了 n 维矩阵操作。还有一个子类模板:
template <class T> class TensorGPU : public Tensor<T>
which implements the same functionality but in GPU. Both templates can work with all basic types, like float, double, int, etc And I also have a class template (simplified):
它实现了相同的功能,但在 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;
}
The reason here to have template template syntax is because I can declare implementation of the class
这里有模板模板语法的原因是因为我可以声明类的实现
class CLayerCuda: public CLayerT<TensorGPU, float>
which will have both weights and inputs of type float and on GPU, but connection_matrix will always be int, either on CPU (by specifying TT = Tensor) or on GPU (by specifying TT=TensorGPU).
它将具有浮点型和 GPU 类型的权重和输入,但 connection_matrix 将始终为 int,无论是在 CPU(通过指定 TT = Tensor)还是在 GPU(通过指定 TT=TensorGPU)。
回答by Mark McKenna
Say you're using CRTP to provide an "interface" for a set of child templates; and both the parent and the child are parametric in other template argument(s):
假设您使用 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;
Note the duplication of 'int', which is actually the same type parameter specified to both templates. You can use a template template for DERIVED to avoid this duplication:
请注意“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;
Note that you are eliminating directly providing the other template parameter(s) to the derivedtemplate; the "interface" still receives them.
请注意,您正在消除直接向派生模板提供其他模板参数;“接口”仍然接收它们。
This also lets you build up typedefs in the "interface" that depend on the type parameters, which will be accessible from the derived template.
这还允许您在依赖于类型参数的“接口”中构建 typedef,这些参数可从派生模板访问。
The above typedef doesn't work because you can't typedef to an unspecified template. This works, however (and C++11 has native support for template typedefs):
上面的 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;
You need one derived_interface_type for each instantiation of the derived template unfortunately, unless there's another trick I haven't learned yet.
不幸的是,派生模板的每个实例化都需要一个衍生接口类型,除非我还没有学到另一个技巧。
回答by Cookie
This is what I ran into:
这就是我遇到的:
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)>>>>>>>>
{
};
Can be solved to:
可以解决为:
template<class A>
class B
{
A& a;
};
template< template<class> class B>
class A
{
B<A> b;
};
class AInstance : A<B> //happy
{
};
or (working code):
或(工作代码):
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();
}
回答by Kuberan Naganathan
In the solution with variadic templates provided by pfalcon, I found it difficult to actually specialize the ostream operator for std::map due to the greedy nature of the variadic specialization. Here's a slight revision which worked for me:
在 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;
}
回答by imallett
Here's one generalized from something I just used. I'm posting it since it's a verysimple example, and it demonstrates a practical use case along with default arguments:
这是我刚刚使用的东西的概括。我发布它是因为它是一个非常简单的示例,它演示了一个实际用例以及默认参数:
#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;
};
回答by colin
It improves readability of your code, provides extra type safety and save some compiler efforts.
它提高了代码的可读性,提供了额外的类型安全性并节省了一些编译器的工作量。
Say you want to print each element of a container, you can use the following code without template template parameter
假设你想打印一个容器的每个元素,你可以使用下面没有模板模板参数的代码
template <typename T> void print_container(const T& c)
{
for (const auto& v : c)
{
std::cout << v << ' ';
}
std::cout << '\n';
}
or with template template parameter
或使用模板模板参数
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';
}
Assume you pass in an integer say print_container(3)
. For the former case, the template will be instantiated by the compiler which will complain about the usage of c
in the for loop, the latter will not instantiate the template at all as no matching type can be found.
假设您传入一个整数 say print_container(3)
。对于前一种情况,模板将由编译器实例化,编译器会抱怨c
for 循环的使用,后者根本不会实例化模板,因为找不到匹配的类型。
Generally speaking, if your template class/function is designed to handle template class as template parameter, it is better to make it clear.
一般来说,如果你的模板类/函数被设计成将模板类作为模板参数来处理,最好说清楚。
回答by cd127
I use it for versioned types.
我将它用于版本化类型。
If you have a type versioned through a template such as MyType<version>
, you can write a function in which you can capture the version number:
如果您有一个通过模板版本化的类型,例如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)
{
...
}
}
So you can do different things depending on the version of the type being passed in instead of having an overload for each type.
You can also have conversion functions which take in MyType<Version>
and return MyType<Version+1>
, in a generic way, and even recurse them to have a ToNewest()
function which returns the latest version of a type from any older version (very useful for logs that might have been stored a while back but need to be processed with today's newest tool).
所以你可以根据传入的类型的版本做不同的事情,而不是为每种类型都重载。您还可以拥有以通用方式接收MyType<Version>
和返回的转换函数MyType<Version+1>
,甚至递归它们以获得一个ToNewest()
函数,该函数从任何旧版本返回最新版本的类型(对于可能已存储一段时间的日志非常有用但需要使用当今最新的工具进行处理)。