模板模板参数有哪些用途?

Fer*_*cio 221 c++ templates template-templates

我已经看到了一些使用模板模板参数(即模板作为参数的模板)来进行基于策略的类设计的C++示例.这种技术有什么其他用途?

Eva*_*ran 186

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

template <template<class> class H, class S>
void f(const H<S> &value) {
}
Run Code Online (Sandbox Code Playgroud)

H是一个模板,但我希望这个函数能够处理所有的特化H.

注意:我已经编程了很多年c ++,只需要一次.我发现这是一个很少需要的功能(当你需要它时当然很方便!).

我一直试图想出好的例子,说实话,大部分时间这都不是必要的,但让我们设想一个例子.让我们假设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;
}
Run Code Online (Sandbox Code Playgroud)

注意:我们std::vector有两个模板参数,类型和分配器,所以我们必须接受它们.幸运的是,由于类型推导,我们不需要明确地写出确切的类型.

您可以这样使用:

f<std::vector, int>(v); // v is of type std::vector<int> using any allocator
Run Code Online (Sandbox Code Playgroud)

或者更好,我们可以使用:

f(v); // everything is deduced, f can deal with a vector of any type!
Run Code Online (Sandbox Code Playgroud)

更新:即使是这个人为的例子,虽然是说明性的,但由于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;
}
Run Code Online (Sandbox Code Playgroud)

这是我更喜欢写这种类型的代码.

  • @bobobobo这两个意味着不同的东西.`f <vector,int>`表示`f <ATemplate,AType>`,`f <vector <int >>`表示`f <AType>` (2认同)

pfa*_*con 155

实际上,模板模板参数的用例非常明显.一旦你了解到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;
}
Run Code Online (Sandbox Code Playgroud)

然后你会发现vector的代码是一样的,因为forward_list是相同的,实际上,即使对于多种地图类型它也仍然是相同的.除了元接口/协议之外,这些模板类没有任何共同点,并且使用模板模板参数可以捕获所有模板类的共性.在继续编写模板之前,有必要检查引用以回忆序列容器接受2个模板参数 - 对于值类型和分配器.虽然分配器是默认的,但我们仍然应该在模板运算符<<中考虑它的存在:

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

Voila,将自动为所有符合标准协议的现有和未来序列容器工作.要添加地图到混合中,需要查看参考,注意它们接受4个模板参数,因此我们需要另一个版本的运算符<< above with 4-arg template template param.我们还会看到std:pair尝试使用2-arg operator <<呈现我们之前定义的序列类型,因此我们将为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;
}
Run Code Online (Sandbox Code Playgroud)

产量

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 
Run Code Online (Sandbox Code Playgroud)

  • 这里的模板模板参数实际上并没有添加任何值.您也可以使用常规模板参数作为类模板的任何给定实例. (19认同)
  • +1,使用可变参数模板添加了C++ 11示例. (15认同)
  • 这是模板模板参数的一个很好的例子,因为它显示了每个人都必须处理的情况. (8认同)
  • 我不得不同意大卫斯通的观点.这里没有指向模板模板参数.制作普通模板(模板<typename Container>)会更简单,同样有效.我知道这篇文章很老了,所以我只为那些偶然发现这个答案的人寻找有关模板模板的信息而增加了2美分. (8认同)
  • 这是我在C++模板中最觉醒的答案.@WhozCraig你是如何获得模板扩展细节的? (3认同)
  • @Arun gcc支持一个名为`__PRETTY_FUNCTION__`的宏,除其他外,它以纯文本形式报告模板参数描述.clang也是这样做的.有时是最方便的功能(如您所见). (3认同)
  • 最后一个版本是否与标准操作符`operator &lt;&lt;(ostream,string)`相冲突?我尝试了它是因为它看起来很棒,但是我无法使它起作用:'operator &lt;&lt;'的模棱两可的重载(操作数类型为'std :: basic_ostream &lt;char&gt;'和'std :: string {aka std :: basic_string &lt;char&gt;}')` (2认同)

yoa*_*ram 64

以下是Andrei Alexandrescu 撰写的"现代C++设计 - 应用的通用编程和设计模式"的简单示例:

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

// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
   ...
};
Run Code Online (Sandbox Code Playgroud)

他解释说: 通常,主机类已经知道或者很容易推断出策略类的模板参数.在上面的示例中,WidgetManager始终管理Widget类型的对象,因此要求用户在CreationPolicy实例化中再次指定Widget是多余的并且可能存在危险.在这种情况下,库代码可以使用模板模板参数来指定策略.

结果是客户端代码可以以更优雅的方式使用"WidgetManager":

typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;
Run Code Online (Sandbox Code Playgroud)

而不是更缺乏模板模板参数的定义需要的更加繁琐且容易出错的方式:

typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;
Run Code Online (Sandbox Code Playgroud)

  • 该问题特别要求提供政策模式以外的示例。 (2认同)

Mik*_*nko 18

这是我的CUDA卷积神经网络库的另一个实际例子.我有以下类模板:

template <class T> class Tensor
Run Code Online (Sandbox Code Playgroud)

这实际上是实现了n维矩阵操作.还有一个子类模板:

template <class T> class TensorGPU : public Tensor<T>
Run Code Online (Sandbox Code Playgroud)

它实现了相同的功能,但在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;
}
Run Code Online (Sandbox Code Playgroud)

这里有模板模板语法的原因是因为我可以声明该类的实现

class CLayerCuda: public CLayerT<TensorGPU, float>
Run Code Online (Sandbox Code Playgroud)

它将具有float和GPU类型的权重和输入,但connection_matrix将始终为int,在CPU上(通过指定TT = Tensor)或在GPU上(通过指定TT = TensorGPU).


Mar*_*nna 11

假设您正在使用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;
Run Code Online (Sandbox Code Playgroud)

请注意'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;
Run Code Online (Sandbox Code Playgroud)

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

这也允许您在依赖于类型参数的"接口"中构建typedef,这些参数可以从派生模板访问.

上面的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;
Run Code Online (Sandbox Code Playgroud)

遗憾的是,对于派生模板的每个实例化,你需要一个derived_interface_type,除非我还没有学到另一个技巧.


Coo*_*kie 7

这是我遇到的:

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)>>>>>>>>
{

};
Run Code Online (Sandbox Code Playgroud)

可以解决:

template<class A>
class B
{
  A& a;
};

template< template<class> class B>
class A
{
  B<A> b;
};

class AInstance : A<B> //happy
{

};
Run Code Online (Sandbox Code Playgroud)

或(工作代码):

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();
}
Run Code Online (Sandbox Code Playgroud)


ima*_*ett 5

这是我刚刚使用的东西的概括。我发布它是因为它是一个非常简单的示例,它演示了一个实际用例以及默认参数:

#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;
};
Run Code Online (Sandbox Code Playgroud)