There is an even better and shorter way to use this sort function. In
C++20, we got introduced to the new concept of ranges.
Ranges are abstraction on top of iterators. We can use them
instead of explicitly providing iterator functions to
algorithms like sort. To do this,
we need to use the sort function from the range's namespace.
Now we can just pass the array itself, because most sequential
containers can be considered as a range. Range functions will
implicitly look for iterators and use them instead of us,
just like this range‑based for loop. If I compile this,
you can see that this function works in the same way, but now the code is
cleaner and uncluttered from iterators. Algorithms under this range's
namespace are also known as constrained algorithms, because you only need
to pass a range to make them work.
Check out all of the available constrained algorithms on the CPP
reference site. Just like iterators, ranges are a big topic,
so we will just scratch the surface of their usage in regards to standard
arrays. Ranges were previously available from the GitHub repository of a
famous C++ developer, Eric Niebler, but since the C++20, they were
officially added to the standard library.
Now that we know how iterators work, I will turn this
array back into a standard array.
Let's say that I want to see the elements from this array in a different way.
I want to filter out only the odd numbers and then
multiply those numbers with 10.
First,
I will define another array of the same size in case all of the elements
are odd numbers. Then I can use the copy_if function from the algorithm
library to copy elements from the original array to this odd array. We
need to provide both iterators from the first array, and the beginning
iterator from the second one.
The third argument is a function which will take in
each element from the original array.
Let's pass the lambda here, because we won't need this function later.
This lambda will take in each element as a parameter,
and if this function returns true,
that element will be copied to the new array, and this
will only be true if the element is odd.
So this odd array will look something like this.
It will contain all of the odd numbers, and since we have one even number,
that place will stay 0.
Usually, this element would be a garbage value, but by adding
this empty initializer list, I instructed the compiler to set
all of the array elements to 0.
Okay, and now I need to multiply these numbers by 10.
I need to create a new timesTenArr, and now let's use the
transform function to modify each element.
The function takes in the same set of arguments.
The returned value from this lambda will be stored as an
element of the new array, and that's it.
Now I can output the elements to see if it worked.
Let's compile this.
It seems that it works.
Although, I don't like this 0 at the end, but to fix that,
we would need to somehow create a new smaller array of this
specific size, and move non‑0 numbers there.
But that would mean that I need to know the size of this small array at runtime,
which means that I need to create some kind of a dynamic
array, which complicates things even more.
So much code for just a different perspective on the original array.
We could have used the algorithm alternatives from the range's namespace,
but this doesn't help much, we just avoid passing
the iterators of the first array.
Fortunately, constrained algorithms are not the only application of
ranges, there is also a special ranges library, which provides us with
new ways of working with sequential data.
So, let's include that library. Ranges also provide
us with the concept of views, which we can pipe together to create a chain,
something like a UNIX pipeline.
And now, instead of creating multiple intermediary arrays,
I can just do everything here, inside of the loop. First, I want to pass
this array to the filter function from the views namespace.
I am using this pipe operator to do that.
The output of the expression on the left is passed down to
the input of the expression on the right,
but in this case, this is done implicitly. We don't have to define any
ranges or iterators. This function is known as an adapter, because it will
take in a range and adapt it to something else.
In this case, an input range is the array itself, and
the output is something called a view.
Views work kind of like temporary ranges.
This filter function will create a temporary range,
which only contains odd numbers.
Filter function works kind of like a copy_if algorithm,
but we only need to pass the lambda with the condition.
The output of this function is a temporary range,
which I then want to pass down to the transform function.
Transform function will multiply all of the elements by
10 and return another view.
So, the for loop will actually loop over this final
temporary range, or we can say, a view.
We don't have to modify the original array or define other arrays to
make the necessary modifications, we can just create a chain of views,
and you can see the reason for that name,
it gives us a different view of the original array. And, if I now compile this,
you can see that the result is even better, no 0s at the end.
The range's library is really powerful, so don't hesitate to use it.
We're almost done with static arrays, I just want to mention
a few more things. For the last example, let's create a
two‑dimensional array. As we know,
this is array of arrays, so I need to define the type of
elements as another standard array.
My 2d array will have two rows and three columns, so that means two
smaller subarrays. Since standard array is a class, this array is
actually an array of objects, and arrays of objects need to be
initialized in a different way.
You may think that something like this will work, we wrap all of the
elements of the subarrays in special curly braces,
but it won't, because when working with arrays of objects,
this initializer list becomes ambiguous.
You need to add another set of curly braces around the
whole initializer list to make this work.
The first set of braces is for the standard array class,
the second one is for the internal array, and then all of these
braces inside define the values for the specific object, in this
case, the subarray. To see if this works,
let's loop through the array.
Since this is a 2d array, we'll need two for loops.
The first one will loop over subarrays, and the second
one over the elements of that subarray.
We said that the implementation of this range‑based for loop looks
something like this, but you can see that in each iteration, the row
variable is making a copy from the value of the current element. Instead,
we want to do something like this, to make this row variable a reference
to the element itself. And we can definitely do this, we just need to turn
this row alias into a reference. When working with objects, always use the
range‑based loop like this, with references, because we don't want to make
a copy of each element, this would degrade the overall performance.