37

Why do std::stack and std::queue use type template parameter instead of template template parameter for their underlying container type?

i.e. why is stack declared like this:

template<typename T, typename Container = deque<T>>
class stack;

but not like this:

template<typename T, template<typename> class Container = deque>
class stack;

?

Vincent Savard
  • 34,979
  • 10
  • 68
  • 73
Hedede
  • 1,133
  • 13
  • 27
  • @CoryKramer this question is not a duplicate of the linked question: The linked question asks about where to we need to put template/typename, while this question is about why an existing class (`deque`) has a certain interface, not about a syntactic question. – bennofs Aug 02 '16 at 13:25
  • 1
    Do defaults even work for template template parameters? – anatolyg Aug 02 '16 at 13:36
  • 1
    @anatolyg Yes, they work. – Konrad Rudolph Aug 02 '16 at 13:40

4 Answers4

38

Because typically containers like std::vector have more than one template argument. By not caring about it being a template, you allow every kind of container to be used.

How would

template<class T, class Allocator = std::allocator<T>> class vector;

fit onto

template<typename> class Container

as you would have it in your stack? (Hint: it doesn't!) You'd need special cases for each number and kind of template arguments (type vs. non-type) you'd want to support, which is silly, because these typically don't contribute any more information than a simple

typename Container

Note that to get at the actual template arguments of e.g. a std::vector, you have the typedefs std::vector::value_type and std::vector::allocator_type, removing the need of having these types available explicitly where you actually use the type (i.e. the Container of stack).

rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • 1
    Ouch. I thought it would be more convenient to be able to write `std::stack` without specifying T twice, but didn't think about restrictions on container template parameters. – Hedede Aug 02 '16 at 13:59
  • 17
    @Hedede Think also about containers which aren't even instantiated from templates. With your design you couldn't do something like `std::stack`. – TartanLlama Aug 02 '16 at 14:09
  • 2
    It's far worse than "special cases of each _number_ of template aguments", because the template template parameter might have a mix of type and non-type template arguments. I.e. not only could you have `Container` but also `Container`. That makes it _exponentially_ worse. – MSalters Aug 03 '16 at 11:47
20

In short: Because using a template template parameter is more restrictive* than using a type parameter without providing any advantages.

* By restrictive I mean that you may need a more complex stuff to obtain the same results than with a "simple" type parameter.

Why is there no advantages?

Your std::stack probably has an attribute like this:

template <typename T, typename Container>
struct stack {
    Container container;
};

If you replace Container, by a template template parameter, why would you obtain?

template <typename T, template <typename...> class Container>
struct stack {
    Container<T> container;
};

You are instantiating Container only once and only for T (Container<T>), so there is no advantages for a template template parameter.

Why is it more restrictive?

With a template template parameter, you have to pass to std::stack a template which expose the same signature, e.g.:

template <typename T, template <typename> class Container>
struct stack;

stack<int, std::vector> // Error: std::vector takes two template arguments

Maybe you could use variadic templates:

template <typename T, template <typename... > class Container>
struct stack {
    Container<T> container;
};

stack<int, std::vector> // Ok, will use std::vector<int, std::allocator<int>>

But what if I do not want to use the standard std::allocator<int>?

template <typename T, 
          template <typename....> class Container = std::vector, 
          typename Allocator = std::allocator<T>>
struct stack {
    Container<T, Allocator> container;
};

stack<int, std::vector, MyAllocator> // Ok...

This is becoming a bit messy... What if I want to use my own container templates that takes 3/4/N parameters?

template <typename T,
          template <typename... > class Container = std::vector,
          typename... Args>
struct stack {
    Container<T, Args...> container;
};

stack<int, MyTemplate, MyParam1, MyParam2> // Ok...

But, what if I want to use a non-templated containers?

struct foo { };
struct foo_container{ };

stack<foo, foo_container> // Error!

template <typename... >
using foo_container_template = foo_container;

stack<foo, foo_container_template> // Ok...

With a type parameter there are no such issues1:

stack<int>
stack<int, std::vector<int, MyAllocator<int>>
stack<int, MyTemplate<int, MyParam1, MyParam2>>
stack<foo, foo_container>

1 There are other cases which do not work with template template parameter such as using templates accepting a mix of type and non-type parameters in specific orders, for which you can create generic template template parameter, even using variadic templates.

Holt
  • 36,600
  • 7
  • 92
  • 139
  • I don’t think this is the correct answer at all: see the other answers for a correct reason. – Konrad Rudolph Aug 02 '16 at 13:38
  • @KonradRudolph In c++11, you could use `template class Container` and it would work with a template template parameter. – Holt Aug 02 '16 at 13:40
  • @Holt True, fair enough. That said, the interface of course predates C++11, and the *reason* for the interface design is first and foremost the technical restriction of pre-11 C++. – Konrad Rudolph Aug 02 '16 at 13:42
  • @Holt But if you use a variadic template template, you still need to get the arguments somehow, so you'd then need variadic parameters on `std::stack` to fill in the arguments to the container. – TartanLlama Aug 02 '16 at 13:44
  • @TartanLlama Not if the arguments are defaulted in the given template. – Holt Aug 02 '16 at 13:45
  • @Holt what if your container takes no template arguments? – nate Aug 02 '16 at 14:17
  • @nate Like `foo_container` at the end of my answer? – Holt Aug 02 '16 at 14:18
  • @Hold lol, didn't see that, just saw argument for variadic template arguments in these comments. – nate Aug 02 '16 at 14:20
  • I suppose a follow-up question I have is: "in that case, what's a good way to enforce that the chosen container type actually stores values of type `T`?", seeing as this is one thing that would be guaranteed if the `template template` approach were used... I guess C++20 concepts fit this bill..? One could have a `Storey` concept for types that store `T`s... – saxbophone Oct 23 '22 at 16:39
18

Using a template template parameter would restrict the types that you could use as the underlying container to those that expose the same template signature. This form allows arbitrary types as long as they support the expected interface.

nate
  • 1,771
  • 12
  • 17
13

Because it doesn’t compile:

std::deque isn’t of type

template <typename T> class std::deque

it’s of type

template<class T, class Alloc> class std::deque

This of course is a more general problem: even if we were to provide the Alloc template parameter to our stack class template, the class would now only work with containers that have exactly two type template arguments. This is an unreasonable restriction.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • @Holt here had a nice answer about type-parameter allowing to use non-template containers like `struct foo; struct foo_container; using foo_stack = std::stack`. But when talking about modern standards, would `template` allow the use of such containers? i.e., can the variadic template "reduce" to a simple class with no template parameters? – Artalus Aug 02 '16 at 13:57
  • @Artalus Good question. No, it can’t. His answer’s back up, by the way. – Konrad Rudolph Aug 02 '16 at 14:04