模板模板参数

时间:2011-06-26 14:14:01

标签: c++ templates

似乎理解模板模板param会杀了我:(,lemme解释了我在脑海中产生的误解让我困惑:

template<class T>
class B {}; // A templated class

这是另一个代码:

template<template<class X> class Z = B> // problem is in this line for me
class BB{}; 

注意模板化类BB的参数列表中的行,即:

template<class X> class Z = B

现在我想问的是什么阻止c ++认为Z不是另一个模板化的类Z,即:

template<class X> class Z{
}

而不是认为Z类是模板参数本身。

非常感谢,我非常感谢能帮助我解除这种误解。

3 个答案:

答案 0 :(得分:73)

Mankarse回答了你的问题,但我认为无论如何我都会发出声音。

模板模板参数与普通模板类型参数一样,只是它们匹配模板而不是具体类型:

// Simple template class
template <typename Type>
class Foo
{
    Type m_member;
};

// Template template class
template <template <typename Type> class TemplateType>
class Bar
{
    TemplateType<int> m_ints;
};

如果有帮助,你可以将它们视为函数指针。普通函数只接受普通模板之类的参数,只接受类型。但是,有些函数接受接受参数的函数指针,就像模板模板类型接受接受类型的模板一样:

void foo(int x)
{
    cout << x << endl;
}

void bar(void (*f)(int))
{
    f(1);
    f(2);
}

要在评论中回答您的问题:模板模板模板参数是不可能的。然而,它们不可能的原因仅仅是因为标准化委员会认为模板模板足够,可能会使编译器实现者的生活更轻松。话虽如此,没有什么可以阻止委员会决定它们是否有可能,那么这样的事情将是有效的C ++:

template <template <template <typename> class> class TemplateTemplateType>
class Baz
{
    TemplateTemplateType<Foo> m_foos;
};

typedef Baz<Bar> Example;
// Example would then have Bar<Foo> m_foos;
// which would have Foo<int> m_ints;

同样,你可以在函数指针中看到相似之处。

                      types <=> values
                  templates <=> functions of values
         template templates <=> functions of functions of values
template template templates <=> functions of functions of functions of values

Baz的类似功能是:

void baz(void (*g)(void (*f)(int)))
{
    g(foo);
}

您在哪里使用模板模板模板?

这是相当牵强的,但我可以想到一个例子:一个非常通用的图搜索库。

图搜索中的两种常见算法是深度优先搜索(DFS)和广度优先搜索(BFS)。除了一个方面,两种算法的实现是相同的:DFS使用一堆节点,而BFS使用一个队列。理想情况下,我们只需编写一次算法,将堆栈/队列作为参数。此外,我们还想指定堆栈或队列的实现容器,以便我们可以执行以下操作:

search<Stack, Vector>( myGraph ); // DFS
search<Queue, Deque>( myGraph ); // BFS

但什么是堆栈或队列?好吧,就像在STL中一样,堆栈或队列可以用任何类型的容器实现:向量,deques,list等,也可以是任何元素类型的堆栈,所以我们的堆栈或队列将具有接口:

Stack<Vector, int> // stack of ints, using a vector implementation
Queue<Deque, bool> // queue of bools, using a deque implementation

VectorDeque本身就是模板类型!

最后,我们的Stack将是一个模板模板,如:

template <template <typename> class Storage, typename Element>
struct Stack
{
    void push(const Element& e) { m_storage.push_back(e); }
    void pop() { m_storage.pop_back(); }
    Storage<Element> m_storage;
};

然后我们的search算法必须是模板模板模板!

template <template <template <typename> class, typename> class DataStructure,
          template <typename> class Storage,
          typename Graph>
void search(const Graph& g)
{
    DataStructure<Storage, typename Graph::Node> data;
    // do algorithm
}

这将非常激烈,但希望你能得到这个想法。

请记住:模板模板模板不是合法的C ++,所以这整个图形搜索的东西实际上都不会编译。这只是一个“假如何?” :)

答案 1 :(得分:16)

这是该语言语法的一部分(它是怪异的,并且大量依赖于上下文)。如果template<class X> class Z出现在template-parameter-list中,那么它将被解释为具有类型的形式参数Z的声明(如元类型;种类以相同方式对类别进行分类,类别) “模板类采用一个类参数”。

答案 2 :(得分:0)

已接受答案中的用法示例具有误导性,

特别适合初学者。诚然,很难想出任何不会被人为设计的东西,但我们至少应该想出一些与总体原则不矛盾的东西。只有当我们界面的用户由于某种原因无法指定模板的类型时,才应该使用模板参数,而我们需要为他们指定模板参数。在 Stack 示例中,我们同时要求 Storage 和 Element,仅使用该 Element 实例化 Storage,这是完全不必要的,用户可以轻松执行基本替换:

Stack<deque<int>> my_stack;

所有堆栈需要做的是:

template <template class Storage>
struct Stack
{
    void push(typename Storage::const_reference e) { m_storage.push_back(e); }
    void pop() { m_storage.pop_back(); }
    Storage m_storage;
    typename Storage::reference top() { return m_storage.back(); }
};

它不会以任何方式为用户决定元素类型是什么,因此它不需要模板参数。因此搜索变为

template <template <typename> class DataStructure,
      template <typename> class Storage,
      typename Graph>
void search(const Graph& g, typename Graph::const_reference)
{
    DataStructure<Storage<typename Graph::Node>> data;
    // do algorithm
}

这里我想我们假设用户无法访问内部的 Graph::Node 类型,并且搜索在某种程度上是 Graph 的友元函数,这似乎是有道理的。然而,我们真的需要用图节点填充结构,还是简单地引用它们?用户不能以任何方式引用节点吗?如果不是,为什么它被称为图,而不是,比如说,slow_unordered_set?所以让我们想象一下他们可以访问某个节点引用/指针类型,然后他们可以这样做:

search<Stack<vector<Graph::node_ptr>>>(graph, 10);

该函数进一步简化为:

template <template StackStructure, typename Graph>
void search(const Graph& g, typename Graph::const_reference)
{
    StackStructure data;
    // do algorithm
}

该死的,现在它比以往任何时候都更通用!你想为存储指定一个分配器吗?没问题,就去做吧。相反,您想要一些需要最大大小参数的静态分配向量?往前走。想要从头开始实现堆栈吗?好吧,只要它像堆栈一样嘎嘎作响......

也许是一个更合适的例子

具有模板参数的模板将是一些表示复杂系统的类,并使用一些存储模板来处理一堆内部结构,并且出于某种原因在该存储模板上参数化:

template <template <typename> class Storage>
class System
{
    Storage<Component_1> components_1;
    Storage<Component_2> components_2;
    Storage<Component_3> components_3;        
    Storage<MetaInfo> registry;
    public:
    // some inane interface
};

如果你问我 - 这段代码很臭,但并不是我不会写。

现在我们有了这个带有模板参数的模板的半合适示例,我们可以为带有模板参数的模板设计一些东西,而模板参数本身有一个模板参数:想象一下,我们最终得到了大约 10 个这样的 System 类它们都具有相同的界面,都在存储模板上参数化,但在其他方面却非常不同。为 SuperSystem 做好准备,这是一个更复杂的类,它使用我们的一堆系统,但至关重要的是需要自行决定每个系统使用哪些存储模板。

template< template< template <typename> class Storage> class System>
class SuperSystem
{
    System<Vector> system_1;
    System<OtherVector> system_2;themselves
    System<List> system_3;
    public:
    // absolutely bonkers interface
};

我们想在我们在这里处理的模板层次结构中指定一些东西,但仍然保留一些可自定义的层次结构。出于某种原因,我们不知道我们将要处理的确切系统是什么,但我们知道所有这些系统的一些非常具体的东西,我们绝对需要按照自己的方式行事。这是这些示例的总体主题,我们的目标不是让事情变得更加通用和可定制,而是相反 - 我们想要锁定某些深层嵌入的东西。

TL;DR

根据我的经验,当您深入元编程库时,您只会遇到带有模板参数的模板的良好用例。经验法则:如果你能识别这种模式

template<...> struct f { typedef ... type; };

作为类型函数,那么在这种心态下,您可以使用带有模板参数的模板,并且可以考虑更深入的事情。否则就拍自己的手腕。