Programming is writing, and writing is visual. We should explore software as read not code as executed. Less code, more software.
Iteration
In his Overload 45 (October 2001) article, minimalism - omit needless code , Kevlin worked on the simple problem of printing the std::string s inside a std::vector to std::cout . An early version looked like this:
typedef vector<string> strings; typedef strings::iterator iterator; for (iterator at = items.begin(); at != items.end(); ++at) { cout << *at << endl; }
A later version looked like this:
class print { ... }; for_each(items.begin(), items.end(), print(cout));
And the final version looked like this:
typedef ostream_iterator<string> out; copy(items.begin(), items.end(), out(cout, "\n"));
Readability
The main source of repetition is repetition. When programming in C++ you often find yourself making a call to a template function where two of the arguments are created by calling begin and end on a container. This quickly gets repetitive. The repetition itself suggests several solutions. Ranges are basic building blocks of the STL design and it is surprising they are not a visible and explicit artefact of its type system. For example:
template<typename iterator> class range { public: // types typedef iterator iterator; public: // c'tor range(iterator start, iterator finish) : from(start), until(finish) { } public: // properties iterator begin() const { return from; } iterator end() const { return until; } private: // state iterator from, until; };
This would make containers substitutable for a range over themselves which would in turn allow STL algorithms to expect a range argument rather than two iterator arguments. For example:
template<typename range, typename function> function for_each(const range & all, function apply) { return for_each(all.begin(), all.end(), apply); }
This version of for_each is not part of STL so you have to provide it yourself. Once you've done this you can rewrite this:
for_each(items.begin(), items.end(), print(cout));
as the impressively readable:
for_each(items, print(cout));
Understanding this statement is a complete no brainer. It clearly and concisely expresses its intention. However, it does require you to create the print class (which hides away the "\n" detail). Alternatively, you could pull the same trick by writing a non standard version of copy :
template<typename range, typename output> output copy(const range & source, output sink) { return copy(source.begin(), source.end(), sink); }
allowing the beautifully readable:
copy(items, out(cout, "\n"));
Preference
Which versions do you prefer? The explicit iteration, the for_each versions, or the copy versions? Can you explain why?
I prefer the copy versions. The name for_each is itself a subtle but strong hint that iteration is involved. It suggests that each of the items will be printed to std::cout , one at a time. The iteration comes first ( for_each , leftmost), followed by the action (print, rightmost). In contrast, the copy is subtler and simply suggests copying the items to cout . It has more of a "single operation" feel to it. The iteration is not visible (and the "\n" is). This difference is important, not because you should always try to hide all iteration, but because the intention was to "write the items to cout ". In other words, the copy version is a simpler and more direct expression of the problem. Lots of code is too solution focused; it lacks an expression of the problem and hence is hard to understand and costly to maintain.
Many thanks to Kevlin for an insightful review of a first draft of this article.