19
Templates Adapted from http://www.cs.washington.edu/people/acm/tut

[ ] Templates Adapted from

Embed Size (px)

Citation preview

Page 1: [ ] Templates Adapted from

[ ]Templates

Adapted from http://www.cs.washington.edu/people/acm/tutorials

Page 2: [ ] Templates Adapted from

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 codeCode that only differs in the data type it handles does not have to berewritten for each and every data type you want to handle. It’s easier toread and maintain since one piece of code is used for everything

Page 3: [ ] Templates Adapted from

Example: a swap function

Stupid method – write an overloaded function for each type

void swap(int &a, int &b) { int c = a; a = b; b = c;}

void swap(T &a, T &b) { T c = a; a = b; b = c;}

Swap for integers Swap for an arbitrary type T

template <typename T>void swap(T &a, T &b) { T c = a; a = b; b = c;}

This function can be used with any type that supports assignment and can be passed in as a non-const reference.

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.

Template method – write one templated function

Page 4: [ ] Templates Adapted from

Template Syntax: swap dissected

template <typename T>void swap(T &a, T &b) { T c = a; a = b; b = c;}

The template<…> line states that everything in the following declaration or definition is under the subject of the template. (In this case, the definition is the function swap)

Here we have a list of “placeholder variables.” In almost all cases, they will be specified with either the typename or class keywords. These two keywords are equivalent.

“Placeholder variables” have one value within each template declaration. Think of them as being replaced by whatever type you specify the template to be.

Page 5: [ ] Templates Adapted from

Template Syntax: Using it

template <typename T>void swap(T &a, T &b) { T c = a; a = b; b = c;}

To use a template, one has to specialize it. This is why it isn’t quite a generic function. It does static polymorphism. It morphs itself to the right type at preprocess time (explained later!).

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<double>(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 <double> since the arguments are doubles. Thus this shorthand works: Example: swap(d1, d2);

Using a template

Syntax

Page 6: [ ] Templates Adapted from

How they Work: Compilation 101

PreprocessorResolves #define, #include, comments, templates

CompilerTranslates code to Machine Language. It outputs an “object file.” This code is not executable

LinkerTakes object files and resolves references to functions and variables in different files

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.

Preprocessor

.c .h .c .h

Compiler

Linker

executable

Libraries

Source code (text)

C/C++ code (text)

Object code (bin)

Native Executable (bin)

Page 7: [ ] Templates Adapted from

How they Work: Compiler, Linker

• 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).

What does it mean to compile?

Page 8: [ ] Templates Adapted from

How they Work: Compiler, Linker

• 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).

What does it mean to link?

Page 9: [ ] Templates Adapted from

How they Work: PreprocessorWhat 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.

Page 10: [ ] Templates Adapted from

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.

Page 11: [ ] Templates Adapted from

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 <typename T>void swap(T &a, T &b) { T c = a; a = b; b = c;}

template <class C>void swap(C &a, C &b) { C c = a; a = b; 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.

Page 12: [ ] Templates Adapted from

Class Templates: Example

Example: A templated, dynamic, 2 dimensional array (Matrix)*

#ifndef MATRIX_H#define MATRIX_H

template <typename T>class Matrix {public: Matrix(int rows, int cols); Matrix(const Matrix &other); virtual ~Matrix();

Matrix& operator=(const Matrix &rhs); T* operator[](int i); int getRows() const; int getCols() const;

protected: void copy(const Matrix &other);

private: Matrix(); int m_rows; int m_cols; T *m_linArray;};

#endif /* MATRIX_H */File: Matrix.h

Notice the only addition to the class definition is the line: template <typename T>

*A commented version of this code is provided separately. It wouldn’t fit on the slide.

Within the the definition block, the placeholder T can be used as a data type. When the template is specialized, it takes on the value of the specialization.

The header is pretty pedestrian. Let’s have some fun.

On to the class implementation.

Page 13: [ ] Templates Adapted from

template <typename T>T* Matrix<T>::operator[](int i) { return m_linArray + (i*m_cols);}

template <typename T>void Matrix<T>::copy(const Matrix &other) { m_rows = other.m_rows; m_cols = other.m_cols;

int size = m_rows * m_cols; m_linArray = new T[size]; for( int i=0; i < size; i++ ) { m_linArray[i] =

other.m_linArray[i]; }}

template <typename T>int Matrix<T>::getRows() const { return m_rows;}

template <typename T>int Matrix<T>::getCols() const { return m_cols;}

Class Templates: Example cont’d#include "Matrix.h"

template <typename T>Matrix<T>::Matrix(){}

template <typename T>Matrix<T>::Matrix(int rows, int cols) { m_rows = rows; m_cols = cols; m_linArray = new T[m_rows * m_cols];}

template <typename T>Matrix<T>::Matrix(const Matrix &other) { copy(other);}

template <typename T>Matrix<T>::~Matrix() { delete[] m_linArray;}

template <typename T>Matrix<T>& Matrix<T>::operator=(const Matrix &other) { if( this != &other ) { delete[] m_linArray; copy(other); }

return *this;} File: Matrix.cc

The next slide explains all this. It wouldn’t fit on this slide.

Page 14: [ ] Templates Adapted from

template <typename T>Matrix<T>& Matrix<T>::operator=(const Matrix &other) { if( this != &other ) { this->~Matrix(); copy(other); }

return *this;}

Class Templates: Member Functions Dissected

Again, a templated class name by itself has no meaning (eg. Matrix by itself means nothing). It only gets meaning through specialization, explicit or implicit. Thus, when referring to an instance of a templated class (a specific specialization), the class name must be explicitly specialized.

Here, the template has been implicitly specialized by its context. It is within the specialization region of the class scope. Thus it does not need the template arguments. For a class definition, the specialization region is the class block.

specialization region of Matrix<T>::

Notice that the specialization region does not include the return type. Thus the return type needs explicit specialization

This may be obvious, but remember that though constructors and destructors have the same name as a the class template, they are functions and do not need to be specialized.

Page 15: [ ] Templates Adapted from

Class Templates: Dark Arts (usage)

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

• Templated classes must be explicitly specialized. Thus, to create a 2 dimensional Matrix of doubles using the last example, the syntax would be: Matrix<double> m(3,3);

Syntax

• This specialization during declaration in reality creates a new type – namely Matrix<double>. This should be thought of as its own type, separate from any other specialization of Matrix (so it is different from Matrix<int>, or Matrix<Foo>, etc.) At this point, the instance behaves as any other instantiated type – at least for compilation.

Now that you have the basics…

Page 16: [ ] Templates Adapted from

Shotgun Safety: Danger Awareness

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

Problem(Identifying the danger)

/* main.cc */#include <iostream>using namespace std;

#include “Matrix.h”

int main(void) { Matrix<int> m1(3,4); cout << m1.getRows() << endl;}

Looks innocent, but it won’t link.

Quiz: What won’t link and why?

The link error happens with m1.getRows() • Nothing from a template gets instantiated

until it is either used or explicitly instantiated.

• Matrix<int>::getRows() const does not get created until it is used at the line with m1.getRows().

• The definition of the function is in Matrix.cc and never used there.

• Thus the definition never gets created and compiled to object code.

Note: The compile line is actually wrong!The file Matrix.cc only contains template code. Since it is never used, it never generates object code and shouldn’t be compiled.

Page 17: [ ] Templates Adapted from

Shotgun Safety: 3 conventions

There are three conventions to avoiding the link problem

(know the routines)

• 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.

Page 18: [ ] Templates Adapted from

Shotgun Safety: An example

The proper procedure• Write the template, separated into a

header and an implementation file• Create an instantiation file for the

template which include the implementation file.

• Compile the instantiation file and not the template implementation file.

• The instantiation file generates the object code for the template.

(faithfully practiced, prevents accidental loss of feet)

/* main.cc */#include <iostream>using namespace std;

#include <Matrix.h>

int main(void) { Matrix<int> m1(3,4); cout << m1.getRows() << endl;}

Example:To make the previously unlinking piece of code link properly, it is necessary to instantiate an integer version of the Matrix template. The file would simply look like this

/* MatrixInst.cc */#include “Matrix.cc”

template Matrix<int>;

This line forces the instantiation of the Matrix class template, as well as all its member functions, for specialization int. Other specializations require their own lines.

notice that the implementation (not header) file is included.

compile line: g++ –Wall –ansi main.cc MatrixInst.cc

Page 19: [ ] Templates Adapted from

Poison detection: Pop Quiz(be aware, so you know before it’s too late)

Will this compile?/*Foo.h */#ifndef FOO_H#define FOO_H

template <typename T>class Foo { Foo() { b = “Hello Mom!”; }};

#endif /* FOO_H */

• The unfortunate answer is yes, it will compile. Even though b is undeclared, it will “compile” because nothing actually instantiates the template, so the compiler never sees the template code.

• This is why not explicitly forcing the instantiation of class templates is dangerous. You won’t know about an 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.