Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

STL Algorithms vs. Hand-Written Loops


I wish that were the end of the story, because I think it's a strong finish. Alas, this is a tale that refuses to go gentle into that good night. Algorithm names are more meaningful than bare loops, it's true, but specifying what to do during an iteration can be clearer using a loop than using an algorithm. For example, suppose you'd like to identify the first element in a vector whose value is greater than some x and less than some y. Here's how you could do it using a loop:

vector<int> v;
int x, y;
...
// iterate from v.begin() until an
// appropriate value is found or
// v.end() is reached
vector<int>::iterator i = v.begin();
for( ; i != v.end(); ++i) {
  if (*i > x && *i < y) break;
}
// i now points to the value
// or is the same as v.end()

It is possible to pass this same logic to find_if, but it requires that you use a nonstandard function object adapter like SGI's compose2:

// find the first value val where the
// "and" of val > x and val < y is true
vector<int> iterator i =
  find_if(v.begin(), v.end(),
       compose2(logical_and<bool>(),
           bind2nd(greater<int>(), x),
           bind2nd(less<int>(), y)));

Even if this didn't use nonstandard components, many programmers would object that it's nowhere near as clear as the loop, and I have to admit to being sympathetic to that view.

The find_if call can be made less imposing by moving the test logic into a separate functor class (i.e., a class declaring an operator() member function):

template<typename T>
class BetweenValues:
  public std::unary_function<T, bool> {
public:
  // have the ctor save the
  // values to be between
  >BetweenValues(const T& lowValue,
                const T& highValue)    
  : lowVal(lowValue), highVal(highValue)
  {}
  // return whether val is
  // between the saved values
  bool operator()(const T& val) const
  {
    return val > lowVal && val < highVal;
  }
private:
  T lowVal;
  T highVal;
};
...
vector<int> iterator i =
  find_if(v.begin(), v.end(),
          BetweenValues<int>(x, y));

But this has its own drawbacks. First, creating the BetweenValues template is a lot more work than writing the loop body. Just count the lines. Loop body: one; BetweenValues template: twenty-four. Not a very good ratio. Second, the details of what find_if is looking for are now physically separate from the call. To really understand the call to find_if, one must look up the definition of BetweenValues, but BetweenValues must be defined outside the function containing the call to find_if. If you try to declare BetweenValues inside the function containing the call to find_if, like this,

// beginning of function
{
  ...
  template <typename T>
  class BetweenValues:
    public std::unary_function<T, bool> { ... };
  vector<int>::iterator i =
    find_if(v.begin(), v.end(),
            BetweenValues<int>(x, y));
  ...
}
// end of function

you'll discover that it won't compile, because templates can't be declared inside functions. If you try to avoid that restriction by making BetweenValues a class instead of a template,

// beginning of function
{
  ...
  class BetweenValues:
    public std::unary_function<int, bool> { ... };
  vector<int> iterator i =
    find_if(v.begin(), v.end(),
            BetweenValues(x, y));
  ...
}
// end of function

you'll find that you're still out of luck, because classes defined inside functions are known as local classes, and local class types can't be bound to template type arguments (such as the type of the functor taken by find_if). Sad as it may seem, functor classes and functor class templates are not allowed to be defined inside functions, no matter how convenient it would be to be able to do it.

In the ongoing tussle between algorithm calls and hand-written loops, the bottom line on code clarity is that it all depends on what you need to do inside the loop. If you need to do something an algorithm already does, or if you need to do something very similar to what an algorithm does, the algorithm call is clearer. If you need a loop that does something fairly simple, but would require a confusing tangle of binders and adapters or would require a separate functor class if you were to use an algorithm, you're probably better off just writing the loop. Finally, if you need to do something fairly long and complex inside the loop, the scales tilt back toward algorithms, because long, complex computations should generally be moved into separate functions, anyway. Once you've moved the loop body into a separate function, you can almost certainly find a way to pass that function to an algorithm (often for_each) such that the resulting code is direct and straightforward.

If you agree with this Item that algorithm calls are generally preferable to hand-written loops, and if you also agree that range member functions are preferable to loops that iteratively invoke single-element member functions, an interesting conclusion emerges: well-crafted C++ programs using the STL contain far fewer loops than equivalent programs not using the STL. This is a good thing. Any time we can replace low-level words like for, while, and do with higher-level terms like insert, find, and for_each, we raise the level of abstraction in our software and thereby make it easier to write, document, enhance, and maintain.


Scott Meyers is one of the world's foremost authorities on C++; Effective STL is his third C++ book. He has a Ph.D. in Computer Science from Brown University, sits on the technical advisory boards of several companies, and provides training and consulting services to clients worldwide. His website is www.aristeia.com.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.