Easier To Use, And More Expressive
One of the goals for each evolutionary increment in C++ is to decrease the probability of an average programmer from making mistakes by supplanting “old style” features/idioms with new, easier to use, and more expressive alternatives. The following code sample attempts to show an example of this evolution from C++98/03 to C++11 to C++14.
In C++98/03, there were two ways of clearing out the set of inner vectors in the vector-of-vectors-of-doubles data structure encapsulated by MyClass. One could use a plain ole for-loop or the std::for_each() STL algorithm coupled with a remotely defined function object (ClearVecFunctor). I suspect that with the exception of clever language lawyers, blue collar programmers (like me) used the simple for-loop option because of its reduced verbosity and compactness of expression.
With the arrival of C++11 on the scene, two more options became available to programmers: the range-for loop, and the std::for_each() algorithm combined with an inline-defined lambda function. The range-for loop eliminated the chance of “off-by-one” errors and the lambda function eliminated the inconvenience of having to write a remotely located functor class.
The ratification of the C++14 standard brought yet another convenient option to the table: the polymorphic lambda. By using auto in the lambda argument list, the programmer is relieved of the obligation to explicitly write out the full type name of the argument.
This example is just one of many evolutionary improvements incorporated into the language. Hopefully, C++17 will introduce many more.
Note: The code compiles with no warnings under gcc 4.9.2. However, as you can see in the image from the bug placed on line 41, the Eclipse CDT indexer has not caught up yet with the C++14 specification. Because auto is used in place of the explicit type name in the lambda argument list, the indexer cannot resolve the std::vector::clear() member function.
With C++03 you could use std::for_each with std::mem_fun_ref(&std::vector::clear). I tend to think that’s better than all the others except the C++11 one that uses range-based for. Although this sort of “functional” approach can get hard to read for anything more complex. A separate functor, a lambda, or ranged-based for can quickly become a better choice.
Ranged-based for is great except when you want to iterate over less than the full range. I really wish C++11 had included some adaptor classes to help make ranged-based for more flexible.
Surely since the lambdas only operate on its argument, there is no need for reference capture ?
Yes, you’re right. Good catch!
C++ 11 option A has the tersest and most readable loop header (no unneeded std::begin and std::end) and loop body (no repeated types, no syntactic “noise” from lambda functions). When would the other new variations be useful?
For the reasons you state, I prefer C++11 option A over all others too. Perhaps people/orgs who strive to minimize loops in their code prefer the for_each() options.
I find the main cases where I don’t use ranged-based for—as hinted at in my previous comment—are when I don’t want to iterate over the entire range. There are tricks to doing that with ranged-based for. e.g. A simple Range class that takes two iterators and has begin & end member functions. But since they aren’t standard, such an idiom is less likely to be immediately recognized by the reader.
I can tell you’re a good programmer. You care about the future maintainers more than being clever. 🙂
The loops in all their versions are needless here –
_vecvecDouble.clear()
destructs all elements of the vector, in this case – the vectors.
It’s an academic example 🙂
I do understand that it is an academic example, but I think it would be a much better example if you called the function something like “clear_inner_vectors” and didn’t clear the outer vector in the function.
My fear is that a novice seeing this example would mistakenly assume that _vecVecDouble.clear() would not destruct the inner vectors, and then write code based on that mistaken assumption.
Great point I’ll fix it soon. Of course, since it’s posted as a .png and not inline text, as many people have pointed out, it takes longer 🙂
C++98/03 didn’t have std::begin and std::end as free functions. You had to use the std::vector::begin and end versions.
Ah yes. I noticed that mistake a few days ago and was wondering when someone was going to point it out. Good catch!
Good example!!
The 8/26/15 Updated Code’s line 27:
Second argument should be vecVecdouble.end( )
Can I reproduce this article to Traditional Chinese ?
Damn! Nice catch Ted. I’ll have to update it one more time now 🙂
You can reproduce it in any language you’d like. I’d be flattered if you did.