c++ 模板模板参数

oknrviil  于 2023-01-06  发布在  其他
关注(0)|答案(3)|浏览(231)

看来理解模板模板参数会杀了我:(,让我解释什么误解我在我的脑海中,使我困惑:

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

下面是其他代码:

template<template<class X> class Z = B> // The 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本身是模板化参数。

avkwfej4

avkwfej41#

曼卡斯已经回答了你的问题,但我想我还是要插一句。
模板template参数就像普通的模板类型参数一样,不同之处在于它们匹配的是模板而不是具体类型:

// 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);
}

在评论中回答您的问题:template模板参数是不可能的。然而,它们不可能的原因仅仅是因为标准化委员会认为模板模板就足够了,可能是为了让编译器实现者的生活更轻松。也就是说,没有什么可以阻止委员会决定它们是可能的,那么像这样的东西将是有效的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中一样,栈或队列可以用任何类型的容器来实现:向量、deque、列表等,并且还可以是任何元素类型的栈,因此我们的栈或队列将具有接口:

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++,所以整个图搜索的东西实际上不会编译。它只是一个“如果?”:)

kgqe7b3p

kgqe7b3p2#

这是该语言语法的一部分(它是畸形的并且大量依赖于上下文),如果template<class X> class Z出现在模板参数列表中,那么它被解释为声明了一个形式参数Z,其类型为(类似于元类型;种类以与类型分类值相同的方式分类类型)“采用一个类参数的模板类”。

92dk7w1h

92dk7w1h3#

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

特别是对于初学者。诚然,很难想出任何不需要人为设计的东西,但我们至少应该设计一些不与总体原则相矛盾的东西。模板参数应该只在我们界面的用户出于某种原因不能指定模板类型,而我们需要为他们指定模板类型时使用。在Stack示例中,我们要求同时使用Storage和Element,仅用该元素示例化存储,这是完全不必要的,用户可以容易地执行基本替换:

Stack<deque<int>> my_stack;

堆栈需要做的就是:

template <typename 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类型,并且search是Graph的一个朋友函数,这似乎有点道理。然而,我们真的需要用图节点填充结构吗,或者只是引用它们?用户可以不以任何方式引用节点吗?如果不可以,为什么它被称为图,而不是slow_unordered_set?所以让我们想象一下,他们可以访问一些节点引用/指针类型,然后他们可以这样做:

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

该函数进一步简化为:

template <typename 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个这样的系统类,它们都有相同的接口,都在存储模板上参数化,但在其他方面却非常非常不同。请为超级系统做好准备,超级系统是一个更复杂的类,它使用了我们的一系列系统,但关键是需要自己决定每个系统使用什么存储模板。

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

我们希望在这里处理的模板层次结构中指定一些内容,但仍保留一些可定制的内容。由于某些原因,我们不知道我们将处理的确切系统,但我们知道关于所有系统的一些非常具体的信息,我们绝对需要按照我们的方式进行。这是这些示例的一个总体主题,我们的目标不是使内容更加通用和可定制。但恰恰相反--我们希望锁定某些根深蒂固的东西。

TL; DR

根据我的经验,只有在深入研究元编程库时,您才会遇到带模板参数的模板的良好用例。如果你能认出这个模式

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

作为一个类型的函数,那么在这种心态下,你可以使用带有模板参数的模板,也许还可以考虑更深层次的东西。2否则,你会给自己一记耳光。

相关问题