Previous section   Next section

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


34.2. for_all() ?

Given the common enumeration of full ranges, it is a simple matter to create full-range equivalents of the standard algorithms. We might create a container-compatible version of std::for_each(), which we'll call for_all() for the moment:



template< typename  C


        , typename  F


        >


inline F for_all(C &c, F f)


{


  return std::for_each(c.begin(), c.end(), f);


}



This is a reasonable implementation for the general case of such an algorithm since most containers—standard and otherwise—provide begin() and end() methods.

As well as reducing the tedium of specifying the two iterator method calls, it can also further reduce the eyestrain when dealing with (pseudo)containers. Our glob_sequence can be declared as an anonymous temporary, giving us the pleasingly condensed:



n = std::count_if( glob_sequence("/usr/include/", "impcpp*")


                 , is_large());



When you're dealing with production code, such savings of syntactic clutter can be a real boost in readability.

Some critics may counter that the considerable amount of code involved in that single statement is another example of how C++ has strayed from the Spirit of C [Como-SOC]; it's certainly hard to argue "nothing is hidden." I would concede this point, but only as far as the functor is concerned. I think arguing that the "hidden" nature of the range access and enumeration is wrong is a specious argument; one may as well argue that we should all eschew the use of library functions and write everything in assembler. The STL Iterator concept [Aust1999, Muss2001] is designed to facilitate maximum efficiency of enumeration—usually the only way to "misuse" sequences is to use higher level Iterator concept [Aust1999, Muss2001] behavior, but for_each() requires only input iterators.

34.2.1 arrays

As we saw in Chapter 14, the fact that we've got two places of definition of the array size is a potential source of errors. Even when they both use the same constant, there are still, in effect, two definitions.



int    ari[10] = { . . . };





std::for_each(&ari[0], &ari[10], print_int);



We also saw that the foolproof way to deal with arrays is to use static array size determination in the form of dimensionof() or a similar construct.



std::for_each(&ari[0], &ari[dimensionof(ari)], print_int);



However, given our definitions of for_all(), we can easily specialize for arrays using the similar facilities, as in:



template< typename  T


        , size_t    N


        , typename  F


        >


inline F for_all(T (&ar)[N], F f)


{


  return std::for_each(&ar[0], &ar[N], f);


}



34.2.2 Naming Concerns

Let's now turn to the issue of the naming of such algorithms. I deliberately chose an unsuitable name, so that we wouldn't get comfy with it. The problem with the name for_all() is all too obvious: it doesn't transfer to other algorithms. Do we have a fill_all(), accumulate_all(), and so on? It's not an attractive option. Ideally we'd like to call it for_each(), so why not do that? Unfortunately, this is a bit of a conundrum: we must select from equally unpleasant options.

We are only allowed to specialize templates that already exist in the standard namespace for new types; we are not allowed to add any new template (or nontemplate) functions or types. I would say that the array form of for_all(), were we to call it for_each(), could not be reasonably classed as a specialization based on new types, although I confess the issue's hardly equivocal.

The alternative is to define it as for_each() within our own namespace. In that case, we must remember to "use" it, via a using declaration, whenever we want to use it outside our namespace. Unfortunately, we'll also have to remember to "use" std::for_each() whenever that's what's required, since any using declaration for our for_each() will mask out the std one. Failure to do this can lead to some very perplexing error messages. But despite this, the problems raised for writing generalized code when the algorithms are of different names are such that, in general, this approach should be seriously considered[2] despite the namespace "use" hassles.

[2] This is the approach taken by John in his Boost-compatible implementation of RangeLib, although he relies more on the explicit qualification of the RangeLib algorithm namespace boost::rtl::rng::for_each.

We'll look at just one example of the namespace problems. Consider that you are using your own library components en masse via a using directive, using namespace acmelib. That's okay, you reason, because in this client code you'll be using heaps of things from the acmelib namespace. One of these is your array version of for_each(). You go along swimmingly with this for a while, and then need to use std::for_each() in some part of the implementation file, so you "use" that one function via a using declaration:



using namespace acmelib; // Using directive


using std::for_each;     // Using declaration





int   ai[10];





for_each(ai, print_int);



Now your code will not compile, because the specific introduction of std::for_each() via the using declaration takes precedence over the one "used" via the general introduction of the using directive. What's to be done: introduce std::for_each() via a using directive? What happens if there're conflicting definitions in the two namespaces of a given function or type that is being used in our code?

What we're forced to do is make an additional using declaration for acmelib::for_each(). Given that, why not simply use using declarations in the first place? It may be a little bit more work initially, but it's a big saving in the long run, and we all know that the cost of the initial coding phase of software is pretty irrelevant in comparison to its maintenance [Glas2003]. This is just one of the reasons why I refuse to use using directives under just about any circumstances.[3]

[3] Much hard-won experience with Java's import x.* also contributes to my loathing for the indiscriminate introduction of names into namespaces. Herb Sutter and I have an ongoing difference of opinion on this point. Fortunately for Herb, he is vastly more knowledgeable and erudite on C++ than I am. Fortunately, for me, this is my book and I can write what I like.

In this case, our new for_each() and std::for_each() have different numbers of arguments, so we could not be writing generalized code that could work with both anyway. Hence, we could just call the algorithms for_each_c(), fill_c(), and so on.

We'll come back to the naming problem toward the end of this chapter.


      Previous section   Next section