Search code examples
c++c++11boostc++14boost-range

Custom range for boost::range library


I’m writing filter and map algorithms using boost::range library:

template <class Range> struct Converter
{
    Converter(const Range& p_range) : m_range(p_range) {}

    template<class OutContainer> operator OutContainer() const
    {
        return {m_range.begin(), m_range.end()};
    }

private:
    Range m_range;
};

template<class Range> Converter<Range> convert(const Range& p_range) { return {p_range}; }

template<class Range, class Fun> auto map(Range&& p_range, Fun&& p_fun)
{
    return convert(p_range | boost::adaptors::transformed(p_fun));
}

template<class Range, class Pred> auto filter(Range&& p_range, Pred&& p_pred)
{
    return convert(p_range | boost::adaptors::filtered(p_pred));
}

Right now I can use them like this:

std::vector<int> l_in  = {1, 2, 3, 4, 5};
std::vector<int> l_tmp_out = filter(l_in, [](int p){ return p < 4; });
std::vector<int> l_out = map(l_tmp_out, [](int p){ return p + 5; });

I would also like to write code this way:

map(filter(l_in, [](int p){ return p < 4; }), [](int p){ return p + 5; });

Unfortunately my Converter class does not compose with boost::range algorithms so this example does not compile. I'm looking for a proper way to change that.

UPDATE

I followed @sehe link and it turned out that all I had to do was to add this four lines to Converter class:

using iterator = typename Range::iterator;
using const_iterator = typename Range::const_iterator;
auto begin() const { return m_range.begin(); }
auto end() const { return m_range.end(); }

Solution

  • Here's my take on things:

    Live On Coliru

    #include <boost/range.hpp>
    #include <boost/range/adaptors.hpp>
    #include <boost/range/algorithm.hpp>
    #include <iostream>
    #include <vector>
    
    namespace MyRange {
        template <typename R> struct Proxy {
            Proxy(R&& r)      : _r(std::move(r)) {}
            Proxy(R const& r) : _r(r) {}
    
            template <typename OutContainer> operator OutContainer() const {
                return boost::copy_range<OutContainer>(_r);
            }
    
            using iterator       = typename boost::range_mutable_iterator<R>::type;
            using const_iterator = typename boost::range_const_iterator<R>::type;
    
            auto begin() const { return range_begin(_r); }
            auto end()   const { return range_end(_r);   }
            auto begin()       { return range_begin(_r); }
            auto end()         { return range_end(_r);   }
    
          private:
            R _r;
        };
    
        template <typename R> auto make_proxy(R&& r) { return Proxy<R>(std::forward<R>(r)); }
    
        template <typename Range, typename Fun> auto map(Range&& p_range, Fun&& p_fun) {
            return make_proxy(std::forward<Range>(p_range) | boost::adaptors::transformed(std::forward<Fun>(p_fun)));
        }
    
        template <typename Range, typename Pred> auto filter(Range&& p_range, Pred&& p_pred) {
            return make_proxy(std::forward<Range>(p_range) | boost::adaptors::filtered(std::forward<Pred>(p_pred)));
        }
    }
    
    int main() {
        using namespace MyRange;
        {
            std::vector<int> l_in  = {1, 2, 3, 4, 5};
            std::vector<int> l_tmp_out = filter(l_in, [](int p){ return p < 4; });
            std::vector<int> l_out = map(l_tmp_out, [](int p){ return p + 5; });
    
            boost::copy(l_out, std::ostream_iterator<int>(std::cout << "\nfirst:\t", "; "));
        }
    
        {
            boost::copy(
                    map(
                        filter(
                            std::vector<int> { 1,2,3,4,5 },
                            [](int p){ return p < 4; }),
                        [](int p){ return p + 5; }),
                    std::ostream_iterator<int>(std::cout << "\nsecond:\t", "; "));
        }
    }
    

    Prints

    first:  6; 7; 8; 
    second: 6; 7; 8; 
    

    NOTES

    • it uses std::forward<> more accurately
    • it uses const/non-const iterators
    • it uses Boost Range traits (range_mutable_iterator<> etc.) instead of hardcoding assuming nested typedefs. This allows things to work with other ranges (e.g. std::array<> or even int (&)[]).

    • the user-defined converson operator uses boost::copy_range<> for similar reasons