<aside> 💡 Template Functions
template<typename T> T min(Tx, Ty) {
return x < y ? x : y;
}
int f() {
int x = 1, y = 2;
int z = min(x, y); // C++ infers T = int from the types of x & y
}
If C++ can’t determine T, you can tell it: int z = min<int>(x, y);
min('a', 'c'); // T = char
min(1.0, 3.0); // T = double
For what types T can min be used? For what types T does the body copile?
↑ the end of Exam coverings
Recall:
void foreach(AbstractIterator &start, AbstractIterator &finish, void(*f)(int)) {
while(start != finish) {
f(*start);
++start;
}
}
// - subtype of Abstract Iterator support !=, *, ++
// - f can be called as a fin
template<typename Iter, typename Fn>
void foreach(Iter start, Iter finish, Fn f) {
// as before
}
Now, Iter can be any tupe that supports !=, *, ++ (including raw ptrs)
void f(int n) { cout << n << endl; }
int a[] = {1, 2, 3, 4, 5}
foreach(a, a + 5, f); // ptints the array
<aside> 💡 <algorithm> library:
example: for_each (as above)
template<typename Iter, typename T>
Iter find(Iter first, Iter last, const T &val) {
// - return iterator to first ite min[first, last) matching val
// - or last, if not found
}
</aside>
<aside> 💡 count - like find, but returns # occurrences of val
template<typename InIter, typename OutIter>
OutIter copy(InIter first, InIter last, OutIter result) {
// copies one container range [first, last) to another, starting at result
} // Note: does **not** allocate memoty
vector v{1, 2, 3, 4, 5, 6, 7};
vector <int>w(4); // 0 0 0 0
copy(v.begin() + 1, v.begin() + 5, w.begin());
// w = {2, 3, 4, 5};
</aside>
<aside> 💡 typename Fn
template<typename InIter, typename OutIterm typename Fn>
OutIter transform(InIter first, InIter last, OutIter result, Fn f) {
while(first != last) {
*result = f(*first);
++first;
++result;
}
return result;
}
int add1(int n) { return n+1; }
...
vector v{2, 3, 5, 7, 11};
vector<int> w(v.size());
transform(v.begin(), v.end(), w.begin(), add1);
// w = {3, 4, 6, 8, 12};
How generalized is this code?
<aside> 💡 1) Fn
class Plus1 {
public:
int operator()(int n) { return n+1; }
};
Plus1 p;
p(4); // 5
transform(v.begin(), v.end(), w.begin(), Plus1{});
// Generalize:
clsss Plus {
int m;
public:
Plus(int m): m{m} {}
int operator()(int n){ return n + m; }
};
transform(v.begin(), v.end(), w.begin(), Plus{1});
// Plus1{}, Plus{1}, p - called **function objects**(sometimes functors)
<aside> 💡 Advantage of f’n objs - can maintain state
class Increasing Plus {
int m = 0;
public:
int operator()(int n){ return n+(m++); }
void reset() { m = 0; }
};
vector<int>(5, 0);
vector<int>w(v.size(), 0);
transform(v.begin(), v.end(), m.begin(), Increasing Plus{});
// w = {0, 1, 2, 3, 4}
// Consider: How many ints in a vector v are even?
vector v{ ____ };
bool even(int n){ return n % 2 == 0; }
int x = count_if(v.begin(), v.end(), even);
// If this were Racket, we might use lambda. Do the same here
int x = count_if(v.begin(), v.end(), w.begin(),
[](int n) { return n % 2 == 0; });
// |_______________________________|
</aside>
</aside>
<aside> 💡 2) Iterators
import<iterator>
vector v{1, 2, 3, 4, 5};
ostream_iterator<int> out {cout, '', ''};
copy(v.begin(), v.end(), out); // Prints 1, 2, 3, 4, 5
vector<int>w;
copy(v.begin(), v.end(), w.begin());
// X Copy doesn't allocate space in w
// - it can't - it doesn't even know that it's iterating over a vector !
But what if we had an iterator whose assignment operator inserts a new item?
copy(v.begin(), v.end(), back_inserter(w));
// copies v to the end of w, adds new item
// back_inserter(w): available for any container with a push_back method
</aside>
<aside> 💡 Range Abstraction
Constructor the method vector<T>::erase
O(n) cost for shuffling item. Fine
But...
Faster - shuffle the items up k pos’n (in one step each), where k = # items being erased
For this reason, most methods come with a “range” version:
iterator vector<T>::erase(iterator first, iterator last);
// erases item in [first, last) - only pats the linear cost once
Recall: transform taks two iteraots specifying a range [first, last) on input.
So maybe iterator isn;t the right level of abstraction - maybe the right abstraction encapsulations a part of iterators - a range
Consider - composing multuple f’ns on some input
<aside> 🤌🏼 consider: composing multiuple f’ns on some input
filter & transform - say, take all of the odd #’s to square them
auto odd = [](int n){ return n % 2 != 0; }
auto sqr = [](int n){ return n * n; }
vector v{ ______ };
vector<int>w(v.size()), x(v.size());
copy_if(v.begin(), v.end(), w.begin(), odd);
transform(w.begin(), w.end(), x.begin(), sqr);
Problems:
calls don’t conpose well
Intermediate storage - on-demand fetching!
What if copy_if of transform returned a range?
oprator so that R | A maps to A(R)
auto x = V | filter(off) | transform(sqr);
</aside>
</aside>