Kevin Song
I'm a student at UT (that's the one in Austin) who studies things.
p
The Stack and Heap
What are the properties of these two memory stores?
A technique for managing resource lifetimes (e.g. memory, files, locks, objects) by tying the lifetime of the resource to the lifetime of a stack-allocated class.
When the stack variable goes out of scope, its destructor is called and the resource is released. Since stack variables go out of scope eventually, the resource is eventually released.
With much love to UW and UPenn for providing inspiration for this lecture
void swap(int& x, int& y){
int temp;
temp = y;
y = x;
x = temp;
}
I want to swap two ints
void swap(float& x, float& y){
float temp;
temp = y;
y = x;
x = temp;
}
I want to swap two ints floats
Question: do I need to rename swap to something like swap_float?
void swap(std::string& x, std::string& y){
std::string temp;
temp = y;
y = x;
x = temp;
}
I want to swap two ints floats strings
void swap(int& x, int& y){
int temp;
temp = y;
y = x;
x = temp;
}
void swap(float& x, float& y){
float temp;
temp = y;
y = x;
x = temp;
}
void swap(std::string& x, std::string& y){
std::string temp;
temp = y;
y = x;
x = temp;
}
Thanks to overloading, we don't have to name our functions differently...
...but we still have to write the same code over and over, which is A Bad Thing™
In programming: the ability to present the same interface for many different underlying datatypes (shapes).
We have already seen distinctions between types of polymorphism:
Another distinction:
If the code does the same thing for all underlying types, it is known as
parametric polymorphism
If the code does different things for different underlying types, we call it
ad-hoc polymorphism
Which type is inheritance?
void swap(std::string& x, std::string& y){
std::string temp;
temp = y;
y = x;
x = temp;
}
C++ handles parametric polymorphism using templates
template <class T>
void swap(T& x, T& y){
T temp;
temp = y;
y = x;
x = temp;
}
Code must do same thing for all T, since there is no mechanism to figure out what T is
template <class T>
What follows this is a template
Modern C++ uses `typename`, which I will use for the rest of the lectures, but be aware that lots of old code uses the `class` keyword here.
The name of the type
(can use whatever name, but single caps letter is traditional).
template <typename T>
The function/class that follows the template prefix will resolve T to some type (e.g. int, char, Ball).
template <class T>
void swap(T& x, T& y){
T temp;
temp = y;
y = x;
x = temp;
}
swap(int, int);
swap(char, char);
swap(arkanoid::Ball, arkanoid::Ball);
swap(int, long);
swap(std::string, std::string);
swap(float, double);
Which calls on the right are valid?
How and when does the compiler know to create template code?
template <class T>
void swap(T& x, T& y){
T temp;
temp = y;
y = x;
x = temp;
}
int main(){
int x, y;
Dog a, b;
swap(x,y);
swap(x,b);
}
(1) Compiler reads template definition.
It now knows that swap() is a template, but it does not generate any code yet!!
(2) Compiler sees usage of swap. It looks up the template and creates an <int> specialization or instantiation.
(3) Compiler sees usage of swap. It looks up the template and fails to create an instantiation. This causes an error on line 13.
The compiler doesn't generate code until it sees the first usage of the template!
int add(int x, int y){
return x + y;
}
int subtract(int x, int y){
return x - y;
}
template <typename T>
T add(T x, T y){
return x + y;
}
template <typename T>
T subtract(T x, T y){
return x - y;
}
_Z3addii:
.LFB0:
.cfi_startproc
leal (%rdi,%rsi), %eax
ret
.cfi_endproc
.LFE0:
.size _Z3addii, .-_Z3addii
.p2align 4
.globl _Z8subtractii
.type _Z8subtractii, @function
_Z8subtractii:
.LFB1:
.cfi_startproc
movl %edi, %eax
subl %esi, %eax
ret
.cfi_endproc
gcc -S
gcc -S
Using typical C++ code organization techniques with templates will cause catastrophic failure
// File main.cpp
#include "swap.h"
int main(){
int a = 3, b = 8;
swap(a,b);
}
// File swap.h
template <typename T>
void swap(T& v1, T& v2);
// File swap.cpp
#include "swap.h"
template <typename T>
void swap(T& a, T& b){
T temp;
temp = a;
a = b;
b = temp;
}
// File main.cpp
#include "swap.h"
int main(){
int a = 3, b = 8;
swap(a,b);
}
// File swap.h
template <typename T>
void swap(T& v1, T& v2);
_start:
blah blah blah
main:
push 8
push 3
call swap
// File swap.h
template <typename T>
void swap(T& v1, T& v2);
// File swap.cpp
#include "swap.h"
template <typename T>
void swap(T& a, T& b){
T temp;
temp = a;
a = b;
b = temp;
}
No usage of template: no instantiation!
_start:
blah blah blah
main:
push 8
push 3
call swap
There are several solutions. The easiest and most common is to place template definitions in the header file.
This is the exact opposite of what we normally do with header files!!
// File swap.h
template <typename T>
void swap(T& v1, T& v2){
T temp;
v1 = temp;
...
}
Templates for classes work pretty much the exact same way for template functions:
template <typename T>
class Pair {
public:
T getFirst() const;
void setFirst(T first);
Pair();
private:
T m_first;
T m_second;
}
If you choose to implement a method outside of the declaration (which usually isn't done), you need to add the template prefix and scope resolution operation:
// Broken
T Pair::getFirst() const{
return this->m_first;
}
// Works
template <typename T>
T Pair<T>::getFirst() const{
return this->m_first;
}
Templates are written without any way to control what types can be implemented
This sometimes leads to interesting problems.
template <typename T, typename U>
??? add(T x, U y){
return x + y;
}
What type should add return?
T = float, U = int?
T = int, U = float?
T = char, U = long?
template <typename T, typename U>
??? add(T x, U y){
return x + y;
}
Note: once we know what T, U are, we can decide! But not before then.
Solution: decltype
template <typename T, typename U>
decltype(x+y) add(T x, U y){
return x + y;
}
Note: decltype is a function which returns types.
None of this will be tested, though declval() will show up in project 3.
Sometimes, you want to use the reference of a type instead of the type, (e.g. for late-binding polymorphism), but you only have access to the type.
int main(){
/* Will use compile-time polymorphism
because MySubClass returns a value */
decltype(MySubClass().foo()) a1;
/* Use declval to use late-binding */
decltype(std::declval<NonDefault>().foo()) a1;
}
Sometimes, you want to use the reference of a type instead of the type, (e.g. for late-binding polymorphism), but you only have access to the type.
int main(){
/* Will use compile-time polymorphism
because MySubClass returns a value */
decltype(MySubClass().foo()) a1;
/* Use declval to use late-binding */
decltype(std::declval<NonDefault>().foo()) a1;
}
Templates can also take values!
template <unsigned int n>
struct factorial {
int value = n * factoral<n-1>::value;
};
template <>
struct factorial<0> {
int value = 1;
};
int main(){
// Computed at compile-time!
int factorial_25 = factorial<25>::value;
}
The same code for all types.
template <class T>
void swap(T& x, T& y){
T temp;
temp = y;
y = x;
x = temp;
}
// File main.cpp
#include "swap.h"
int main(){
int a = 3, b = 8;
swap(a,b);
}
// File swap.h
template <typename T>
void swap(T& v1, T& v2);
// File swap.cpp
#include "swap.h"
template <typename T>
void swap(T& a, T& b){
T temp;
temp = a;
a = b;
b = temp;
}
This file structure will fail to compile, since template definition is not known on line 6 of main()
Solution: template definitions go in the header file!
This is exactly the opposite of how non-template code should be structured!
Useful when determining type is difficult or impossible
template <typename T, typename U>
decltype(x+y) add(T x, U y){
return x + y;
}
Quiz Formats:
Quiz Formats:
Resources:
Lectures:
By Kevin Song
Templates
I'm a student at UT (that's the one in Austin) who studies things.