Question

I started playing with Boost::Range in order to have a pipeline of lazy transforms in C++. My problem now is how to split a pipeline in smaller parts. Suppose I have:

int main(){
  auto map = boost::adaptors::transformed; // shorten the name
  auto sink = generate(1) | map([](int x){ return 2*x; })
                          | map([](int x){ return x+1; })
                          | map([](int x){ return 3*x; });
  for(auto i : sink)
    std::cout << i << "\n";
}

And I want to replace the first two maps with a magic_transform, i.e.:

int main(){
  auto map = boost::adaptors::transformed; // shorten the name
  auto sink = generate(1) | magic_transform()
                          | map([](int x){ return 3*x; });
  for(auto i : sink)
    std::cout << i << "\n";
}

How would one write magic_transform? I looked up Boost::Range's documentation, but I can't get a good grasp of it.

Addendum: I'm looking to write a class like this:

class magic_transform {
    ... run_pipeline(... input) {
        return input | map([](int x){ return 2*x; })
                     | map([](int x){ return x+1; });
};
Was it helpful?

Solution

The most difficult problem is figuring out the return type in the code. decltype and lambdas do not mix well (see here), so we have to think of an alternative way:

auto map = boost::adaptors::transformed;

namespace magic_transform
{
   std::function<int(int)> f1 = [](int x){ return 2*x; };
   std::function<int(int)> f2 = [](int x){ return x+1; };
   template <typename Range>
   auto run_pipeline(Range input) -> decltype(input | map(f1) | map(f1))
   {
        return input | map(f1) | map(f2);
   }
}

...
auto sink = magic_transform::run_pipeline(generate(1))
                          | map([](int x){ return 3*x; });

The simple solution is stick the lambdas into std::function so we can use decltype to deduce the return type. I used namespace magic_transform in the example, but you could adapt this code into a class if you would like also. Here is a link adapting your code to the above.

Also, using std::function might be overkill here. Instead you could just declare two normal functions instead (example).

I was also experimenting with boost::any_range, there seems to be some incompatibilities with C+11 lambdas, etc. The closest I could get was the following (example):

auto map = boost::adaptors::transformed;
using range = boost::any_range<
               const int,
               boost::forward_traversal_tag,
               const int&,
               std::ptrdiff_t
               >;

namespace magic_transform
{
    template <typename Range>
    range run_pipeline(Range r)
    {
        return r | map(std::function<int(int)>([](int x){ return 2*x; }))
             | map(std::function<int(int)>([](int x){ return x+1; }));
    }
}

int main(){
  auto sink = magic_transform::run_pipeline(boost::irange(0, 10))
                          | map([](int x){ return 3*x; });
  for(auto i : sink)
    std::cout << i << "\n";
}

OTHER TIPS

I think will work:

auto magic_transform()->decltype(boost::adaptors::transformed(std::function<int(int)>())
{
    std::function<int(int)> retval = [](int x){ return [](int x){ return x+1; }(2*x);
    return boost::adaptors::transformed(retval);
}

but it probably isn't what you are looking for. :) (Jokes in the above code: chained lambdas for 2*x+1, use of decltype on basically the implementation to find the return type),

Looking at the source code for http://www.boost.org/doc/libs/1_46_1/boost/range/adaptor/transformed.hpp, the type that magic_transform wants to return is boost::range_detail::transform_holder<T>, where T is the type of the function.

When you do it on the stack with lambdas, T ends up being some very narrow type. If you want to pass around abstract transformations without exposing all of the details, using std::function<outtype(intype)> may be reasonable (there will be a small run-time overhead).

Hope that works.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top