Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 34.  Functors and Ranges


34.1. Syntactic Clutter

Many of the standard library algorithms operate on ranges, where a range is defined as a pair of iterators [Aust1999]. This abstraction is very powerful, and has been exploited to the degree that much of the STL, and, therefore, much of modern C++, relies upon it.

An example of this might be in a simple program to read in integers into a vector:



std::fstream f("integers.dat", std::ios::in | std::ios::out);





std::copy( std::istream_iterator<int>(f)


         , std::istream_iterator<int>()


         , std::back_inserter(v2));



In this case, the second argument is a default-constructed iterator that acts as an indicator for the end of range. The two iterators are not connected in a physical sense; the implementation of istream_iterator is such that a default-constructed instance may be interpreted as the logic end point of the range.

Many times we use algorithms over a range of values acquired from a container or containerlike, object as in:



struct dump_string


{


  void operator ()(std::string const &) const;


};





std::vector<std::string> >   strings = . . .;


std::for_each(strings.begin(), strings.end(), dump_string());



As such, it can become tedious, since we replicate the same calls to begin() and end() time and time again. This is miles away from being an imperfection—barely even a minor gripe—but there are circumstances in which it can be a pain. Consider the case when using (pseudo)containers such as glob_sequence (see section 20.6.3) that are only created in order to elicit their range.[1] Let's imagine we want to determine how many Imperfect C++ header files are larger than 1024 bytes, because we want to really immerse ourselves in the magic:

[1] Note that I'm kind of creating my own problem to solve in this specific case, because I tend to extend STL in this way. It is possible in many cases to follow the example of istream_iterator and encapsulate the start of the enumeration in the constructor of the nondefault iterator; Boost's file-system enumeration component works in this way. But this is a logical disconnect that troubles me too much to follow the lead—you may well see it differently.



struct is_large


  : public std::unary_function<char const *, bool>


{


  bool operator ()(char const *file) const


  {


    . . . // Return true if "file" > 1024 bytes


  }


};





glob_sequence gs("/usr/include/", "impcpp*");


size_t n = std::count_if(gs.begin(), gs.end(), is_large());



gs is not needed for any other purpose, and its presence otherwise serves only to pollute the local namespace.

An analogous situation occurs when we want to enumerate two or more ranges in the same scope. We can end up introducing several variables for the different begin and end conditions of each range into the current scope, as we saw in section 17.3.2, or using the double-scoping trick from section 17.3.1.


      Previous section   Next section