CS 105C: Lecture 7

Last Time...

Templates implement parametric polymorphism in C++

The same code for all types.

template <class T>
void swap(T& x, T& y){
  T temp;
  temp = y;
  y = x;
  x = temp;
}

Templates need to be instantiated--for this, the definition needs to be known

// 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!

Templates can compile fine with one set of types, but cause a type error with another.

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!

Questions!

Q: What are some common use cases for templates?

Q: What are some common use cases for templates?

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: What are some common use cases for templates?

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: What are some common use cases for templates?

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: What are some common use cases for templates?

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: What are some common use cases for templates?

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

CS 105C: Lecture 7

<iterator>, <algorithm>, lambdas

or

How to use <algorithm>

Hypothetical Situation

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.

Standard library functions are often not very pretty

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

Remove all bricks that collide with the ball

How did <algorithm> and its surrounding structures even come about?

Iterator

How do I add one to each element of a vector?

int begin = 0;
int i = begin;
while(i != vec.size()){
  vec[i]++;
  i++;
}

How do I add one to each element of a linked list?

Node* begin = linkedlist.head();
Node* target = begin;
while(target != nullptr){
  target->data++;
  target = target->next;
}

How do I add one to each element of a binary tree?

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

What do these solutions all have in common?

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

What do these solutions all have in common?

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 ++

These traits form the core of the iterator

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();
  }
}

In fact, we can do even better!

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 */
}

What other capabilities do iterators have?

The Three Rs

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;

<algorithm>s based on iterators

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

  • no copies of 3
  • only one copy of 3
  • two or more copies of 3

Bonus Iterators!

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?

Solution: Use an insert_iterator

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);

Reverse Iterators

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());
}

Computing Functions for Algorithm

Computing Functions for Algorithm

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? */);

Introducing Lambdas

(starting in C++14)

Lambdas are anonymous functions

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

Lambdas are anonymous functions

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);

A C++ Lambda has three parts

[=](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; }

The function body

Works pretty much like a normal function body: sequence of statements separated by semicolons.

 

What variables can we access?

  • Anything declared in the lambda arguments
  • Any variables that would normally be accessible in a standalone function (e.g. global variables, static class variables)
  • Any variables declared in the capture block
[=](int x, int y) -> bool { return x <= y; }

The parameters and return value

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; }

The captures clause

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.

Example Captures

#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?

Example Captures

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);

Auto Capture

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:

 

  • =      means automatically capture all unresolved variables by value
  • &      means automatically capture all unresolved variables by reference

 

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)

Reading The Docs

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.

Putting it all together

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()

Summary

Iterators

A way to abstract out "go through every element in the collection."

Have different capabilities: read/write and motion.

Iterators

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.

C++ Lambdas

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

Upcoming things

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.

Additional Readings

Notecards

  • Name and EID
  • One thing you learned today (can be "nothing")
  • One question you have about the material. If you leave this blank, you will be docked points.

    If you do not want your question to be put on Piazza, please write the letters NPZ and circle them.

CS105C: Lecture 7

By Kevin Song

CS105C: Lecture 7

Iterators, Algorithm, and Lambdas

  • 361