How to implement flatmap using rangev3 ranges

Viewed 1765

I have a pretty simple flatmap function implemented in C++ for std::vector, but it has been suggested that ranges are generally better. Here's the vector based solution:

// flatmap: [A] -> (A->[B]) -> [B]    
template<typename T, typename FN>
static auto flatmap(const std::vector<T> &vec, FN fn) 
     -> std::vector<typename std::remove_reference<decltype(fn(T())[0])>::type> {
    std::vector<typename std::remove_reference<decltype(fn(T())[0])>::type> result;
    for(auto x : vec) {
        auto y = fn(x);
        for( auto v : y ) {
            result.push_back(v);
        }
    }
    return result;
};

It has also been suggested that I use iterators, but that breaks the nice composability of the function:

map(filter(flatmap( V, fn), fn2), fn3)

I would assume that in a range-v3 world I'd be aiming for writing the above as:

auto result = v | flatmap(fn) | filter(fn2) | transform(fn3);

It feels like flatmap should just be a trivial combination of views::for_each, yield_from and transform, but I'm struggling to work out how to hook them all together.

3 Answers

Both answers are correct, but I wanted to add a little more context since the naming of for_each can be a bit confusing (it did confuse me). Here is an example you can use to verify that view::for_each is actually range's flatMap:

#include <range/v3/all.hpp>
#include <iostream>
#include <vector>
using namespace ranges;

int main()
{
    const std::vector<int> a = { 0, 1, 2 };
    auto b = a | view::for_each([] (int x) { return view::ints(x, x+3); });

   ranges::for_each( b, [] (int x) { std::cout << x << " "; } );
   std::cout << std::endl;
}

This will print 0 1 2 1 2 3 2 3 4. The example also shows a possible source for confusion, since there is actually a function for_each in the ranges namespace whose functionality is similar to that of e.g. Java's forEach (i.e. applying a function to each member of a range without a return value).

If you look at the documentation of view::for_each you see that it is acutally implemented using transform and join.

 auto   operator() (Rng &&rng, Fun fun) const -> decltype(join(transform(static_cast< Rng &&>(rng), std::move(fun))))
Related