Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Extensibility

If we want to use BOOST_FOREACH to iterate over some new collection type, we must "teach" BOOST_FOREACH how to interact with our type. Since BOOST_FOREACH is built on top of Boost.Range, we must extend Boost.Range in order to extend BOOST_FOREACH. The section Extending Boost.Range explores this topic in detail.

Below is an example for extending BOOST_FOREACH to iterate over a sub-string type, which contains two iterators into a std::string.

namespace my
{
    // sub_string: part of a string, as delimited by a pair
    // of iterators
    struct sub_string
    {
        std::string::iterator begin;
        std::string::iterator end;

        /* ... implementation ... */
    };

    // Add overloads of range_begin() and range_end() in the
    // same namespace as sub_string, to be found by Argument-Dependent Lookup.

    inline std::string::iterator range_begin( sub_string & x )
    {
        return x.begin;
    }

    inline std::string::iterator range_end( sub_string & x )
    {
        return x.end;
    }

    // Also add overloads for const sub_strings. Note we use the conversion
    // from string::iterator to string::const_iterator here.

    inline std::string::const_iterator range_begin( sub_string const & x )
    {
        return x.begin;
    }

    inline std::string::const_iterator range_end( sub_string const & x )
    {
        return x.end;
    }
}

namespace boost
{
    // specialize range_mutable_iterator and range_const_iterator in namespace boost
    template<>
    struct range_mutable_iterator< my::sub_string >
    {
        typedef std::string::iterator type;
    };

    template<>
    struct range_const_iterator< my::sub_string >
    {
        typedef std::string::const_iterator type;
    };
}

Now that we have taught Boost.Range (and hence BOOST_FOREACH) about our type, we can now use BOOST_FOREACH to iterate over our sub_string type.

my::sub_string substr;
BOOST_FOREACH( char ch, substr )
{
    // Woo-hoo!
}

There are some portability issues we should be aware of when extending BOOST_FOREACH. Be sure to check out the Portability section. In particular, if your compiler does not support Argument-Dependent Lookup, the Boost.Range Portability section offers some suggested work-arounds.

Making BOOST_FOREACH Work with Non-Copyable Sequence Types

For sequence types that are non-copyable, we will need to tell BOOST_FOREACH to not try to make copies. If our type inherits from boost::noncopyable, no further action is required. If not, we must specialize the boost::foreach::is_noncopyable<> template, as follows:

class noncopy_vector
{
    // ...
private:
    noncopy_vector( noncopy_vector const & ); // non-copyable!
};

namespace boost { namespace foreach
{
    template<>
    struct is_noncopyable< noncopy_vector >
      : mpl::true_
    {
    };
}}

Another way to achieve the same effect is to override the global boost_foreach_is_noncopyable() function. Doing it this way has the advantage of being portable to older compilers.

// At global scope...
inline boost::mpl::true_ *
boost_foreach_is_noncopyable( noncopy_vector *&, boost::foreach::tag )
{
    return 0;
}
[Tip] Tip

Even though we have to tell BOOST_FOREACH that our type is non-copyable, that doesn't mean that BOOST_FOREACH always makes a copy of our sequence type. Obviously, doing so would be expensive and even wrong in some cases. BOOST_FOREACH is quite smart about when to make a copy and when not to. The is_noncopyable<> trait is needed to elide the copy, which is on a branch that might never get taken.

Optimizing BOOST_FOREACH for Lightweight Proxy Sequence Types

On some compilers, BOOST_FOREACH must occasionally take a slightly slower code path to guarantee correct handling of sequences stored in temporary objects. It asks itself, "Should I make a copy of this object?" and later, "Did I make a copy or not?" For some types of sequences, this is overkill. Consider a sequence which is a simple pair of iterators. Jumping through hoops of fire to avoid copying it doesn't make sense because copying it is so cheap.

A pair of iterators is an example of a lightweight proxy. It does not store the values of the sequence; rather, it stores iterators to them. This means that iterating over a copy of the proxy object will give the same results as using the object itself. For such types, BOOST_FOREACH provides a hook that lets us tell it not to worry about the expense of making a copy. This can result in slightly faster loop execution. Simply specialize the boost::foreach::is_lightweight_proxy<> trait, as follows:

struct sub_string
  : boost::iterator_range< std::string::iterator >
{
    // ...
};

namespace boost { namespace foreach
{
    template<>
    struct is_lightweight_proxy< sub_string >
      : mpl::true_
    {
    };
}}

Alternately, we could achieve the same effect by overriding the global boost_foreach_is_lightweight_proxy() function, as follows:

// At global scope...
inline boost::mpl::true_ *
boost_foreach_is_lightweight_proxy( sub_string *&, boost::foreach::tag )
{
    return 0;
}

This method is portable to older compilers.


PrevUpHomeNext