开发者

Template template parameters

开发者 https://www.devze.com 2023-03-15 06:42 出处:网络
It seems understanding template template paramet开发者_StackOverflow中文版ers will kill me :(, Let me explain what misconception I made in my mind which confuses me:

It seems understanding template template paramet开发者_StackOverflow中文版ers will kill me :(, Let me explain what misconception I made in my mind which confuses me:

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

Here is other code:

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

Note the line in the parameter list of templated class BB, which is:

template<class X> class Z = B

Now, what stops C++ to think that Z is not another templated class Z?

I.e.,

template<class X> class Z {
}

rather than thinking class Z is a templated parameter itself.


Mankarse has answered your question, but I thought I'd chime in anyway.

Template template parameters are just like normal template type parameters, except that they match templates instead of concrete types:

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

If it helps, you can kind of think of them as like function pointers. Normal functions just accept arguments like normal templates just accept types. However, some functions accept function pointers which accept arguments, just like template template types accept templates that accept types:

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

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

To answer your question in the comments: template template template parameters are not possible. However, the reason they are not possible is just because the standardisation committee decided that template templates were enough, probably to make lives easier for the compiler implementors. That being said, there's nothing stopping the committee from deciding that they are possible, then things like this would be valid 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;

Again, you can see parallels in function pointers.

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

The analogous function to Baz would be:

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

Where would you use a template template template?

It's pretty far-fetched but I can think of one example: a really generic graph searching library.

Two common algorithms in graph searching are the depth-first search (DFS) and the breadth-first search (BFS). The implementation of the two algorithms is identical except in one regard: DFS uses a stack of nodes whereas BFS uses a queue. Ideally, we'd just write the algorithm once, with the stack/queue as an argument. Also, we'd want to specify the implementation container of the stack or queue, so that we could do something like:

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

But what is a Stack or a Queue? Well, just like in the STL a stack or a queue can be implemented with any kind of container: vectors, deques, lists etc. and could also be stacks of any element type, so our stacks or queues would have the interface:

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

But Vector and Deque themselves are template types!

So finally, our Stack would be a template template like:

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

And our search algorithm would then have to be a template template template!

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
}

That would be pretty intense, but hopefully you get the idea.

Remember: template template templates are not legal C++, so this whole graph search thing won't actually compile. It's just a "what if?" :)


This is part of the syntax of the language (which is monstrous and massively context-dependent). If template<class X> class Z occurs in a template-parameter-list then it is interpreted as declaration of a formal parameter Z with the kind (like a meta-type; kinds classify types in the same way types classify values) "template class taking one class argument".


The usage examples in the accepted answer are misleading,

especially for beginners. Granted it's hard to come up with anything that won't be contrived, but we should at least contrive something that doesn't contradict the overall principles. Template parameters should be used only when the user of our interface can't specify the type of the template for one or the other reason, and we need to do it for them. In the Stack example we ask for both Storage and Element, only to instantiate Storage with that very Element, which is entirely unnecessary, the user can easily perform a basic substitution:

Stack<deque<int>> my_stack;

And all the stack needs to do is this:

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

It doesn't in any way decide for the user what the element type is, so it does not need the template parameter. Hence the search becomes

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
}

Here I guess we assume that internal Graph::Node type is not accessible to the user, and search is somehow a friend function of the Graph, which seems to make some sense. However, do we actually need to fill the structure with graph nodes, or simply references to them? Can the user not refer to the nodes in any way? If not, why is it called a graph, and not, say, slow_unordered_set? So lets imagine for a second they have an access to some node reference/pointer type, then they can do this:

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

The function simplifies further to this:

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

Gosh darn it, now it's more generic than ever! Do you want to specify an allocator for the storage? No problem, just do it. You instead wanted some statically allocated vector that requires maximum size parameter? Go right ahead. Want to implement the stack from scratch altogether? Well, as long as it quacks like a stack...

Perhaps a more appropriate example

of a template with template parameters would be some class that represents a complex system and uses some Storage template for a bunch of internal structures, and for some reason is parameterized on that Storage template:

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

If you ask me - this code reeks, but it's not like I wouldn't write it.

Now that we have this semi-appropriate example for a template with a template parameter, we can contrive something for a template with a template parameter that itself has a template parameter: Imagine somehow we end up with like 10 of these System classes that all have the same interface, all parameterized on a Storage template, but otherwise very VERY different. Brace yourselves for the SuperSystem, an even more complicated class, that uses a bunch of our systems, but CRUCIALLY needs to decide itself what Storage templates to use with each system.

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

We want to specify something down the template hierarchy we're dealing with here, but still leave something up the hierarchy customizable. For some reason we don't know what exact system we will be dealing with, but we know something very specific about all of them, that we absolutely need to go our way. This is an overarching theme with these examples, our goal is not to make things more generic and customizable, but the opposite - we want to lock down certain deeply embedded things.

TL;DR

In my experience you would only encounter good use cases for templates with template parameters when knee deep in a meta programming library. A rule of thumb: if you can recognize this pattern

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

as a type function, then in that mindset you are allowed to use templates with template parameters, and maybe ponder about anything deeper. Otherwise slap yourself on the wrist.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号