One benefit of std::begin
and std::end
is that they serve as extension points
for implementing standard interface for external classes.
If you'd like to use CustomContainer
class with range-based for loop or template
function which expects .begin()
and .end()
methods, you'd obviously have to
implement those methods.
If the class does provide those methods, that's not a problem. When it doesn't,
you'd have to modify it*.
This is not always feasible, for example when using external library, esspecially
commercial and closed source one.
In such situations, std::begin
and std::end
come in handy, since one can provide
iterator API without modifying the class itself, but rather overloading free functions.
Example: suppose that you'd like to implement count_if
function that takes a container
instead of a pair of iterators. Such code might look like this:
template<typename ContainerType, typename PredicateType>
std::size_t count_if(const ContainerType& container, PredicateType&& predicate)
{
using std::begin;
using std::end;
return std::count_if(begin(container), end(container),
std::forward<PredicateType&&>(predicate));
}
Now, for any class you'd like to use with this custom count_if
, you only have
to add two free functions, instead of modifying those classes.
Now, C++ has a mechanisim called Argument Dependent Lookup
(ADL), which makes such approach even more flexible.
In short, ADL means, that when a compiler resolves an unqualified function (i. e.
function without namespace, like begin
instead of std::begin
), it will also
consider functions declared in namespaces of its arguments. For example:
namesapce some_lib
{
// let's assume that CustomContainer stores elements sequentially,
// and has data() and size() methods, but not begin() and end() methods:
class CustomContainer
{
...
};
}
namespace some_lib
{
const Element* begin(const CustomContainer& c)
{
return c.data();
}
const Element* end(const CustomContainer& c)
{
return c.data() + c.size();
}
}
// somewhere else:
CustomContainer c;
std::size_t n = count_if(c, somePredicate);
In this case, it doesn't matter that qualified names are some_lib::begin
and some_lib::end
- since CustomContainer
is in some_lib::
too, compiler will use those overloads in count_if
.
That's also the reason for having using std::begin;
and using std::end;
in count_if
.
This allows us to use unqualified begin
and end
, therefore allowing for ADL and
allowing compiler to pick std::begin
and std::end
when no other alternatives are found.
We can eat the cookie and have the cookie - i. e. have a way to provide custom implementation
of begin
/end
while the compiler can fall back to standard ones.
Some notes:
For the same reason, there are other similar functions: std::rbegin
/rend
,
std::size
and std::data
.
As other answers mentions, std::
versions have overloads for naked arrays. That's useful,
but is simply a special case of what I've described above.
Using std::begin
and friends is particularly good idea when writing template code,
because this makes those templates more generic. For non-template you might just
as well use methods, when applicable.
P. S. I'm aware that this post is nearly 7 years old. I came across it because I wanted to
answer a question which was marked as a duplicate and discovered that no answer here mentions ADL.
std::
all the time. – Leafage