[ Templates ]
Adapted from http://www.cs.washington.edu/people/acm/tutorials
The immortal question: Why?
What exactly are templates for, and why learn them?
• Limited Generic Programming (polymorphism)
Some functions have the same semantics for some (if not all) data
types. For instance, a function print() should display a sensible
representation of anything passed in. Ideally, it shouldn’t need to be
rewritten for each possible type.
• Less repetitive code
Code that only differs in the data type it handles does not have to be
rewritten for each and every data type you want to handle. It’s easier to
read and maintain since one piece of code is used for everything
Example: a swap function
Problem: Oftentimes, it is nice to be able to swap the values of two
variables. This function’s behavior is similar for all data types. Templated
functions let you do that – in most cases without any syntax changes.
Stupid method – write an overloaded function for each type
Swap for integers Swap for an arbitrary type T
void swap(int &a, int &b) { void swap(T &a, T &b) {
int c = a; T c = a;
a = b; a = b;
b = c; b = c;
} }
Template method – write one templated function
template This function can be used with any
void swap(T &a, T &b) { type that supports assignment and can
T c = a; be passed in as a non-const reference.
a = b;
b = c;
}
Template Syntax: swap dissected
The template line states that
everything in the following Here we have a list of “placeholder
declaration or definition is under variables.” In almost all cases, they
the subject of the template. (In this will be specified with either the
case, the definition is the function typename or class keywords.
swap) These two keywords are
equivalent.
template
void swap(T &a, T &b) {
T c = a;
a = b;
b = c;
}
“Placeholder variables” have one value
within each template declaration. Think of
them as being replaced by whatever type
you specify the template to be.
Template Syntax: Using it
Using a template template
To use a template, one has to specialize void swap(T &a, T &b) {
it. This is why it isn’t quite a generic T c = a;
function. It does static polymorphism. It a = b;
morphs itself to the right type at b = c;
preprocess time (explained later!). }
Syntax
To explicitly specialize a template, write its name with the arguments for the
placeholder variables in angle brackets. This method always works.
Example:
double d1 = 4.5, d2 = 6.7;
swap(d1, d2);
Templates however can auto-sense their placeholder values if all information
about what the placeholders represent can be inferred from the context
(arguments, and for member functions, the associated class instance). This is
called implicit specialization. In the previous case, the compiler is smart
enough to figure out that T is a double even without the explicit
since the arguments are doubles. Thus this shorthand works:
Example:
swap(d1, d2);
How they Work: Compilation 101
Preprocessor Libraries
Resolves #define, #include,
Source code .c .h .c .h
comments, templates
(text)
Preprocessor
Compiler
Translates code to Machine C/C++ code
(text)
Language. It outputs an “object Compiler
file.” This code is not executable Object code
(bin)
Linker
Linker
Takes object files and resolves
references to functions and Native Executable executable
variables in different files (bin)
When you build an executable from a C++ source file, the preprocessor removes
all the things listed under “Preprocessor.” The result is pure C++ code (no
comments, templates, #includes, etc). This code is then compiled and linked.
All good programmers understand this process well.
How they Work: Compiler, Linker
What does it mean to compile?
• The term “compile” is somewhat ambiguous. Often, when people say
“compile” they mean “build.” In the formal sense, it means turning one
language into another language. With C++, this generally means turning
C++ source code into Machine Code.
• Each C++ source file is usually compiled into an object file that contains the
code of all its defined functions. At this point, if you call a function from a
library or another file, the object code (stuff in the object file) only says “this
function exists somewhere and I want to use it”
• This if formally what the compiler is. When you get syntax errors, this is
usually the compiler talking to you (as opposed to the linker or preprocessor).
How they Work: Compiler, Linker
What does it mean to link?
• After compilation happens, all the object files need to be linked together into
a final executable. All “I want this function” stubs in the object files have to
actually be resolved to some block of machine code and then the resulting
executable has to be formatted in a way the operating system can understand.
• If you write a prototype for a function, but forget to define it, your code will
compile but it won’t link. Link errors are usually harder to track, as the
linker can’t always give you line numbers (the linker only looks at the object
files and knows nothing about the original source).
How they Work: Preprocessor
What is the preprocessor?
The preprocessor, formally, deals with the directives that start with a # sign (like
#include, #define). However, here, the term will be used to mean everything
that happens before the compiler gets the stuff to turn into machine code.
The relevant (in relation to templates) things the preprocessor does are:
• Replaces all #include statements and with the files they refer to.
• Removes all comments.
• Replaces all #defines macros with their value
• Generates actual code from templates
Templates do not exist!
• Templates are a preprocessor construct. They are cookie-cutters with which
the preprocessor generates real C++ code. When a template is used, (that
is, specialized, implicitly or explicitly), it get instantiated.
• The instantiation tells the preprocessor to create a version of the template
where each placeholder is replaced by its specialization. At this point, the
specific version of the template comes into existence and can be compiled. It
does not exist otherwise!
• In a very real way, a template just does a search and replace for each type you
specialize the template for. In essence, you are doing the same as writing a
bunch of overloaded functions. It’s just done for you, behind your back.
How they Work: Consequences
Problem:
Templates are resolved in the preprocessing stage, so they don’t exist to
the compiler until they get instantiated. This is the balance between trying
to make templates work transparently, and trying to make things efficient.
Effects:
• Template code will not get compiled until used (and thus instantiated).
Thus, the compiler will not catch syntax errors until the template is used.
• This holds at the level of individual methods. They are not compiled
unless they are used.
Class Templates: Class Definition
Syntax:
Templated classes basically follow the same syntax as templated
functions. However, the rules for which templated classes can infer their
specialization (see Template Syntax) are a bit more convoluted.
Before moving on, a bit of review on templated functions:
Are the following two templates equivalent?
template template
void swap(T &a, T &b) { void swap(C &a, C &b) {
T c = a; C c = a;
a = b; a = b;
b = c; b = c;
} }
Answer:
Yes, they are equivalent. This may be relevant when writing class
templates as it is possible that a situation may arise where two definitions
are written for the same thing. If this happens, the program will not build
since there are two equivalent function definitions. The name of the
placeholder doesn’t matter, and “typename” and “class” can be used
interchangeably. Just something to remember.
Class Templates: Example
Example: A templated, dynamic, 2 dimensional array (Matrix)*
#ifndef MATRIX_H
Notice the only addition to #define MATRIX_H
the class definition is the
line: template
template class Matrix {
public:
Matrix(int rows, int cols);
Matrix(const Matrix &other);
Within the the virtual ~Matrix();
definition
block, the Matrix& operator=(const Matrix &rhs);
T* operator[](int i);
placeholder T can int getRows() const;
be used as a data int getCols() const;
type. When the
protected:
template is void copy(const Matrix &other);
specialized, it
takes on the value private: The header is pretty
of the Matrix(); pedestrian. Let’s have
int m_rows;
specialization. some fun.
int m_cols;
T *m_linArray; On to the class
}; implementation.
File: Matrix.h #endif /* MATRIX_H */
*A commented version of this code is provided separately. It wouldn’t fit on the slide.
Class Templates: Example cont’d
#include "Matrix.h" template
T* Matrix::operator[](int i) {
template return m_linArray + (i*m_cols);
Matrix::Matrix() }
{}
template
template void
Matrix::Matrix(int rows, int cols) { Matrix::copy(const Matrix &other) {
m_rows = rows; m_rows = other.m_rows;
m_cols = cols; m_cols = other.m_cols;
m_linArray = new T[m_rows * m_cols];
} int size = m_rows * m_cols;
m_linArray = new T[size];
template for( int i=0; i ::Matrix(const Matrix &other) { m_linArray[i] =
copy(other); other.m_linArray[i];
} }
}
template
Matrix::~Matrix() { template
delete[] m_linArray; int Matrix::getRows() const {
} return m_rows;
}
template
Matrix&
Matrix::operator=(const Matrix &other) { template
if( this != &other ) { int Matrix::getCols() const {
delete[] m_linArray; return m_cols;
copy(other); }
}
The next slide explains
return *this; all this. It wouldn’t fit
} File: Matrix.cc on this slide.
Class Templates: Member Functions Dissected
Again, a templated class name by itself
has no meaning (eg. Matrix by itself Here, the template has been
means nothing). It only gets meaning implicitly specialized by its context.
through specialization, explicit or implicit. It is within the specialization region
Thus, when referring to an instance of a of the class scope. Thus it does not
templated class (a specific need the template arguments. For a
specialization), the class name must be class definition, the specialization
explicitly specialized. region is the class block.
template
Matrix&
Matrix::operator=(const Matrix &other) {
if( this != &other ) {
this->~Matrix(); This may be
copy(other); obvious, but
}
remember that
return *this; though constructors
} and destructors
Notice that the specialization region of have the same name
specialization Matrix:: as a the class
template, they are
region does not include the functions and do not
return type. Thus the return need to be
type needs explicit specialized.
specialization
Class Templates: Dark Arts (usage)
Syntax
• Templated classes must be explicitly specialized. Thus, to create a 2
dimensional Matrix of doubles using the last example, the syntax would be:
Matrix m(3,3);
• This specialization during declaration in reality creates a new type – namely
Matrix. This should be thought of as its own type, separate from
any other specialization of Matrix (so it is different from Matrix, or
Matrix, etc.) At this point, the instance behaves as any other
instantiated type – at least for compilation.
Now that you have the basics…
So, young Jedi, you want to become a template master. You think you know all the
tricks. Little do you know, your programming life now hangs in the balance. For
what comes next is the real lesson. The coming information is what makes people
shiver in fear when the word template is mentioned. Listen to what is presented
next closely lest the subtleties stay shrouded in shadows. Now is time to learn of
convention, for once you break from convention, you enter the dark side of
template hell, and forever shall it dominate your path… Mu ha ha ha
Shotgun Safety: Danger Awareness
(Identifying the danger)
Problem
Templates do not exist until you use them. They must be instantiated. Unless
this is done explicitly, instantiation occurs at the first usage for all known
template definitions. Thus, consider this example. Compile with
g++ -Wall –ansi main.cc Matrix.cc
Looks innocent, but it won’t link. /* main.cc */
#include
Quiz: What won’t link and why? using namespace std;
The link error happens with m1.getRows() #include “Matrix.h”
• Nothing from a template gets instantiated int main(void) {
until it is either used or explicitly Matrix m1(3,4);
instantiated. cout ::getRows() const }
does not get created until it is used at the
line with m1.getRows(). Note: The compile line is actually wrong!
• The definition of the function is in Matrix.cc The file Matrix.cc only contains template
and never used there. code. Since it is never used, it never
• Thus the definition never gets created and generates object code and shouldn’t be
compiled to object code. compiled.
Shotgun Safety: 3 conventions
(know the routines)
There are three conventions to avoiding the link problem
• Write all the code inline in the .h files.
• Do the same as above, but kind of fake it by writing an implementation
file with your implementation and #include the implementation file in
your header file.
• Write the template as you would a normal class (using a header and an
implementation file). Then create a new source file and #include the
template implementation file there. This is the file which you
compile, not the template implementation. (See next slide for example)
The first two methods have the problem that anytime an implementation of a
function is changed, all code that uses it must be recompiled (not just
relinked). This is very slow on large builds. Also, the build process will
instantiate the template many more times than necessary which is a waste of
time and space. The third method is free from such problems. It also avoids
some other hurdles since it forces the instantiation of everything at one point.
Shotgun Safety: An example
(faithfully practiced, prevents accidental loss of feet)
The proper procedure
/* main.cc */ • Write the template, separated into a
#include header and an implementation file
using namespace std;
• Create an instantiation file for the
#include template which include the
int main(void) {
implementation file.
Matrix m1(3,4); • Compile the instantiation file and not
cout ;
This line forces the instantiation of the Matrix class
compile line: template, as well as all its member functions, for
g++ –Wall –ansi main.cc MatrixInst.cc specialization int. Other specializations require their
own lines.
Poison detection: Pop Quiz
(be aware, so you know before it’s too late)
/*Foo.h */ Will this compile?
#ifndef FOO_H
• The unfortunate answer is yes, it will
#define FOO_H
compile. Even though b is
undeclared, it will “compile” because
template
nothing actually instantiates the
class Foo {
template, so the compiler never sees
Foo() {
the template code.
b = “Hello Mom!”;
} • This is why not explicitly forcing the
}; instantiation of class templates is
dangerous. You won’t know about an
#endif /* FOO_H */ error until you use it.
Because of this, some people believe that it is better to write a non-templated
version of a class first, and then make a template from that. Some
beliefs, however, are just wrong. That is one of them.
If templates are done properly with forced instantiation (see previous
slide), then this scenario will not occur.