Component of the Week #18: bdlb_transformiterator

Summary:
  • Provides an iterator wrapper that applies a functor transformation on each value when dereferenced.

The bdlb_transformiterator component defines a class template, bdlb::TransformIterator, that wraps another iterator and applies a user-provided functor to transform values when the iterator is dereferenced. This component is particularly useful when you need to transform a sequence of values on-the-fly without creating a new container.

bdlb::TransformIterator accepts two template parameters:

  1. t_FUNCTOR: The type of a callable object that will be invoked with a single argument. In C++03, some restrictions apply; see the component documentation for more details.

  2. t_ITERATOR: The type of the underlying iterator that will be wrapped. This can be any iterator type, such as a raw pointer or an iterator obtained from a bsl container.

This component also provides the bdlb::TransformIteratorUtil::make helper function, which accepts an iterator and a callable, and returns an object of the appropriate bdlb::TransformIterator type. This function is useful before C++17, where class template argument deduction cannot be used. Here’s a simple example that calculates the sum of squares of integers in an array:

#include <bdlb_transformiterator.h>
#include <bsl_iostream.h>
#include <bsl_iterator.h>
#include <bsl_numeric.h>

using namespace BloombergLP;

/// Return the square of the specified `x`.
int sqr(int x) { return x*x; }

int main() {
    const int data[5] = { 1, -1, 2, -2, 3 };

    // Use standard algorithms with the transform iterators
    const int sum = bsl::accumulate(
        bdlb::TransformIteratorUtil::make(bsl::begin(data), &sqr),
        bdlb::TransformIteratorUtil::make(bsl::end(data),   &sqr),
        0);

    bsl::cout << "Sum of squares: " << sum << bsl::endl;
    // Output: Sum of squares: 19
}

In C++17 and later, class template argument deduction (CTAD) can be used as an alternative to bdlb::TransformIteratorUtil::make, as will be demonstrated by the next example.

When using bdlb::TransformIterator with lambdas, it is important to be aware of the fact that each lambda has a unique type. Therefore, writing out the lambda twice to create the begin and end iterators (whether using CTAD or the bdlb::TransformIteratorUtil::make function) will result in two different types:

const bsl::vector<int> values = {1, 2, 3, 4, 5};

// Create a transform iterator with a lambda that squares each value
bdlb::TransformIterator squareBegin(values.begin(),
                                    [](int x) { return x * x; });
bdlb::TransformIterator squareEnd(values.end(),
                                  [](int x) { return x * x; });

// ERROR: Iterator types don't match
// bsl::for_each(squareBegin, squareEnd, [](int x) {
//     bsl::cout << x << " ";
// });

To solve this problem, the lambda should be declared ahead of time, and stored in a variable:

const auto sqr = [](int x) { return x * x; };

// Create a transform iterator with a lambda that squares each value
bdlb::TransformIterator squareBegin(values.begin(), sqr);
bdlb::TransformIterator squareEnd(values.end(), sqr);

bdlb::TransformIterator is particularly powerful when nested, applying a composition of multiple transformations to each value.

Note that, because the functor is applied during dereference, that transformation is recalculated each time the iterator is dereferenced. If the transformation is expensive and you may access each transformed value multiple times, it might be more efficient to store the transformed values in a separate container.

For more details, see the documentation for bdlb_transformiterator.