Just Software Solutions

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: , ,
Stumble It! stumbleupon logo | Submit to Reddit reddit logo | Submit to DZone dzone logo

Comment on this post

If you liked this post, why not subscribe to the RSS feed RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.

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(); }

by Olivier S at 09:04:30 on Monday, 25 February 2019

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.

by Anthony Williams at 09:12:37 on Thursday, 21 March 2019

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.

by Alex at 09:14:05 on Thursday, 21 March 2019

Add your comment

Your name:

Email address:

Your comment:

Design and Content Copyright © 2005-2024 Just Software Solutions Ltd. All rights reserved. | Privacy Policy