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;
}
We need to put template definitions in the headers--this is exactly the opposite of how headers are usually used!
template <typename T, typename U>
void addElem(T<U>& collection, const U& toAdd){
collection.insert(toAdd);
}
std::vector<int> vec;
addElem(vec, 5);
std::set<int> set;
addElem(set, 5);
std::list<int> lst;
addElem(lst, 5);
std::array<int> arr;
addElem(arr, 5);
Okay!
Okay!
Okay!
std::abs
std::all_of
std::any_of
std::atoi
std::begin
std::boolalpha
std::cerr
std::cin
std::cout
std::distance
std::end
std::endl
std::ends
std::exception
std::fabs
std::find
std::terminate
std::tolower
std::tuple
std::uppercase
std::vector
std::ws
std::move
nullptr
std::ostream
std::ostringstream
std::pair
std::pow
std::printf
std::rand
std::range
std::regex
std::remove
std::set
std::setfill
std::setprecision
std::setw
std::fixed
std::forward
std::fstream
std::getline
std::hex
std::ifstream
std::ios
std::iostream
std::is
std::isalpha
std::isdigit
std::istream
std::iterator
std::map
std::max
std::min
std::size
std::sort
std::min
std::sprintf
std::sqrt
std::srand
std::stoi
std::strcmp
std::strerror
std::string
std::stringbuf
std::stringstream
std::strrchr
std::strstr
std::strtok
std::abs
std::all_of
std::any_of
std::atoi
std::begin
std::boolalpha
std::cerr
std::cin
std::cout
std::distance
std::end
std::endl
std::ends
std::exception
std::fabs
std::find
std::terminate
std::tolower
std::tuple
std::uppercase
std::vector
std::ws
std::move
nullptr
std::ostream
std::ostringstream
std::pair
std::pow
std::printf
std::rand
std::range
std::regex
std::remove
std::set
std::setfill
std::setprecision
std::setw
std::fixed
std::forward
std::fstream
std::getline
std::hex
std::ifstream
std::ios
std::iostream
std::is
std::isalpha
std::isdigit
std::istream
std::iterator
std::map
std::max
std::min
std::size
std::sort
std::min
std::sprintf
std::sqrt
std::srand
std::stoi
std::strcmp
std::strerror
std::string
std::stringbuf
std::stringstream
std::strrchr
std::strstr
std::strtok
std::abs
std::all_of
std::any_of
std::atoi
std::begin
std::boolalpha
std::cerr
std::cin
std::cout
std::distance
std::end
std::endl
std::ends
std::exception
std::fabs
std::find
std::terminate
std::tolower
std::tuple
std::uppercase
std::vector
std::ws
std::move
nullptr
std::ostream
std::ostringstream
std::pair
std::pow
std::printf
std::rand
std::range
std::regex
std::remove
std::set
std::setfill
std::setprecision
std::setw
std::fixed
std::forward
std::fstream
std::getline
std::hex
std::ifstream
std::ios
std::iostream
std::is
std::isalpha
std::isdigit
std::istream
std::iterator
std::map
std::max
std::min
std::size
std::sort
std::min
std::sprintf
std::sqrt
std::srand
std::stoi
std::strcmp
std::strerror
std::string
std::stringbuf
std::stringstream
std::strrchr
std::strstr
std::strtok
std::abs
std::all_of
std::any_of
std::atoi
std::begin
std::boolalpha
std::cerr
std::cin
std::cout
std::distance
std::end
std::endl
std::ends
std::exception
std::fabs
std::find
std::terminate
std::tolower
std::tuple
std::uppercase
std::vector
std::ws
std::move
nullptr
std::ostream
std::ostringstream
std::pair
std::pow
std::printf
std::rand
std::range
std::regex
std::remove
std::set
std::setfill
std::setprecision
std::setw
std::fixed
std::forward
std::fstream
std::getline
std::hex
std::ifstream
std::ios
std::iostream
std::is
std::isalpha
std::isdigit
std::istream
std::iterator
std::map
std::max
std::min
std::size
std::sort
std::min
std::sprintf
std::sqrt
std::srand
std::stoi
std::strcmp
std::strerror
std::string
std::stringbuf
std::stringstream
std::strrchr
std::strstr
std::strtok
std::abs
std::all_of
std::any_of
std::atoi
std::begin
std::boolalpha
std::cerr
std::cin
std::cout
std::distance
std::end
std::endl
std::ends
std::exception
std::fabs
std::find
std::terminate
std::tolower
std::tuple
std::uppercase
std::vector
std::ws
std::move
nullptr
std::ostream
std::ostringstream
std::pair
std::pow
std::printf
std::rand
std::range
std::regex
std::remove
std::set
std::setfill
std::setprecision
std::setw
std::fixed
std::forward
std::fstream
std::getline
std::hex
std::ifstream
std::ios
std::iostream
std::is
std::isalpha
std::isdigit
std::istream
std::iterator
std::map
std::max
std::min
std::size
std::sort
std::min
std::sprintf
std::sqrt
std::srand
std::stoi
std::strcmp
std::strerror
std::string
std::stringbuf
std::stringstream
std::strrchr
std::strstr
std::strtok
Q: Does the following code work?
struct Animal { ... };
struct Dog : public Animal { ... };
template <typename T>
swap(T& a, T& b){
T temp;
temp = a;
a = b;
b = temp;
}
swap(Dog(), Animal());
A: No. Even if the template could infer the correct type, we cannot swap an Animal and a Dog, since there's no guarantee the Animal is actually a Dog.
Q: Why can't we just use auto instead of this whole template business?
auto swap (auto& x, auto& y){
auto temp = x;
x = y;
y = temp;
}
A: They have different meanings. 'auto' means "there is exactly one type that can go here. Please figure it out for me."
Templates mean "any type can go here."
Caveat: C++20 introduces new syntax with concepts that allows this to happen. However, you cannot constrain the types for x and y (e.g. force them to be the same type).
Q: Why do you pass ints to template parameters if they're used for parametric polymorphism?
A: I should have clarified this more
Templates in C++ serve similar purposes to generics in Java, but have additional functionality that made them much more powerful.
template <unsigned int n>
...
or
You're coding along on Project N, and you suddenly need to count the number of elements in an std::vector equal to a certain value.
You remember what Kevin said about using STL (standard library) functions, so you go to cpprereference.com to look up how to use std::count...
...and then you write a for-loop.
...which is a shame, because they're usually pretty useful, and not that hard to use once you understand what's going on!
auto bbb = ball.getBoundingBox();
auto collides_with_ball = [bbb](const Brick &b) {return bbb.intersects(b.getBoundingBox());}
auto last = std::remove_if(bricks.begin(), bricks.end(), collides_with_ball);
bricks.erase(last, bricks.end()); # Cleanup due to how std::remove_if works
int begin = 0;
int i = begin;
while(i != vec.size()){
vec[i]++;
i++;
}
Node* begin = linkedlist.head();
Node* target = begin;
while(target != nullptr){
target->data++;
target = target->next;
}
Node* begin = tree.root();
Node* target = begin;
while(target != nullptr){
target->data++;
target = tree.in_order_traversal().next(target);
}
Node* begin = tree.root();
Node* target = begin;
while(target != nullptr){
target->data++;
target = tree.pre_order_traversal().next(target);
}
...or...
Node* begin = tree.root();
Node* target = begin;
while(target != nullptr){
target->data++;
target = tree.in_order_traversal().next(target);
}
Node* begin = linkedlist.head();
Node* target = begin;
while(target != nullptr){
target->data++;
target = target->next;
}
int begin = 0;
int i = begin;
while(i != vec.size()){
vec[i]++;
i++;
}
A first valid element
A current element
A first invalid element
A way to access the data within the element
A way to get the next element
An associated data structure
A first valid element
A current element
A first invalid element
A way to access the data within the element
A way to get the next element
An associated data structure
begin()
(no special method)
end()
* [dereference]
next() or ++
Node* begin = tree.root();
Node* target = begin;
while(target != nullptr){
target->data++;
target = tree.in_order_traversal().next(target);
}
Node* begin = linkedlist.head();
Node* target = begin;
while(target != nullptr){
target->data++;
target = target->next;
}
int begin = 0;
int i = begin;
while(i != vec.size()){
vec[i]++;
i++;
}
How to add one to every element of a vector linked list tree collection?
template<typename T>
void add_one(T& collection){
T::iterator it = collection.begin();
while(it != collection.end()){
*it++;
it = it.next();
}
}
T::iterator it = collection.begin();
while(it != collection.end()){
auto& elem = *it;
/* Do computation */
it = it.next();
}
This pattern is common enough that we gave it a special coat of paint:
for (auto& elem : collection){
/* Do computation */
}
Reading and Riting and Rithmetic
Input Iterator
Can read input and increment
Output Iterator
Can write output and increment
Forward Iterator
Can both read and write input* and increment
* Unless it's a const
iterator, because life
is never simple
Bidirectional Iterator
Can do everything forward iterators can, can also decrement
Random Access Iterator
Can do everything bidirectional iterators can, can also increment/decrement by arbitrary amounts
iter++;
iter--;
iter+3;
count, find, fill
How do we call this function?
std::vector<int> vec = {1,3,5,7,9,12,14,16,27,3,2,4,19,3};
int num_equals_3 = std::count(vec.begin(), vec.end(), 3);
std::vector<int> vec = {1,3,5,7,1,9,12,14,16,1,27,3,2,4,19,3};
// Let's be honest: who actually wants to type out this type signature??
std::vector<int>::iterator iter = std::find(vec.begin(), vec.end(), 3);
auto iter2 = std::find(vec.begin(), vec.end(), 3);
Returns iterator to *first* instance of value, if it exists
if (iter2 == vec.end()){
/* Did not find value in the vector */
}
What if it doesn't?
Why does this need a ForwardIterator?
std::vector<int> vec = {1,3,5,7,1,9,12,14,16,1,27,3,2,4,19,3};
auto iter1 = std::find(vec.begin(), vec.end(), 3);
auto iter2 = std::find(next(iter1), vec.end(), 3);
std::fill(iter1, iter2+1, 5);
What does this code do?
Suppose that vec has
Useful little tools in the STL
Copies the elements between first and last into the range indicated by d_first
auto iter = first;
while(iter != last){
*d_iter = *iter;
++iter;
++d_iter;
}
What if we don't want to overwrite elements in the destination?
Note: using std::inserter() requires that the underlying class implements an insert() method.
If the class implements a push_back() method, you can also use std::back_inserter()
// Copy all elements from src into dest
std::vector<int> src = /* initialization */;
std::vector<int> dest;
// If we don't reserve, we run off the end
dest.reserve(src.size());
std::copy(src.begin(), src.end(), dest.begin());
// Copy all elements from src into dest
std::vector<int> src = /* initialization */;
std::set<int> dest;
// How do I insert the element into a set?
// I can't preallocate space anymore...
// Copy all elements from src into dest
std::vector<int> src = /* initialization */;
std::set<int> dest;
auto dest_inserter = std::inserter(dest.end());
std::copy(src.begin(), src.end(), dest_inserter);
Just an iterator that runs in the opposite direction of the "normal" iterator
Class must support at least bidirectional iterators to have a reverse iterator
bool is_palindrome(const std::string& a){
return std::equals(a.begin(), a.end(),
a.rbegin(), a.rend());
}
So far, we can count all elements in a collection equal to something (5, or "mystring", or false)
But what if we want to count all even elements? Or all elements that are Fibonacci numbers?
What if we want to sort things?
int neven = std::count_if(vec.begin(), vec.end(), /* What type of thing goes here? */);
int nodd = std::count_if(vec.begin(), vec.end(), /* What type of thing goes here? */);
(starting in C++14)
if (point == Point(0,0)){
std::cout << "It's the origin!" << std::endl;
}
Name?
Name?
The second point has no name: it is an anonymous object
std::count_if(vec.begin(), vec.end(), [](int x){return x%2==0;});
no name!
We can still bind them to names though!
auto is_even = [](int x){return x%2==0;}
bool three_is_even = is_even(3);
[=](int x, int y) -> bool { return x <= y; }
The capture block
Parameters and return type
The function body
[=](int x, int y) -> bool { return x <= y; }
Works pretty much like a normal function body: sequence of statements separated by semicolons.
What variables can we access?
[=](int x, int y) -> bool { return x <= y; }
Similar to how they work in regular functions (in fact, the arrow syntax is legal for regular functions as well!)
[=](int x, int y) { return x <= y; }
If we don't specify a return type, the compiler will automatically deduce one.
[=](int x, int y) -> auto { return x <= y; }
equivalent
[=](int x, int y) -> bool { return x <= y; }
Lambdas can capture values from their current scope.
Think of them as being saved when the lambda is being created and used when the lambda is called.
A capture clause is a comma-separated list of variable names which should be captured by the lambda.
#include<vector>
#include<string>
#include<iostream>
std::vector<std::string> adjectives = {"mean", "green", "lean"};
int main(){
std::string suffix;
std::cin >> suffix;
auto append_suffix = [suffix](std::string& f){return f + suffix;};
assert(append_suffix("hey") == std::string("hey") + suffix);
std::transform(adjectives.begin(), adjectives.end(), append_suffix);
}
What are the captures? Parameter types?
What are the captures? Parameter types?
auto bbb = ball.getBoundingBox();
auto collides_with_ball =
[bbb](const Brick &b) {return bbb.intersects(b.getBoundingBox());}
auto last = std::remove_if(bricks.begin(), bricks.end(), collides_with_ball);
Sometimes, you don't want to write out a list of 30 million variables (though if you're capturing that many, you might already be doing something wrong)
C++ gives us two special capture characters to automatically generate the capture list:
These are generally safe to use all over the place, though be careful if the variable names overlap (e.g. you have both a capture and an input named the same thing)
Rule 1: You can pretend the ExecutionPolicy overloads don't exist (unless you want to run these in parallel!)
Rule 2: Look at the types and names and think about what they mean! Always look at the overloads without Predicates or Ops first
Rule 3: The output structure is assumed to be large enough to hold all the data.
(If this is false, make the output large enough or use an insert iterator)
Rule 3.5: If two input ranges are needed, and there is not a last iterator for the second range, it is assumed the two ranges are the same size.
Rule 4: Unary lambdas take one operand, Binary lambdas take two
Predicates return booleans, and Ops return anything.
Scrabble code!
Note: the following code is completely agnostic to the underlying data structures: the only rule is that your data structure has to implement the appropriate iterator types.
The user enters several words they want to try playing. The program makes sure that all the scrabble words are legal, then picks the highest-scoring one.
Note: code does not 100% compile--ask me if you want to see a compiling version (it needs templates)
constexpr auto legalWords = gen_scrabble_legal_words();
unsigned int scoreWord(std::string word){
/* Compute scrabble point value */
}
int main(){
/* Some collection of strings: again, we don't care
what the format is as long as it has iterators */
auto userWords = get_user_input();
// Use built-in string comparison
std::sort(userWords.begin(), userWords.end());
auto illegalWords;
std::set_difference(userWords.begin(), userWords.end()
,legalWords.begin(), legalWords.end()
,std::inserter(illegalWords.begin()));
if(!illegalWords.empty()){
std::cout << "Illegal word in input!" << std::endl;
}
...
// Continued on next slide
// Continue from previous slide
auto legalUserWords;
auto is_legal_word = [&legalWords](std::string word) -> bool {
return std::find(legalWords.begin(), legalWords.end(), word) != legalWords.end();
};
std::copy_if(userWords.begin(), userWords.end(), std::inserter(legalUserWords.begin())
,is_legal_word);
auto compare_words = [](const std::string& a, const std::string& b){
return scoreWord(a) < scoreWord(b);
};
// Iterator to largest element
auto maxIter = std::max_element(legalUserWords.begin(), legalUserWords.end(), compare_words);
std::cout << "The highest-scoring legal word that you entered was " << *maxIter << std::endl;
} // End of main()
A way to abstract out "go through every element in the collection."
Have different capabilities: read/write and motion.
std::begin() or collection.begin() gets us an iterator to the first valid element.
std::end() or collection.end() gets us an iterator to the first invalid element.
not the last valid element!
Use std::inserter() or std::back_inserter() to get an insertion iterator.
An anonymous function that can be constructed and used in a local scope.
[=](int x, int y) -> bool { return x <= y; }
Capture Clause
Parameters
Function Body
Project 2 is still due next week.
If you haven't started thinking about the garbage collector yet, start worrying.
==========
No quiz next week! There will be two back-to-back quizzes at some point in the future.