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.