Similar to functions, classes can also be parameterized with one or more types. Container classes, which are used to manage elements of a certain type, are a typical example of this feature. By using class templates, you can implement such container classes while the element type is still open. In this chapter we use a stack as an example of a class template.
As we did with function templates, we declare and define class Stack<>
in a header file as follows:
basics/stack1.hpp
#include <vector>
#include <cassert>
template<typename T>
class Stack {
private:
std::vector<T> elems; // elements
public:
void push(T const& elem); // push element
void pop(); // pop element
T const& top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
template<typename T>
void Stack<T>::push (T const& elem)
{
elems.push_back(elem); // append copy of passed elem}
template<typename T>
void Stack<T>::pop ()
{
assert(!elems.empty());
elems.pop_back(); // remove last element
}
template<typename T>
T const& Stack<T>::top () const
{
assert(!elems.empty());
return elems.back(); // return copy of last element
}
As you can see, the class template is implemented by using a class template of the C++ standard library: vector<>
. As a result, we don’t have to implement memory management, copy constructor, and assignment operator, so we can concentrate on the interface of this class template.
Declaring class templates is similar to declaring function templates: Before the declaration, you have to declare one or multiple identifiers as a type parameter(s). Again, T
is usually used as an identifier:
template<typename T>
class Stack {
…
};
Here again, the keyword class
can be used instead of typename
:
template<class T>
class Stack {
…
};
Inside the class template, T
can be used just like any other type to declare members and member functions. In this example, T
is used to declare the type of the elements as vector of T
s, to declare push()
as a member function that uses a T
as an argument, and to declare top()
as a function that returns a T
:
template<
typename T>
class Stack {
private:
std::vector<T> elems; // elements
public:
void push(T const& elem); // push element
void pop(); // pop element
T const& top()
const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
The type of this class is Stack<T>
, with T
being a template parameter. Thus, you have to use Stack<T>
whenever you use the type of this class in a declaration except in cases where the template arguments can be deduced. However, inside a class template using the class name not followed by template arguments represents the class with its template parameters as its arguments (see Section 13.2.3 on page 221 for details).
If, for example, you have to declare your own copy constructor and assignment operator, it typically looks like this:
template<typename T>
class Stack {
…
Stack (Stack const&); // copy constructor
Stack& operator= (Stack const&); // assignment operator
…
};
which is formally equivalent to:
template<typename T>
class Stack {
…
Stack (Stack<T> const&); // copy constructor
Stack<T>& operator= (Stack<T> const&); // assignment operator
…
};
but usually the <T>
signals special handling of special template parameters, so it’s usually better to use the first form.
However, outside the class structure you’d need:
template<typename T>
bool operator== (Stack<T> const& lhs, Stack<T> const& rhs);
Note that in places where the name and not the type of the class is required, only Stack
may be used. This is especially the case when you specify the name of constructors (not their arguments) and the destructor.
Note also that, unlike nontemplate classes, you can’t declare or define class templates inside functions or block scope. In general, templates can only be defined in global/namespace scope or inside class declarations (see Section 12.1 on page 177 for details).
To define a member function of a class template, you have to specify that it is a template, and you have to use the full type qualification of the class template. Thus, the implementation of the member function push()
for type Stack<T>
looks like this:
template<typename T>
void Stack<T>::push (T const& elem)
{
elems.push_back(elem); // append copy of passed elem
}
In this case, push_back()
of the element vector is called, which appends the element at the end of the vector.
Note that pop_back()
of a vector removes the last element but doesn’t return it. The reason for this behavior is exception safety. It is impossible to implement a completely exception-safe version of pop()
that returns the removed element (this topic was first discussed by Tom Cargill in [CargillExceptionSafety] and is discussed as Item 10 in [SutterExceptional]). However, ignoring this danger, we could implement a pop()
that returns the element just removed. To do this, we simply use T
to declare a local variable of the element type:
template<typename T>
T Stack<T>::pop ()
{
assert(!elems.empty());
T elem = elems.back(); // save copy of last element
elems.pop_back(); // remove last element
return elem; // return copy of saved element
}
Because back()
(which returns the last element) and pop_back()
(which removes the last element) have undefined behavior when there is no element in the vector, we decided to check whether the stack is empty. If it is empty, we assert, because it is a usage error to call pop()
on an empty stack. This is also done in top()
, which returns but does not remove the top element, on attempts to remove a nonexistent top element:
template<typename T>
T const& Stack<T>::top () const
{
assert(!elems.empty());
return elems.back(); // return copy of last element
}
Of course, as for any member function, you can also implement member functions of class templates as an inline function inside the class declaration. For example:
template<typename T>
class Stack {
…
void push (T const& elem) {
elems.push_back(elem); // append copy of passed elem
}
…
};
To use an object of a class template, until C++17 you must always specify the template arguments explicitly.1 The following example shows how to use the class template Stack<>
:
basics/stack1test.cpp
#include "stack1.hpp"
#include <iostream>
#include <string>
int main()
{
Stack< int> intStack; // stack of ints
Stack<std::string> stringStack; // stack of strings
// manipulate int stack
intStack.push(7);
std::cout << intStack.top() << ’\n’;
// manipulate string stack
stringStack.push("hello");
std::cout << stringStack.top() << ’\n’;
stringStack.pop();
}
By declaring type Stack<int>
, int
is used as type T
inside the class template. Thus, intStack
is created as an object that uses a vector of int
s as elements and, for all member functions that are called, code for this type is instantiated. Similarly, by declaring and using Stack<std::string>
, an object that uses a vector of strings as elements is created, and for all member functions that are called, code for this type is instantiated.
Note that code is instantiated only for template (member) functions that are called. For class templates, member functions are instantiated only if they are used. This, of course, saves time and space and allows use of class templates only partially, which we will discuss in Section 2.3 on page 29.
In this example, the default constructor, push()
, and top()
are instantiated for both int
and strings. However, pop()
is instantiated only for strings. If a class template has static members, these are also instantiated once for each type for which the class template is used.
An instantiated class template’s type can be used just like any other type. You can qualify it with const
or volatile
or derive array and reference types from it. You can also use it as part of a type definition with typedef
or using
(see Section 2.8 on page 38 for details about type definitions) or use it as a type parameter when building another template type. For example:
void foo(Stack <int> const& s) // parameter s is int stack
{
using IntStack = Stack <int>; // IntStack is another name for Stack<int>
Stack< int> istack[10]; // istack is array of 10 int stacks
IntStack istack2[10]; // istack2 is also an array of 10 int stacks (same type)
…
}
Template arguments may be any type, such as pointers to float
s or even stacks of int
s:
Stack< float*> floatPtrStack; // stack of float pointers
Stack<Stack< int>> intStackStack; // stack of stack of ints
The only requirement is that any operation that is called is possible according to this type.
Note that before C++11 you had to put whitespace between the two closing template brackets:
Stack<Stack< int> > intStackStack; // OK with all C++ versions
If you didn’t do this, you were using operator >>
, which resulted in a syntax error:
Stack<Stack< int>> intStackStack;
// ERROR before C++11
The reason for the old behavior was that it helped the first pass of a C++ compiler to tokenize the source code independent of the semantics of the code. However, because the missing space was a typical bug, which required corresponding error messages, the semantics of the code more and more had to get taken into account anyway. So, with C++11 the rule to put a space between two closing template brackets was removed with the “angle bracket hack” (see Section 13.3.1 on page 226 for details).
A class template usually applies multiple operations on the template arguments it is instantiated for (including construction and destruction). This might lead to the impression that these template arguments have to provide all operations necessary for all member functions of a class template. But this is not the case: Template arguments only have to provide all necessary operations that are needed (instead of that could be needed).
If, for example, class Stack<>
would provide a member function printOn()
to print the whole stack content, which calls operator<<
for each element:
template<typename T>
class Stack {
…
void printOn() (std::ostream& strm) const {
for (T const& elem : elems) {
strm << elem << ’ ’; // call << for each element
}
}
};
You can still use this class for elements that don’t have operator<< defined:
Stack<std::pair< int, int>> ps; // note: std::pair<> has no operator<< defined
ps.push({4, 5}); // OK
ps.push({6, 7}); // OK
std::cout << ps.top().first << ’\n’; // OK
std::cout << ps.top().second << ’\n’; // OK
Only if you call printOn()
for such a stack, the code will produce an error, because it can’t instantiate the call of operator<<
for this specific element type:
ps.printOn(std::cout); // ERROR: operator<< not supported for element type
This raises the question: How do we know which operations are required for a template to be able to get instantiated? The term concept is often used to denote a set of constraints that is repeatedly required in a template library. For example, the C++ standard library relies on such concepts as random access iterator and default constructible.
Currently (i.e., as of C++17), concepts can more or less only be expressed in the documentation (e.g., code comments). This can become a significant problem because failures to follow constraints can lead to terrible error messages (see Section 9.4 on page 143).
For years, there have also been approaches and trials to support the definition and validation of concepts as a language feature. However, up to C++17 no such approach was standardized yet.
Since C++11, you can at least check for some basic constraints by using the static_assert
keyword and some predefined type traits. For example:
template<typename T>
class C
{
static_assert(std::is_default_constructible<T>::value,
"Class C requires default-constructible elements");
…
};
Without this assertion the compilation will still fail, if the default constructor is required. However, the error message then might contain the entire template instantiation history from the initial cause of the instantiation down to the actual template definition in which the error was detected (see Section 9.4 on page 143).
However, more complicated code is necessary to check, for example, objects of type T
provide a specific member function or that they can be compared using operator <
. See Section 19.6.3 on page 436 for a detailed example of such code.
See Appendix E for a detailed discussion of concepts for C++.
Instead of printing the stack contents with printOn()
it is better to implement operator<<
for the stack. However, as usual operator<<
has to be implemented as nonmember function, which then could call printOn()
inline:
template<typename T>
class Stack {
…
void printOn() (std::ostream& strm) const {
…
}
friend std::ostream& operator<< (std::ostream& strm,
Stack<T> const& s) {
s.printOn(strm);
return strm;
}
};
Note that this means that operator<<
for class Stack<>
is not a function template, but an “ordinary” function instantiated with the class template if needed.2
However, when trying to declare the friend function and define it afterwards, things become more complicated. In fact, we have two options:
1. We can implicitly declare a new function template, which must use a different template parameter, such as U
:
template<typename T>
class Stack {
…
template<typename U>
friend std::ostream& operator<< (std::ostream&, Stack<U> const&);
};
Neither using T
again nor skipping the template parameter declaration would work (either the inner T
hides the outer T
or we declare a nontemplate function in namespace scope).
2. We can forward declare the output operator for a Stack<T>
to be a template, which, however, means that we first have to forward declare Stack<T>
:
template<typename T>
class Stack;
template<typename T>
std::ostream& operator<< (std::ostream&, Stack<T> const&);
Then, we can declare this function as friend:
template<typename T>
class Stack {
…
friend std::ostream& operator<< <T> (std::ostream&,
Stack<T> const&);
};
Note the <T> behind the “function name” operator<<. Thus, we declare a specialization of the nonmember function template as friend. Without <T> we would declare a new nontemplate function. See Section 12.5.2 on page 211 for details.
In any case, you can still use this class for elements that don’t have operator<< defined. Only calling operator<< for this stack results in an error:
Stack<std::pair< int, int>> ps; // std::pair<> has no operator<< defined
ps.push({4, 5}); // OK
ps.push({6, 7}); // OK
std::cout <<
ps.top().first << ’\n’; // OK
std::cout <<
ps.top().second << ’\n’; // OK
std::cout << ps << ’\n’; // ERROR: operator<< not supported
// for element type
You can specialize a class template for certain template arguments. Similar to the overloading of function templates (see Section 1.5 on page 15), specializing class templates allows you to optimize implementations for certain types or to fix a misbehavior of certain types for an instantiation of the class template. However, if you specialize a class template, you must also specialize all member functions. Although it is possible to specialize a single member function of a class template, once you have done so, you can no longer specialize the whole class template instance that the specialized member belongs to.
To specialize a class template, you have to declare the class with a leading template<>
and a specification of the types for which the class template is specialized. The types are used as a template argument and must be specified directly following the name of the class:
template<>
class Stack<std::string> {
…
};
For these specializations, any definition of a member function must be defined as an “ordinary” member function, with each occurrence of T
being replaced by the specialized type:
void Stack<std::string>::push (std::string const& elem)
{
elems.push_back(elem); // append copy of passed elem
}
Here is a complete example of a specialization of Stack<>
for type std::string
:
basics/stack2.hpp
#include "stack1.hpp"
#include <deque>
#include <string>
#include <cassert>
template<>
class Stack<std::string> {
private:
std::deque<std::string> elems; // elements
public:
void push(std::string const&); // push element
void pop(); // pop element
std::string const& top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
void Stack<std::string>::push (std::string const& elem)
{
elems.push_back(elem); // append copy of passed elem
}
void Stack<std::string>::pop ()
{
assert(!elems.empty());
elems.pop_back(); // remove last element
}
std::string const& Stack<std::string>::top () const
{
assert(!elems.empty());
return elems.back(); // return copy of last element
}
In this example, the specialization uses reference semantics to pass the string argument to push()
, which makes more sense for this specific type (we should even better pass a forwarding reference, though, which is discussed in Section 6.1 on page 91).
Another difference is to use a deque instead of a vector to manage the elements inside the stack. Although this has no particular benefit here, it does demonstrate that the implementation of a specialization might look very different from the implementation of the primary template.
Class templates can be partially specialized. You can provide special implementations for particular circumstances, but some template parameters must still be defined by the user. For example, we can define a special implementation of class Stack<>
for pointers:
basics/stackpartspec.hpp
#include "stack1.hpp"
// partial specialization of class Stack<> for pointers:
template<typename T>
class Stack<T*> {
private:
std::vector<T*> elems; // elements
public:
void push(T*); // push element
T* pop(); // pop element
T* top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
template<typename T>
void Stack<T*>::push (T* elem)
{
elems.push_back(elem); // append copy of passed elem
}
template<typename T>
T* Stack<T*>::pop ()
{
assert(!elems.empty());
T* p = elems.back();
elems.pop_back(); // remove last element
return p; // and return it (unlike in the general case)
}
template<typename T>
T* Stack<T*>::top () const
{
assert(!elems.empty());
return elems.back(); // return copy of last element
}
With
template<typename T>
class Stack<T*> {
};
we define a class template, still parameterized for T
but specialized for a pointer (Stack<T*>
).
Note again that the specialization might provide a (slightly) different interface. Here, for example, pop()
returns the stored pointer, so that a user of the class template can call delete
for the removed value, when it was created with new
:
Stack< int*> ptrStack; // stack of pointers (special implementation)
ptrStack.push(new int{42});
std::cout << *ptrStack.top() << ’\n’;
delete ptrStack.pop();
Class templates might also specialize the relationship between multiple template parameters. For example, for the following class template:
template<typename T1, typename T2>
class MyClass {
…
};
the following partial specializations are possible:
// partial specialization: both template parameters have same type
template<typename T>
class MyClass<T,T> {
…
};
// partial specialization: second type is int
template<typename T>
class MyClass<T,int> {
…
};
// partial specialization: both template parameters are pointer types
template<typename T1, typename T2>
class MyClass<T1*,T2*> {
…
};
The following examples show which template is used by which declaration:
MyClass< int, float> mif; // uses MyClass<T1,T2>
MyClass< float, float> mff; // uses MyClass<T,T>
MyClass< float, int> mfi; // uses MyClass<T,int>
MyClass< int*, float*> mp; // uses MyClass<T1*,T2*>
If more than one partial specialization matches equally well, the declaration is ambiguous:
MyClass< int, int> m; // ERROR: matches MyClass<T,T>
// and MyClass<T,int>
MyClass< int*, int*> m; // ERROR: matches MyClass<T,T>
// and MyClass<T1*,T2*>
To resolve the second ambiguity, you could provide an additional partial specialization for pointers of the same type:
template<typename T>
class MyClass<T*,T*> {
…
};
For details of partial specialization, see Section 16.4 on page 347.
As for function templates, you can define default values for class template parameters. For example, in class Stack<>
you can define the container that is used to manage the elements as a second template parameter, using std::vector<>
as the default value:
basics/stack3.hpp
#include <vector>
#include <cassert>
template<typename T, typename Cont = std::vector<T>>
class Stack {
private:
Cont elems; // elements
public:
void push(T const& elem); // push element
void pop(); // pop element
T const& top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
template<typename T, typename Cont>
void Stack<T,Cont>::push (T const& elem)
{
elems.push_back(elem); // append copy of passed elem}
template<typename T,
typename Cont>
void Stack<T,Cont>::pop ()
{
assert(!elems.empty());
elems.pop_back(); // remove last element
}
template<typename T, typename Cont>
T const& Stack<T,Cont>::top () const
{
assert(!elems.empty());
return elems.back(); // return copy of last element
}
Note that we now have two template parameters, so each definition of a member function must be defined with these two parameters:
template<typename T, typename Cont>
void Stack<T,Cont>::push (T const& elem)
{
elems.push_back(elem); // append copy of passed elem
}
You can use this stack the same way it was used before. Thus, if you pass a first and only argument as an element type, a vector is used to manage the elements of this type:
template<typename T, typename Cont = std::vector<T>>
class Stack {
private:
Cont elems; // elements
…
};
In addition, you could specify the container for the elements when you declare a Stack object in your program:
basics/stack3test.cpp
#include "stack3.hpp"
#include <iostream>
#include <deque>
int main()
{
// stack of ints:
Stack< int> intStack;
// stack of doubles using a std::deque<> to manage the elements
Stack< double,std::deque< double>> dblStack;
// manipulate int stack
intStack.push(7);
std::cout << intStack.top() << ’\n’;
intStack.pop();
// manipulate double stack
dblStack.push(42.42);
std::cout << dblStack.top() << ’\n’;
dblStack.pop();
}
Stack< double,std::deque< double>>
you declare a stack for double
s that uses a std::deque<>
to manage the elements internally.
You can make using a class template more convenient by defining a new name for the whole type.
To simply define a new name for a complete type, there are two ways to do it:
1. By using the keyword typedef
:
typedef Stack
typedef Stack<int> IntStack; // typedef
void foo (IntStack const& s); // s is stack ofints
IntStack istack[10]; // istack is array of 10 stacks of ints
We call this declaration a typedef3 and the resulting name is called a typedef-name.
2. By using the keyword using
(since C++11):
using IntStack = Stack <int>; // alias declaration
void foo (IntStack const& s); // s is stack of ints
IntStack istack[10]; // istack is array of 10 stacks of ints
Introduced by [DosReisMarcusAliasTemplates], this is called an alias declaration.
Note that in both cases we define a new name for an existing type rather than a new type. Thus, after the typedef
typedef Stack <int> IntStack;
or
using IntStack = Stack <int>;
IntStack
and Stack<int>
are two interchangeable notations for the same type.
As a common term for both alternatives to define a new name for an existing type, we use the term type alias declaration. The new name is a type alias then.
Because it is more readable (always having the defined type name on the left side of the =
, for the remainder of this book, we prefer the alias declaration syntax when declaring an type alias.
Unlike a typedef
, an alias declaration can be templated to provide a convenient name for a family of types. This is also available since C++11 and is called an alias template.4
The following alias template DequeStack
, parameterized over the element type T
, expands to a Stack
that stores its elements in a std::deque
:
template<typename T>
using DequeStack = Stack<T, std::deque<T>>;
Thus, both class templates and alias templates can be used as a parameterized type. But again, an alias template simply gives a new name to an existing type, which can still be used. Both DequeStack<int>
and Stack<int, std::deque<int>>
represent the same type.
Note again that, in general, templates can only be declared and defined in global/namespace scope or inside class declarations.
Alias templates are especially helpful to define shortcuts for types that are members of class templates. After
struct C {
typedef … iterator;
…
};
or:
struct MyType {
using iterator = …;
…
};
a definition such as
template<typename T>
using MyTypeIterator = typename MyType<T>::iterator;
allows the use of
MyTypeIterator< int> pos;
instead of the following:5
typename MyType<T>::iterator pos;
Since C++14, the standard library uses this technique to define shortcuts for all type traits in the standard library that yield a type. For example, to be able to write
std::add_const_t<T> // since C++14
instead of
typename std::add_const<T>::type // since C++11
the standard library defines:
namespace std {
template<typename T> using add_const_t = typename add_const<T>::type;
}
Until C++17, you always had to pass all template parameter types to class templates (unless they have default values). Since C++17, the constraint that you always have to specify the template arguments explicitly was relaxed. Instead, you can skip to define the templates arguments explicitly, if the constructor is able to deduce all template parameters (that don’t have a default value),
For example, in all previous code examples, you can use a copy constructor without specifying the template arguments:
Stack< int> intStack1; // stack of strings
Stack< int> intStack2 = intStack1; // OK in all versions
Stack intStack3 = intStack1; // OK since C++17
By providing constructors that pass some initial arguments, you can support deduction of the element type of a stack. For example, we could provide a stack that can be initialized by a single element:
template<typename T>
class Stack {
private:
std::vector<T> elems; // elements
public:
Stack () = default;
Stack (T const& elem) // initialize stack with one element
: elems({elem}) {
}
…
};
This allows you to declare a stack as follows:
Stack intStack = 0;
// Stack<int>
deduced since C++17
By initializing the stack with the integer 0
, the template parameter T
is deduced to be int
, so that a Stack<int>
is instantiated.
• Due to the definition of the int
constructor, you have to request the default constructors to be available with its default behavior, because the default constructor is available only if no other constructor is defined:
Stack() = default;
• The argument elem
is passed to elems
with braces around to initialize the vector elems
with an initializer list with elem
as the only argument:
: elems({elem})
There is no constructor for a vector that is able to take a single parameter as initial element directly.6
Note that, unlike for function templates, class template arguments may not be deduced only partially (by explicitly specifying only some of the template arguments). See Section 15.12 on page 314 for details.
In principle, you can even initialize the stack with a string literal:
Stack stringStack = "bottom"; // Stack<char const[7]>
deduced since C++17
But this causes a lot of trouble: In general, when passing arguments of a template type T
by reference, the parameter doesn’t decay, which is the term for the mechanism to convert a raw array type to the corresponding raw pointer type. This means that we really initialize a
Stack< char const[7]>
and use type char const[7]
wherever T
is used. For example, we may not push a string of different size, because it has a different type. For a detailed discussion see Section 7.4 on page 115.
However, when passing arguments of a template type T
by value, the parameter decays, which is the term for the mechanism to convert a raw array type to the corresponding raw pointer type. That is, the call parameter T
of the constructor is deduced to be char const*
so that the whole class is deduced to be a Stack<char const*>
.
For this reason, it might be worthwhile to declare the constructor so that the argument is passed by value:
template<typename T>
class Stack {
private:
std::vector<T> elems; // elements
public:
Stack (T elem)
// initialize stack with one element by value
: elems({elem}) {
// to decay on class tmpl arg deduction
}
…
};
With this, the following initialization works fine:
Stack stringStack = "bottom"; // Stack<char const*>
deduced since C++17
In this case, however, we should better move the temporary elem
into the stack to avoid unnecessarily copying it:
template<typename T>
class Stack {
private:
std::vector<T> elems; // elements
public:
Stack (T elem)
// initialize stack with one element by value
: elems({std::move(elem)}) {
}
…
};
Instead of declaring the constructor to be called by value, there is a different solution: Because handling raw pointers in containers is a source of trouble, we should disable automatically deducing raw character pointers for container classes.
You can define specific deduction guides to provide additional or fix existing class template argument deductions. For example, you can define that whenever a string literal or C string is passed, the stack is instantiated for std::string
:
Stack( char const*) -> Stack<std::string>;
This guide has to appear in the same scope (namespace) as the class definition. Usually, it follows the class definition. We call the type following the ->
the guided type of the deduction guide.
Now, the declaration with
Stack stringStack{"bottom"}; // OK: Stack<std::string>
deduced since C++17
deduces the stack to be a Stack<std::string>
. However, the following still doesn’t work:
Stack stringStack = "bottom"; // Stack<std::string>
deduced, but still not valid
We deduce std::string
so that we instantiate a Stack<std::string>
:
class Stack {
private:
std::vector<std::string> elems; // elements
public:
Stack (std::string const& elem) // initialize stack with one element
: elems({elem}) {
}
…
};
However, by language rules, you can’t copy initialize (initialize using =
) an object by passing a string literal to a constructor expecting a std::string
. So you have to initialize the stack as follows:
Stack stringStack{"bottom"}; // Stack<std::string>
deduced and valid
Note that, if in doubt, class template argument deduction copies. After declaring stringStack
as Stack<std::string>
the following initializations declare the same type (thus, calling the copy constructor) instead of initializing a stack by elements that are string stacks:
Stack stack2{stringStack};
// Stack<std::string>
deduced
Stack stack3(stringStack);
// Stack<std::string>
deduced
Stack stack4 = {stringStack};
// Stack<std::string>
deduced
See Section 15.12 on page 313 for more details about class template argument deduction.
Aggregate classes (classes/structs with no user-provided, explicit, or inherited constructors, no private or protected nonstatic data members, no virtual functions, and no virtual, private, or protected base classes) can also be templates. For example:
template<typename T>
struct ValueWithComment {
T value;
std::string comment;
};
defines an aggregate parameterized for the type of the value val
it holds. You can declare objects as for any other class template and still use it as aggregate:
ValueWithComment< int> vc;
vc.value = 42;
vc.comment = "initial value";
Since C++17, you can even define deduction guides for aggregate class templates:
ValueWithComment(
char const*, char const*)
-> ValueWithComment<std::string>;
ValueWithComment vc2 = {"hello", "initial value"};
Without the deduction guide, the initialization would not be possible, because ValueWithComment
has no constructor to perform the deduction against.
The standard library class std::array<>
is also an aggregate, parameterized for both the element type and the size. The C++17 standard library also defines a deduction guide for it, which we discuss in Section 4.4.4 on page 64.
• A class template is a class that is implemented with one or more type parameters left open.
• To use a class template, you pass the open types as template arguments. The class template is then instantiated (and compiled) for these types.
• For class templates, only those member functions that are called are instantiated.
• You can specialize class templates for certain types.
• You can partially specialize class templates for certain types.
• Since C++17, class template arguments can automatically be deduced from constructors.
• You can define aggregate class templates.
• Call parameters of a template type decay if declared to be called by value.
• Templates can only be declared and defined in global/namespace scope or inside class declarations.
1 C++17 introduced class argument template deduction, which allows skipping template arguments if they can be derived from the constructor. This will be discussed in Section 2.9 on page 40.
2 It is a templated entity, see Section 12.1 on page 181.
3 Using the word typedef instead of “type definition” in intentional. The keyword typedef
was originally meant to suggest “type definition.” However, in C++, “type definition” really means something else (e.g., the definition of a class or enumeration type). Instead, a typedef should just be thought of as an alternative name (an “alias”) for an existing type, which can be done by a typedef
.
4 Alias templates are sometimes (incorrectly) referred to as typedef templates because they fulfill the same role that a typedef
would if it could be made into a template.
5 The typename
is necessary here because the member is a type. See Section 5.1 on page 67 for details.
6 Even worse, there is a vector constructor taking one integral argument as initial size, so that for a stack with the initial value 5
, the vector would get an initial size of five elements when : elems(elem)
is used.