Search code examples
c++boost-geometryrange-v3

How do you make a pipeable function like ranges::to<T>() with range-v3 ranges?


My general question is how do you make something like ranges::to<T>() for classes for which ranges::to<T>() does not work?

But specifically I am looking for a pipeable way to construct a boost geometry multi_linestring from a range-v3 range view of linestrings. Somewhat surprisingly ranges::to just works when constructing a linestring but fails to compile when constructing a multi_linestring, as below.

namespace r = ranges;
namespace rv = ranges::views;
namespace bg = boost::geometry;

using point = bg::model::point<double, 2, bg::cs::cartesian>;
using polyline = bg::model::linestring<point>;
using polylines = bg::model::multi_linestring<polyline>;

int main() {

    std::vector<point> some_points = { {1,1},{2,2},{3,3},{4,4},{5,5} };
    auto poly = some_points | r::to<polyline>(); // <- this works

    std::vector<std::vector<point>> vec_of_vec_of_pts = {
        {{1,1},{2,2},{3,3},{4,4},{5,5}},
        {{6,6},{7,7},{8,8}},
        {{9,9},{10,10},{11,11},{12,12}}
    };

    auto polys = vec_of_vec_of_pts | rv::transform(
            [](const auto& v) { return v | r::to<polyline>(); }
        ) | r::to<polylines>();   // <- this does not compile.

    return 0;
} 

The particular error message from Visual Studio is

1>[...]: error C2678: binary '|': no operator found which takes a left-hand operand of type 'ranges::transform_view<ranges::ref_view<std::vector<std::vector<point,std::allocator<point>>,std::allocator<std::vector<point,std::allocator<point>>>>>,Arg>' (or there is no acceptable conversion)
1>        with
1>        [
1>            Arg=main::<lambda_1>
1>        ]
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\cstddef(42,27): message : could be 'std::byte std::operator |(const std::byte,const std::byte) noexcept' [found using argument-dependent lookup]
1>C:\libraries\range-v3\include\range\v3\view\any_view.hpp(66,24): message : or       'ranges::category ranges::operator |(ranges::category,ranges::category) noexcept' [found using argument-dependent lookup]
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\regex(1191,1): message : or       'std::_Node_flags std::operator |(std::_Node_flags,std::_Node_flags) noexcept' [found using argument-dependent lookup]
1>[...]: message : while trying to match the argument list '(ranges::transform_view<ranges::ref_view<std::vector<std::vector<point,std::allocator<point>>,std::allocator<std::vector<point,std::allocator<point>>>>>,Arg>, ranges::detail::to_container::closure<meta::id<polylines>,ranges::detail::to_container::fn<meta::id<polylines>>>)'
1>        with
1>        [
1>            Arg=main::<lambda_1>
1>        ]

My current work-around is a function template that will do the conversion:

template<typename Rng>
polylines to_polylines(Rng lines) {
    polylines polys;
    polys.resize(r::distance(lines));
    for (auto&& [i, line] : rv::enumerate(lines)) {
        polys[i] = line;
    }
    return polys;
}

which I can use like

auto polys = to_polylines(
    vec_of_vec_of_pts |
    rv::transform(
        [](const auto& v) { return v | r::to<polyline>(); }
    )
);

but it can't be a target of a range-v3 pipeline. How can I implement something like the above that can be piped to?


Solution

  • The pipeline is just overloaded operator |, you can overload it yourself.

    struct to_polyline_tag{} to_polylines;
    
    template<typename Range>
    polylines opeator | (Range&& lines, to_polyline_tag) {
        // the body of your to_polylines()
        polylines polys;
        polys.resize(r::distance(lines));
        for (auto&& [i, line] : rv::enumerate(lines)) {
            polys[i] = line;
        }
        return polys;
    }
    // use: some_range_like | to_polylines