The Boost C++ Libraries

Adaptors

The standard library provides several algorithms you can pass a predicate to. For example, the predicate passed to std::count_if() determines which elements are counted. Boost.Range provides the similar function boost::count_if(). However, this algorithm is only provided for completeness because Boost.Range provides adaptors that make algorithms with predicates superfluous.

You can think of adaptors as filters. They return a new range based on another range. Data isn’t necessarily copied. Since a range is just a pair of iterators, an adaptor returns a new pair. The pair can still be used to iterate over the original range but may, for example, skip certain elements. If boost::count() is used with such an adaptor, boost::count_if() is no longer required. Algorithms don’t have to be defined multiple times just so they can be called with and without predicates.

The difference between algorithms and adaptors is that algorithms iterate over a range and process data, while adaptors return a new range – new iterators – that determines what elements the iteration returns. However, no iteration is executed. An algorithm must be called first.

Example 30.4. Filtering a range with boost::adaptors::filter()
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <array>
#include <iterator>
#include <iostream>

int main()
{
  std::array<int, 6> a{{0, 5, 2, 1, 3, 4}};
  boost::copy(boost::adaptors::filter(a, [](int i){ return i > 2; }),
    std::ostream_iterator<int>{std::cout, ","});
}

Example 30.4 uses an adaptor that can filter ranges. As you can see, the adaptor is just a function. boost::adaptors::filter() expects as its first parameter a range to filter and as its second parameter a predicate. The predicate in Example 30.4 removes all numbers from the range that aren’t greater than 2.

boost::adaptors::filter() does not change the range a, it returns a new range. Since a range isn’t much different from a pair of iterators, the new range also refers to a. However, the iterators for the new range skip all numbers that are less than or equal to 2.

Example 30.4 writes 5,3,4 to standard output.

Example 30.5. Using keys(), values() and indirect()
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <array>
#include <map>
#include <string>
#include <utility>
#include <iterator>
#include <iostream>

int main()
{
  std::array<int, 3> a{{0, 1, 2}};
  std::map<std::string, int*> m;
  m.insert(std::make_pair("a", &a[0]));
  m.insert(std::make_pair("b", &a[1]));
  m.insert(std::make_pair("c", &a[2]));

  boost::copy(boost::adaptors::keys(m),
    std::ostream_iterator<std::string>{std::cout, ","});
  boost::copy(boost::adaptors::indirect(boost::adaptors::values(m)),
    std::ostream_iterator<int>{std::cout, ","});
}

Example 30.5 uses two adaptors, boost::adaptors::keys() and boost::adaptors::values(), to access keys and values in a container of type std::map. It also shows how adaptors can be nested. Because m stores pointers to the values to be printed, rather than the values themselves, the range returned by boost::adaptors::values() is passed to boost::adaptors::indirect(). This adaptor can always be used when a range consists of pointers, but an iteration should return the values the pointers refer to. That’s why Example 30.5 writes a,b,c,0,1,2, to standard output.

Example 30.6. boost::adaptors::tokenize() – an adaptor for strings
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/regex.hpp>
#include <string>
#include <iostream>

int main()
{
  std::string s = "The Boost C++ Libraries";
  boost::regex expr{"[\\w+]+"};
  boost::copy(boost::adaptors::tokenize(s, expr, 0,
    boost::regex_constants::match_default),
    std::ostream_iterator<std::string>{std::cout, ","});
}

Example 30.6 introduces an adaptor for strings. You can use boost::adaptors::tokenize() to get a range from a string with the help of a regular expression. You pass a string and a regular expression of the type boost::regex to boost::adaptors::tokenize(). In addition, you need to pass a number that refers to a group in the regular expression and a flag. If no group is used, you can pass 0. The flag boost::regex_constants::match_default selects the default settings for regular expressions. You can also pass other flags. For example, you can use boost::regex_constants::match_perl if you want the regular expression to be applied according to the rules for the programming language Perl.