Cookies   I display ads to cover the expenses. See the privacy policy for more information. You can keep or reject the ads.

Video thumbnail
- Hi, I'm Jonathan Boccara.
I write on the blog Fluent C++.
It's a blog about expressive code in C++
with an article every Tuesday and Friday,
and I work at Murex.
I am a team leader of a C++ developer software,
software developer team. (audience grumbling)
I'm sorry? - Do you have a microphone?
- It's not working.
Oh, it's, okay.
Just let me put that up.
Hi, hi, everyone hears me?
We're good.
Okay.
Right, so I was Jonathan Boccara.
I was on Fluent C++ writing twice a week
about expressive code in C++,
and I'm still working at Murex as a team leader
of a C++ software developer team.
What we're gonna talk about today is the STL algorithms.
We're gonna start off this talk with a question to you.
Who in this room is aware of the fact
that it's a good thing to know the STL algorithms?
Just raise your hand if you're aware of that fact.
Pretty much everyone, hands rising.
That's pretty cool.
Second question, who knows them all?
Go on, raise your hand.
Go on, then.
That's pretty much, that's actually no one.
No one's hand's rising now.
That's exactly the purpose of this talk.
When you walk out of that room,
you will know every single thing
that's in the STL algorithms library.
To do that, I suggest that we start
with a brief chat about why it's important
to know them and what to learn in them,
and then we dive in the STL algorithms themselves.
All right?
So, there's this thing that's been popularized
across the C++ community over the past few years
that's we need to know all the STL algorithms
and we need to know them well, very well.
But why?
Well, for one thing, they can make
our code more expressive,
and they do that by raising levels of abstraction,
which means that they allow to express
what we want to do as opposed to how we want to execute it,
and I think that raising the levels of abstraction
is one of the most efficient way to write expressive code.
And it can be sometimes spectacular.
Like, if you've seen that talk a couple of years back,
by Sean Parent, which was called C++ Seasoning,
in that talk, Sean took a chunk of code
that was a bit hard to understand
and he refactored it into a more concise
and expressive piece of code,
just by picking the right STL algorithm.
That's not the only reason
If we write ourselves our manipulations
on collections, then we have to have, to write somehow,
the traversal of a collection,
and we have to write a for-loop if we don't resort
to using STL algorithms, and when we write for-loops,
there are things that can go wrong,
like for example, we need to be sure
that the traversal doesn't go past the ends,
like, we have to run that mental check,
run the loop in our head to make sure,
and even then, there's a risk,
and if we go past the end of a collection in C++,
then we get into undefined behavior,
and from that point on, anything can happen.
Literally, anything can happen,
and that's allowed by the standard.
What's happening on the screen right now,
is absolutely legal C++,
even though it's probably more likely
that you get a crash than a clown popping up next to you
but still, the clown is legal by the standard.
Another kind of mistake is dealing with the case
of an empty loop, and that's the same thing,
we have to run it in our head, have a risk
of it going wrong sometimes,
and then going to undefined behavior all over again.
With the STL algorithms, this doesn't happen,
so that's one more reason to use them.
Another case of mistake we can make
when writing our own for-loops is about complexity.
Some algorithms that they can be implemented naively,
in quadratic complexity, and in a more astute manner,
in a linear complexity.
We go back to an example later with algorithms on sets.
Also, STL algorithms are used
by billions of people every day.
Well, maybe not billions of people every day,
but quite a lot of people fairly often.
(audience laughing)
(laughing) More seriously, they are parts
of standard C++ which means that you should not be afraid
to use them and think that maybe
somebody's not gonna be able to understand them
because they don't know them.
I think we should level up to the STL algorithms
and not the other way around.
So this is the common, this is part
of the common vocabulary of C++
just as much as int or if or whatever the language is.
And it's so, so standard
that even if you're running an older C++ version,
you can still get most of the STL algorithms,
like for example, if you're in C++03 for example,
and you want to use like all of or any of or none of,
which appeared in C++11,
well, you can just grab that code
and copy it into your codebase and just start using them,
so most of them are within your reach,
whichever version of C++ you're on right now.
Right, so, now, let's say that you want
to go to the STL algorithms, you want to take that path.
Well, there's a trap that's lying
just at the beginning of that path,
and this is called for_each.
For_each is one of the STL algorithms.
It's nothing, there's nothing wrong with it.
It has a use case we'll see a bit later,
but the trap that it represents is that,
it looks like a for-loop, right,
because it takes a begin and an end,
and some code that it will execute
on every element of the collection, just like a for-loop.
So, you might be tempted to think
that you can replace all your for-loops by for_each,
and that you'd be done,
that you'd be using the STL algorithms.
And that's not quite the spirit,
because there's much more than for_each
in the STL algorithms library.
There's many more, many more algorithms
that let you express more nuance when you write your code.
I'm going to show you something to illustrate the fact
that there's much more than for_each
in the STL algorithms library.
(moderate orchestral music)
(dramatic orchestral music)
(intense orchestral music)
Ladies and gentlemen, I present to you
the world of the STL algorithms.
(audience clapping)
On this map, I've placed all the STL algorithms
as of C++17,
and I've grouped them by region,
according to the logical relationship they have, right?
The rest of this talk will consist in exploring that world,
but before diving in,
we can note a few things while looking at it from afar.
The first thing we can note is that,
for_each is just one of them on one island,
lost on the middle of the ocean,
so it's just one of them.
What we can note, also, is where most of them are.
You can observe that the largest region
on that world is over here,
and that's the lands of queries.
Call that queries because all those algorithms
that live here,
they just read into the collection
and extract some piece of information,
and they don't actually modify it.
I don't know what you think, but I would have thought
that most algorithm would somehow change the collection,
like sort or partition or whatever.
They are there, as well, but most of them,
they're just read-only operations.
I suggest that we start our journey
by the other end of the world from for_each,
down here on the algorithms on heaps.
So, what is a heap to begin with?
A heap is a data structure
that looks like a tree, but that has a property.
It's that every node must be smaller
than its children.
I think it's called that because it kind of like,
looks like a heap in real life,
like, the small ones are at the top
and the bigger one at the bottom.
It's better having a heap.
That you can observe, that the heap on the right
of the picture is the other way around, right?
The biggest element is at the top
and every element is bigger by its children.
That's a C++ heap, it's called a max heap.
The relationship between the parent and its children
is the parent is bigger, right?
So it's the other way around from the heap from real life,
but, you know, we developer don't really live
in real life anyway, so, does it really matter?
Don't think so.
So, one interesting property of a heap
is that we can squash it down into a range like that.
One more time, more slowly.
To squash down a heap into a range,
we take the first row, which is just the root of the tree
and then the second row, and then the third row,
and so on and so forth, and when we do this,
there's this nice property that it's a range,
so we can represent it with a vector, for example,
which is quite convenient to work with, and also,
to go from a node to its left child,
it's essentially taking its index
and multiplying it by twos, nearly that.
So it's a way to represent a tree into a range.
If we have a range, we have a beginning and an end
and we can use STL algorithms.
So, the first thing we can do
is taking values that are not particularly a heap,
and rearrange them so that they respect the heap property.
To make a heap, to make a heap, we use std::make_heap
with a begin and an end, and that rearranges
the element inside of a collection.
That's our first algorithm of the day.
That's one down, 104 to go.
I forgot to mention,
it's 105 STL Algorithms In Less Than an Hour, Each.
(audience laughing)
Just making sure we've got the same understanding here,
all right?
No, I'm joking.
Don't worry, well, either way, you'll find out.
Let's move on, what we can do with a heap
is add something to it, independently from C++,
to add something to a heap, we put the new thing
right at the end and it might not be its right position
because it may be smaller than its father,
and the way to add it to the heap is to swap it
with its parent until it reaches its final position.
All right, now, to do that in C++,
we can squash down the heap, right,
to represent it as a range.
Say that it's a vector, we add something to the end
and then we need something to make it bubble its way up
to its final position, and that's push_heap.
Now, push_heap just does the same thing
within, with the tree version, but in range version.
Now, to remove something from a heap,
or this thing with heap, why do we use heaps
in the first place, is to be able to get the maximum value
of the collection in not too much time,
and count it on time.
In fact, the heap, the max element of the collection
is just the front, so this is where we access
and this is also what we allow to remove.
To do that, we swap it with the last one.
That's what pop_heap does.
At this point, the range in yellow is probably not a heap
because there's the one at the end
that was probably one of the smallest ones,
that was at the beginning,
so we need to make it bubble its way down
to its final position, and that's what pop_heap does.
And if we want to remove, to actually remove that value,
we pop it back from the vector.
Now, if we don't pop it back from the vector,
but leave it here and do that again,
for the part in yellow, the shrinking heap,
if we do that over and over,
at the end, we'll get, can someone see
what we'll get at the end?
Yeah, right, a sorted collection.
So that's a way to sort the collection,
and that's what sort_heap,
std::sort_heap does.
Talking about sorting, this is the region around the corner,
the shore of sorting.
So, let's see how to sort with the STL.
The simplest way probably is std::sort.
That takes a collection, begin, end, and rearranges them
so that they're in sorted order, okay.
Now, if we only want to sort the beginning of a collection,
as we saw in depth in Fred's talk,
we can just partial sort the things.
A partial sort takes a begin and an end,
and sort of like a middle,
something inside of the collection,
and sorts the collection from the beginning to that point
and the rest is in unspecified order, right.
So the left-hand side of the collection
is what would be there if it was completely sorted.
Now, to sort just one element,
that's nth_element, which takes a begin, an end,
and something inside of the collection,
and puts at that position inside of the collection
the element that would be there
if the whole thing were sorted.
And for the rest, everything that's on the left is smaller,
and everything that's on the right is not smaller.
There's sort_heap, which we've mentioned
with repeatedly calling pop_heap,
and there's inplace_merge, inplace_merge
is the incremental step in a merge sort,
so we've got a collection that we can mentally think of
as two halves,
and merge sort, so the two halves are sorted
and merge sort combines the whole thing
into a sorted collection.
So that's about sorting with the STL.
Just further up the shore,
there's the region of partitioning.
So, what's partitioning in the first place?
To partition a collection is to look at it
through a predicate, something that returns a Boolean.
Here, our predicate is being blue.
To partition a collection is putting all the blue ones
at the beginning, and other not-blue ones at the end.
Of course, it works for blue and red and yellow
and actually every, any predicate, all right.
The border between the blue ones and the not-blue ones
is called a partition point, that's the end
of the blue range and that's also the begin
of the not-blue range, can you see that?
Okay, that's called partition_point
and to retrieve from a partition collection,
there's std::partition_point.
Right, if we take a step back on that part of the world,
that was the land of permutation,
the place where elements move around the collection
even if they don't change value.
We've seen heaps, sort, partition,
and there are a few others that we'll go through quickly.
There's this permutation that takes the element
at the end and put it at the bottom,
put them at the beginning, sorry.
That's rotate.
Another way to change the order of the element collection
is to do that randomly, and that's what std::shuffle does.
So std::shuffle takes a collection with elements
in a certain order, and takes something
that generates random numbers,
and rearranges the collection in random order.
There is an order we can define on permutations.
Given a set of object, given a collection of object,
we can order them, order all their possible arrangements,
and an intuitive way to see that is thinking
about an alphabetical order, right?
So, in this collection, one, two, three,
all the way to nine, the smallest one
if those were in a dictionary,
would be that first one, one, all the way to nine.
And the one just after would be the same thing,
with the nine and an eight, swapped together, right?
And so on, and the largest one would be the one
at the bottom here, from nine down to one, right?
If you have a collection in a given order,
any order between the smallest and the biggest one,
next_permutation which takes a beginning and an end
would rearrange it so that it goes to the order
that's just after it, and prev_permutation
to the order that's just before it.
That's interesting if you'd like to do something
for every possible arrangement of a collection,
we can repeatedly call next_permutation
until you've cycled back to the beginning.
And finally, the last one is reverse,
that reverses the element collection.
Right, so we've been all around the lands of permutation.
Just in the corner of the map here
are the secret runes.
The secret runes are things that you can combine
with other algorithms to generate new algorithms.
I call them runes because it's like,
it's like going to see runes to augment your powers
and also because I find that kind of cool as a name.
So let's call them that for purpose of this presentation.
We've seen all the algorithm that go
with the stable rune, so stable,
when you tack it onto an algorithm,
it does what this algorithm does,
but keep the relative order.
Think about partition for example.
It put all the blue ones at the beginning, that's for sure,
but maybe in the process, some of them got swapped around.
With stable_partition, they all keep
the same relative order, same thing with stable_sort.
The is rune checks for a predicate on the collection.
We know what sort, partition, heap, means,
or is_sorted, is_partitioned, is_heap returns a Boolean
to say, to indicate whether it's a sorted,
a partition or a heap that we're looking at.
An is until returns an iterator
that's the first position where that predicate
doesn't hold anymore.
All right, so for example, if we have a sorted collection,
and then we call is_sorted_until,
we'll get the end of that collection.
All right, let's take a step back.
So we've been down there in the lands of permutation.
Let's now focus on the largest part
of the world, the lands of queries.
There are quite a few of them in there,
but we'll go through them quite quickly.
So, one thing you can extract out of a collection
is some sort of value.
For example, the simplest one is probably count
that takes begin and an end and a value
and returns how many times this value occurs
in the collection.
There's accumulate, that makes the sum
of the element of the collection calling operator plus,
or any custom function you'd pass it.
In C++17, there's reduce that appeared,
that does practically the same thing as accumulate,
except it has a slightly different interface.
It can accept to take no initial value,
and it can also be run in parallel,
but essentially, it's the same as accumulate,
and transform_reduce takes a function
and applies that function to the element of the collection
before calling the reduce.
Partial_sum, well, it sums the,
all the elements starting from the beginning
to the current point of the collection,
so here, partial_sum would return a collection
with, in the first position, what was in the first position
of the original collection,
and in the second one would be the first one
plus the second one, in the third one
would be the first plus the second plus the third,
so on and so forth.
In C++17,
there's inclusive_scan, exclusive_scan,
transform inclusive_scan, transform exclusive_scan,
which have interesting names, let's say.
Or inclusive_scan is the same thing as partial_sum,
except it can be run in parallel.
Exclusive_scan is the same thing as inclusive_scan,
except it doesn't include the current element,
so for example, with exclusive_scan,
the second element would be equal
to the first element of the original collection,
the third one would be one plus two,
the fourth one would be one plus two plus three,
and so on and so forth,
and the transform versions of those two guys
take a function, apply it to the collection,
and then perform the inclusive slash exclusive scan.
Inner_product makes the inner product of the collection
which is a multiplication
of the counterpart elements
and then summing that whole thing.
Adjacent difference makes the difference
between every two neighbors in the collection,
and sample, appearing in C++17,
takes something that can return some,
that can generate random numbers,
and it takes a number,
say n, and it produces n element
of that collection, selected randomly.
Apart from querying a value, we can query a property.
There's all_of, any_of and none_of,
which are really useful in everyday code.
All_of takes collection and a predicate,
returns if all of the elements satisfy that predicate,
any_of if at least one element satisfy that predicate,
and none_of if no element satisfy that predicate.
Now, what do you think all_of return,
returns with an empty collection?
What do you think, Fred?
True.
Does everyone, everyone agrees with Fred?
Yeah, that's true.
Any_of on an empty collection?
Yeah, false.
None_of?
Come on, there's only two possible answers.
Yeah, true, absolutely.
That was querying a property on one range.
We can also query a property on two ranges,
which is essentially different ways of comparing them.
The simplest way to compare two ranges
is to see if they have the exact same contents,
so the element would be equal two by two
and it would have the same size
and that's what std::equal returns, it returns a Boolean.
Now, if we'd like to know if the two collections
contain the same elements but not necessarily
in the same order, that's is_permutation,
also returning a Boolean.
Now, we can also compare ranges
and say which one is smaller than the other one.
To define smaller, we could go with size.
That's not very precise, there's another way to do that,
is going back to the alphabetical,
lexicographical ordering, all right.
In this example, I've used characters,
but that can be with any type
that has a comparison operator.
You can see on this example that,
the left collection is smaller
than the second one, than the right one.
If it was in, if they were in a dictionary, right,
even though it's longer in terms of size,
it would come before in a dictionary,
and that's what lexicographical_compare returns
in the form of a Boolean.
Now, the last way to compare two collection
is to traverse them both
and stop whenever they start to differ.
That's what mismatch has, std::mismatch,
and it returns a pair of iterators
pointing to the respective position
of where they start to differ in the two collections.
Now, a classic thing to extract
in a collection is a position,
like looking for something,
or to look for something in a collection now,
essentially, two ways to do that.
It depends on whether the collection is sorted
or not sorted.
If it's sorted, we can use a binary search.
It's not sorted, we'll have to go all the way through
from the beginning.
Let's start with a not sorted case
because it has the lower number of algorithms.
Well, the simplest one, and very famous one,
is find, std::find takes a value,
takes, sorry, begin and end and value,
and returns the iterator where,
pointing to that value.
Now, or end, if that value is not found.
Now, it has a sort of like twin brother
that's much less famous, but quite useful as well
and it's called adjacent_find,
and that returns the first position
where two adjacent value occur
and are equal to the value search,
so adjacent_find takes a beginning and an end and a value
and returns the first position where two of those values
appear in a row.
On the sorted side, we are not really looking for a value,
because searching for a given value could,
it could be at several places, and since it's sorted,
they would be lumped together,
all right, so in this example,
if we're searching for orange,
we're actually looking for a sub range
that could be empty for orange is not there at all,
and that's what equal_range returns.
Search in a sorted collection will use equal_range.
There are other algorithms that sort of like,
look like it, lower_bound and upper_bound,
and that only indicate positions to insert elements.
Like, if I were to insert a new orange
at the left of the existing orange, I would use lower_bound,
and at the right of the existing orange,
I would use upper_bound, and there's this guy, binary_search
that returns a Boolean, just appear, unexpected,
because it takes a beginning and end and a value
and only tells you whether it's there or not,
but doesn't tell you where it found it,
and it uses a binary search, which is a good thing. (laughs)
Now, we've been looking for one value in a collection
like orange, we can also look for a range of value
in a collection, like, looking for the first occurrence
of the sub range in a big range,
like looking for that range on the left in the big range.
To do that, there's an algorithm
that has a very bizarre name,
because it's called search.
I don't know what you think, but for me,
search is almost the same as find,
except find says that it will find it, right,
and search says it will try to find it,
but search has nothing to do with find.
It searches a sub range into a range.
That's not the best name.
The best, I think the most curious name
in the whole STL library is what follows,
which is looking for a sub range but starting from the end.
It's called find_end.
(audience laughing)
That's good to know, isn't it?
And what we can search in a sub range
is any of the value in the little range
finding its first occurrence in the big range
and that's find_first_of.
And finally, we can search for a relative value
which is the largest one or the smallest one.
That's max_element or min_element, or both,
minmax_element, that returns a pair of iterators
pointing to the max and the min element,
sorry, the min and the max element.
Right, we've been around the largest part of the world.
Now we're going to move in my favorite place.
It's the glorious county of the algorithms on sets,
because they're glorious.
Well, what's a set to begin with?
It includes std::set but not only.
In C++, what we call a set is any sorted collection,
so a std::set is sorted and is a set,
but a sorted vector, for the purpose of the STL algorithms,
is also considered as a set.
Set_difference, which is my favorite algorithm,
takes two sets and returns
the elements that are in the first one
but not in the second one,
and its end phase is like that,
it produces this output via an output iterator.
I like it so much because it's useful all the time,
and it's also in linear complexity,
and it would be easy to implement in quadratic complexity
but it's in linear complexity because it uses the fact
that the input that's sorted,
and also its output is sorted.
Set_difference has siblings, set_intersection,
that returns the element that are both in A and in B,
set_union that returns element that are in A or in B,
so, everyone,
set_symmetric_difference, which is, I think,
the longest name yet, it's the longest name
in the whole library, fun fact.
That returns the elements that are in A and not in B,
and those that are in B and not in A,
includes, that returns a Boolean, that indicates
whether all the elements of B are also in A,
and merge, that takes two sorted collection
and puts them together into one big sorted collection.
Oh, that's it, that was too short.
Okay, let's take a step back.
We've been in the most populated regions of the world,
all over here, we now have just
these parts, small parts, to cover,
so maybe we'll take an hour after all.
So let's go to this place that, the place of movers,
so movers are algorithms that move things around.
The simplest way to move things around
is probably to copy them, and so std::copy,
it will be the simplest algorithm in the whole library.
Takes two collection, or takes one collection
with a begin and an end, and the output iterator
of a second collection or a back inserter
or stream iterator or whatever,
and copies every element of the first collection
into this output iterator.
There's also std::move.
Like, we know std::move like std::move,
but there's also std::move, the algorithm,
and it takes a begin and an end
and our output iterator just like std::copy,
and it calls the usual std::move on every element
of the input collection, so after std::move the algorithm,
all the elements of the input collection are moved,
and to swap the contents of two ranges, there's swap ranges
and they all obviously have to be the same size.
Now, a little exercise.
Let's say that we've got a collection at the top,
one, two, three, all the way up to 10,
and we would like to write some code
and use an algorithm that we've seen
from the beginning of our talk up until now,
to turn it into the collection at the bottom,
so we want to copy the first five elements
four positions down.
Which algorithm do you think we could use to do that?
Come on, there's less than 105 possible answers.
Yeah. - Copy.
- Copy, absolutely.
So, we'll use copy.
I thought to use copy as well,
and then it, I think it crashed when I used it.
So let's see what happens, what's happening.
So, the first thing copy does
is to copy the first element, right, to the output,
so I don't know if you are thinking
about that, but I put as an output iterator,
begin, plus four, or plus, sorry, plus three.
And then we've lost a value, we've lost four, right?
Which is annoying.
What we would like to do is to copy,
but starting from the last one, right?
Because we don't care about the eight
that used to be there, and then move all the way back up,
yeah, so what we would really like to do
is to copy, but backwards.
That's what we're gonna say, right?
- Reverse copy. - Yeah, copy backwards.
Pretty much the same, hey.
And there's also move backwards
that does the same thing, but moves.
Right, so that's the movers.
Now, just down around the corner,
there's the value modifiers, which I would have expected
to be more more numerous, but there's just four of them
in a quite large piece of land.
So, those guys actually change the values
inside of a collection, so, how can we change the values
inside of a collection?
One easy way to do that is to put the same value everywhere.
That's what fill does, it takes a begin, an end and a value
and copies that values all the way through.
Another way that's a bit similar in spirit
is to use a function that can be called
with no argument, and we call that function once
for every element of the collection.
That's generate.
Another way to fill a collection that's quite useful
to have, like, I don't know, a unit test, stuff like that,
to have quickly a sequence of values,
is iota, which has a funny name, I think.
It takes begin, an end, a value,
put that value at the beginning,
and then increment it and increment it,
until it fills the whole collection with increments.
And finally, there's replace,
that quite expectedly
replaces the values in a collection,
like replace 42, 43 will replace every 42 by a 43
in a collection of ints.
Right, now, jumping above the water,
there's this island of the structure changers.
What I call the structure changers, is the algorithms
that after they've done their job,
the collection doesn't look the same even from afar.
So, there are two of them, remove and unique.
Remove is used
to remove values, but it has something peculiar with it.
Since STL algorithms only act as the iterators
of a collection and not the collections themselves,
they can't change the size for example of a collection.
They can't perform an erase in a collection.
All they can do is to change the values
between the begin and the end,
so what remove, std::remove does,
is to pull up the collection
by leaving only those that should not be removed,
so if we want to remove the 99s,
we use std::remove, begin, end, 99,
and it will pull up everything that's left without the 99
and what's at the end is not specified.
We can't rely on it, it could be the element
that were there before, it could be the 99s,
it could be anything else.
It could change, it could vary from compiler to compiler,
from version to version, so we can't rely on that.
And to actually remove those element
from, say, a vector, we have to call a method
on the container, like erase on a vector.
We'd like to erase all the element that are crossed out,
and to do that, remove returns an iterator
that points to the first element that's here crossed out,
so we have to erase between what remove returns
and the end,
which is quite of a mouthful of code
to remove the 99 in the collection, isn't it?
I think that we should not use that code,
and we should drop it into a function with a nicer name,
and use this in our everyday code.
I reckon that's been a proposal by STL,
Mr. STL, the guy, to add those functions to the standard,
but it hasn't been accepted yet, so until it is,
if it is, we should just wrap this function,
it's quite an easy thing to do.
If you want to grab the code, it's been published
on Fluent C++ last week, I reckon.
Unique does something quite similar.
It removes the duplicates in a collection,
or more precisely the adjacent duplicates in a collection.
Right, so if we do unique here,
this collection has several 99s,
and it will remove only the adjacent 99,
because it has a linear complexity,
it will only traverse the collection once,
and unique will also pull up the values
that are to be left and return an iterator to the values,
to the, yeah, the values that are crossed out.
Those value, we can't rely on them, just like remove,
and we have to erase them, quite a bit of code,
from not much, and we should package that into a function
with a better name.
Whether unique is a better name, I'm not sure,
but we should at least package that into a function.
Have we got a question there?
Sorry, I couldn't hear that.
- [Man] Sorry, one was not removed.
- Indeed, one was not removed.
That's a good observation, and it's not removed
because there's no adjacent ones in the collection.
Thanks for that.
We've unlocked a new rune, copy.
We've seen all the algorithms that are combined with copy
in the STL algorithms library,
so, copy, you can tack it on an algorithm
like those algorithms and it creates new algorithms
that do the same thing but in a new collection,
so like remove or unique or replace or whatever,
they operate in place.
Those guys, they leave the collection untouched
and they take an output iterator
and they produce the output to that output iterator.
We've also unlocked if.
That, as a predicate, so, where,
say, find, for example was taking a value,
find_if takes a predicate
but does essentially the same thing.
And we're going to go to that part
we started the talk with, the lonely islands,
with for_each and transform.
Let's start with transform.
There's nothing wrong with for_each or transform.
The reason why I put them on lonely islands
is because I couldn't see in what family they would belong.
I think they sort of like, separate,
even though it's not a problem in itself.
So, transform, essentially it applies a function
to the elements of a collection,
so it takes a collection, begin, end,
a function, f, and an output, like an output iterator,
such as this back_inserter here.
And it produces the, well, the application
of that function to every element of the collection.
What's a bit less famous about transform
is its other overload.
The one that takes two collections
and a function that takes two parameters, all right.
So.
It applies that function just like transform
with one input collection to the two collection
by feeding the two collection into f
for each element and producing the output.
Yeah, like for example, if you'd like to sum
the elements of two collection two by two,
you can do that with one line of coding with transform
and f would be std::plus.
And there's for_each, and for_each, if we think
about what it does, the for_each takes a begin,
an end, and some piece of code, a function,
it could be a Lambda for example, or a regular function,
and it applies that function to every element.
What's interesting to note is that it doesn't care
about what f returns,
so, for the purpose of for_each,
f might as well return void.
Now, what does a function
that returns void do?
Anyone, an idea?
- [Man] It makes side-effects.
- Yeah, exactly, side-effects.
For_each is made to perform side-effects.
Now, are side-effects a good thing or a bad thing
is absolutely not the purpose of that talk,
even thought I think that some case are useful,
with side-effects, but for-each is made
for doing side-effects.
That could be side-effects on the elements themselves,
modifying the elements of a collection,
or that could be side-effect in the more general sense
of sending stuff to the logger or whatever,
right, but the for_each is made for side-effects.
Using for_each for making anything else
such as a partition or a find or whatever,
is not the right usage of for_each,
there are algorithms that are more appropriate
than for_each, and if there's no algorithm more appropriate
than for_each in the STL algorithms,
then the algorithms outside of the STL algorithm,
we briefly talk about them in a minute.
Okay, so, we've been literally
all around the world.
We've started down there, we've been in land of queries,
the glorious county of algorithm on sets,
movers, the value modifiers, and the structure changers,
and even up there.
There's only one place we haven't been yet,
the raw memory.
So we're gonna finish up with that one.
What does that mean, algorithms in the raw memory?
Well, if you think back to fill
and copy and move, they have one thing in common.
Does anyone see what they have in common?
It's that they use the assignment operator.
For_each assigns a 42, I'm sorry.
Fill assigns a 42, copy assigns with the copy assignment,
move assigns with the move assignment.
They all use an assignment.
Now, if we use an assignment operator,
it means that the object has been constructed,
and hopefully that's true.
But, in some rarer cases,
we have objects that are not constructed yet
if we have just the memory, just chunks of memory
that we got from somewhere, and we'd like to construct
instead of calling the assignment operator,
we'd like to call constructor on those chunks of memory.
For that, we use the uninitialized counterpart,
uninitialized_fill, uninitialized_copy, uninitialized_move,
and these use constructors, so this takes,
this is the constructor that takes a value,
this is a copy constructor, this is a move constructor.
So, in practice, you get a chunk of memory from somewhere,
like shared memory or wherever,
and you'd like to fill it with values
but you can't call fill because you can't call
the assignment operator because there is just raw memory,
right, it hasn't been initialized yet.
So, we need to call the constructor.
This is what uninitialized_fill does, right,
it takes the two pointers for at the beginning and the end
and calls the value constructor,
leading to an initialization
of that chunk of memory.
Now, once we're done with it, we want to call the destructor
of those guys because we've been in charge
of the constructor, we're probably also in charge
of calling the destructor,
and that's what std::destroy does.
Kind of like, sounds familiar, right.
Destroy, but we don't really use it on a day-to-day basis,
but that's what it does,
so, destroy will destroy the,
calls the destructor of all the elements in that collection
and if we'd like to call the other kinds of constructor,
such as default constructor or value constructor,
what value initialization goes in it,
uninitialized_default_construct,
uninitialized_value_construct,
and we've unlocked our final rune, the underscore n,
that changes a little the interface
like instead of taking a begin and an end,
it takes a begin and a size.
Let's take an example.
So, still using fill, so fill takes a begin,
an end, value, put that value all the way through,
fill_n takes a begin and a size, okay,
and a value and it fills the first five elements
starting from begin, it fills them with the 42s.
Now, what do you think this line of,
this third line of code does?
Imagine that collection is a vector,
an empty vector, for example.
What do you think is in collection after executing
that third line of code?
Say that a little louder.
- [Man] Insert five elements equal to 42.
- Absolutely.
That would insert five elements equal to 42,
right, because it would assign
through the back_inserter operator, five times.
Right, this time we've been around the world, everywhere.
You know everything in the STL algorithms library.
Now what?
Well, for one thing, you can use them in your codes
to make it more expressive, all right.
You can think about if you want to write a for-loop,
think about if you could use an algorithm instead
or if you could replace existing for-loops
that are like a bit of a problem in your code,
by an STL algorithm to make it smoother to work with,
and if you'd like a second pair of eyes,
I always like to read code, so you can reach out to me
and show me your code, then I can have a look
and think about which algorithm with you
we can put in your code,
I'd be more than happy to look at code.
If you'd like to see more algorithms,
there are some outside of the STL
and the library nextdoor is Boost.
There's those Boost.Algorithms,
which contains all the algorithms are in the same spirit
as the algorithms of the STL.
Soon I will publish on Fluent C++ a series of articles
going all through all these guys.
To go further, it's a good thing to understand
in depth, the STL algorithms.
I think it's a good investment of our time
about their complexity, about prerequisite,
post-requisite,
think about sorted collections for example,
and see how they're implemented,
and that's often quite instructive.
And finally, if you use them on a day-to-day basis,
you can get inspired.
You can have a better intuition
of what abstractions work well,
and that's fundamental for writing good code
and that intuition, you can use that in your own code.
And once you're familiar enough,
you can start thinking about writing your own algorithms
so what sort of algorithm can we write?
Well, we can combine an algorithm, an existing algorithm
with an existing rune to create a new combination
like sort_copy doesn't exist, for example,
or we could add a city on the map.
If I were to add something, I think I would add something
to the algorithm on sets, do something like an algorithm
that would have three outputs,
those that are in A not in B, B not in A,
and both in A and B, all in a single pass, I think.
I don't know.
If you think about things to add,
I'm happy to hear about that, too,
or start a new family.
If you'd like to get the map, you can download it
on fluentcpp.com/getTheMap
and there you can also order the poster
that you can hang in your homes and offices,
and for the people in the room,
if you'd like to acquire a poster right now,
like this one, I've got a bunch of them with me
so you can come and see me after the talk.
If you'd like to get in touch,
you can find me on Twitter, @JoBaccara
and if you're interested in reading about the STL
or about expressive code in general,
then check out fluentcpp.com, Fluent C++
with a new post every Tuesday and every Friday,
about expressive code in C++.
Thanks.
(audience applauding)