Begin and End with range-based for loops
Saturday, 23 February 2019
On slack the other day, someone mentioned that lots of companies don't use range-based for
loops
in their code because they use PascalCase
identifiers, and their containers thus have Begin
and
End
member functions rather than the expected begin
and end
member functions.
Having recently worked in a codebase where this was the case, I thought it would be nice to provide a solution to this problem.
The natural solution would be to provide global overloads of the begin
and end
functions: these
are always checked by range-based for
if the member functions begin()
and end()
are not
found. However, when defining global function templates, you need to be sure that they are not too
greedy: you don't want them to cause ambiguity in overload resolution or be picked in preference to
std::begin
or std::end
.
My first thought was to jump through metaprogramming hoops checking for Begin()
and End()
members that return iterators, but then I thought that seemed complicated, so looked for something
simpler to start with.
The simplest possible solution is just to declare the functions the same way that std::begin()
and
std::end()
are declared:
template <class C> constexpr auto begin(C &c) -> decltype(c.Begin()) {
return c.Begin();
}
template <class C> constexpr auto begin(const C &c) -> decltype(c.Begin()) {
return c.Begin();
}
template <class C> constexpr auto end(C &c) -> decltype(c.End()) {
return c.End();
}
template <class C> constexpr auto end(const C &c) -> decltype(c.End()) {
return c.End();
}
Initially I thought that this would be too greedy, and cause problems, but it turns out this is fine.
The use of decltype(c.Begin())
triggers SFINAE, so only types which have a public member named
Begin
which can be invoked with empty parentheses are considered; for anything else these
functions are just discarded and not considered for overload resolution.
The only way this is likely to be a problem is if the user has also defined a begin
free function
template for a class that has a suitable Begin
member, in which case this would potentially
introduce overload resolution ambiguity. However, this seems really unlikely in practice: most such
function templates will end up being a better match, and any non-template functions are
almost certainly a better match.
So there you have it: in this case, the simplest solution really is good enough! Just include this
header and you're can
freely use range-based for
loops with containers that use Begin()
and End()
instead of
begin()
and end()
.
Posted by Anthony Williams
[/ cplusplus /] permanent link
Tags: cplusplus, for, range
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Design and Content Copyright © 2005-2024 Just Software Solutions Ltd. All rights reserved. | Privacy Policy
3 Comments
Thanks for the tip. This is very C++11 minded. I guess that if you are lucky enough to have C++14 in your toolchain, you could spare yourself the explicit return type with something like:
template <class C> constexpr auto begin(C &c) { return c.Begin(); }
Putting the functions in the global namespace ensures that they are picked up by the range-based for loop, even when the container is in a namespace, which is the whole point.
They will only clash with classes or namespaces named "begin" and "end", which strikes me as unlikely, or if there is another overload of "begin" or "end" in the global namespace that takes a single container argument that could be ambiguous, which again seems unlikely to me.
Finally, you only need to include the header where you intend to use the range based for loop with containers with PascalCase members, so it is restricted to those source files where you do this.
If it works better for you to put the functions in a namespace and have a using declaration where you wish to use them for range-based for loops, do that.
There is no SFINAE involved in this last version though.
Regarding the header, I'm kind of concerned that it puts those function in the global namespace.