Contents
Here we go again with two features that have been added in C++11 that can greatly simplify the code we write.
std::begin & Co.
std::begin
is a free function template that by default does nothing more than calling a begin
member function on its argument and returning the result. Its twin is std::end
which, as you might have guessed, does the same with an end
member function of the argument.
So, if you have one of the usual standard containers, you can write a loop over the complete container as follows:
auto numbers = std::vector<int>{1, 1, 2, 3, 5, 8}; for (auto iter = std::begin(numbers); iter != std::end(numbers); ++iter) { std::cout << *iter << '\n'; }
At first sight, this may not look like much of an improvement, but we have only just started. There are a few specializations of std::begin
and std::end
already built into the language, for std::initializer_list
, std::valarray
and plain C-style arrays, which return suitable iterator-like objects, e.g. T*
for arrays of T.
Especially plain arrays are still often used, and they have no begin
and end
member functions, so in the past a full iteration loop over arrays had to look different than over any standard library container. However, since C++11 you can use the exact same code, making it possible to write template functions that can treat arrays, vectors and the like equally:
int numbers[] = {1, 1, 2, 3, 5, 8}; for (auto iter = std::begin(numbers); iter != std::end(numbers); ++iter) { std::cout << *iter << '\n'; }
Of course, there are also std::rbegin
and std::rend
for reverse iteration. In addition, since C++14 there are versions that explicitly take the parameter as a constant and return the corresponding const_iterator
for standard containers and const T*
for arrays. Those functions are called std::cbegin
, std::cend
etc.
Adopting third party containers
Suppose you have a third party library that defines some containers that instead of C++ style iterators provides some other means of iteration. Maybe they provide a Java style iterator class with hasNext
, next
and get
functions or they only provide an indexing operator or something different.
In the past, loops over such containers had to be written different from loops over standard library containers. Even though it is usually possible to write a custom iterator class that provides operator++
, operator*
and anything you need for iterators, it is not possible to add begin
and end
member functions to such a container class that would return the corresponding custom iterators.
std::begin
and std::end
as free functions can be specialized for any such container to return the custom iterators, making it possible to use the standard form of full iteration loops, making the code more uniform, which reduces unnecessary attention to implementation details.
Prefer to use `begin(c)` and `end(c)` over `c.begin()` and `c.end()`, since they are more likely to continue to do the right thing if the type of `c` changes.
Which brings us to the second feature that beautifully rounds up the full iteration loops:
Range based for loops
Since C++11 there is another style of for loops, which looks like this:
for (element_decl : range_expr) statement
Which is roughly correspondent to
{ auto && __range = range_expr ; for (auto __it = begin(__range), __e = end(__range); __it != __e; ++__it) { element_decl = *it; statement } }
Note that this is quite a simplification of the actual wording in the C++ standard, but the spirit remains: It is a full range loop from begin()
to end()
of the range denoted by range_expr
. Both begin
and end
are looked up via argument dependent lookup with the speciality that namespace std
is always considered, too.
That means, that if you provide anything as range_expr
where calling free functions begin
and end
makes any sense, either because you provided the functions in the associated namespace or because you overloaded std::begin
and std::end
, this loop construct will just work.
So, the initial examples of full range loops could be rewritten like this:
int numbers[] = {1, 1, 2, 3, 5, 8}; //or a vector... for (auto&& num : numbers) { std::cout << num << '\n'; }
Note the auto&&
for the loop element variable – this kind of declaration always works, independent of if you pass a const or non-const range, and regardless of what kind of value (r-value or l-value) the dereferencing of iterators will return. In fact, there has been a proposal to enable the omission of a type specifier for num
making it auto&&
by default.
Prefer using `auto&&` as type specifier for the loop element variable.
There are some quirks to the lookup rules:
- If the range expression is a plain array, no functions will be called, and range based for will just use the pointer to the array and one past the end.
- If the range expression is of a type that has `begin` and `end` member functions, those will be called instead of any free functions.
However, those special rules only matter in corner cases, or if you do something fancy and unexpected, which is usually not a good idea. So, for completeness a few guidelines for overloading begin
and end
functions:
- Don’t overload or specialize any `begin` and `end` functions for plain arrays.
- If you write your own container classes, provide the right `begin` and `end` member functions as well as free functions and `std::begin` and `std::end` specializations (if possible).
- Be consistent: `std::begin` and `std::end` specializations, free `begin` and `end` functions and `begin` and `end` member functions should do exactly the same to avoid confusion and surprises – implement one in terms of the others.
If you stick to these rules and to sensible and readable code in general, range based for will as good as always work (I know of no exceptions, but with C++ and its different compilers you never know for sure). So, since range based for looks different to other for loops, it very clearly says “This is a full range loop – no exceptions”.
Prefer range based for over classic iterator loops from `begin` to `end`.
Permalink
Does it make sense to use “erase” in a loop for std::vector?
std::vector v{ 1, 2, 3, 4, 5, 6 };
std::vector::iterator it = v.begin();
while (it != v.end()) {
if (condition)
it = v.erase(it); // after erasing, ‘it’ will be set to the next element in v
else
++it; // manually set ‘it’ to the next element in v
}
See more: https://sodocumentation.net/cplusplus/topic/511/std–vector
Permalink
Often it is not considered good practice to use raw loops like this for containers, especially in this case, where the container is modified while iterating over it. The idiomatic way to achieve the same would be
const auto newEnd = v.remove_if(begin(v), end(v), condition);
v.erase(newEnd, v.end());
Permalink
A bit late in the game, but this paragraph needs some help:
“If the range expression is a plain array, the will no functions be called, and range based for will just use the pointer to the array and one past the end.”
Other than that, these articles are awesome.
Permalink
Hi Patty,
thanks for the hint – it’s never too late to fix things like that 😉
I’m glad you like the articles!
Permalink
How to find out what type is returned by begin() and end() functionts, because I have an exercise where is required to write without auto, decltype type and alias.
Permalink
There are several possibilities. First, you can just say
int
instead ofauto
and the compiler will give you an error message telling you that some type is not convertible to int. However, this will usually give you a messy templated type while there are typedefs/type aliases that are canonical and more pronounceable.For
begin()
andend()
on containers there is a convention that is used throughout the standard library with little or no exceptions:some_container_type::begin()
returns asome_container_type::iterator
(orconst_iterator
if you call it on constant objects). The same goes forend
.In the end, you can always use a C++ reference to look up what the functions of a standard container return: http://en.cppreference.com/w/cpp/container/vector/begin
Permalink
Permalink