Search code examples
c++boostjoinrange

boost::range::join for multiple ranges


I want to do the following:

std::vector<int> a = {1,2,3}, b = {4,5,6}, c = {7,8,9};

for(auto&& i : join(a,b,c)) {
  i += 1
  std::cout << i;  // -> 2345678910
}

I tried using boost::range::join, this works fine:

auto r = boost::join(a,b);
for(auto&& i : boost::join(r,c)) {
  i += 1;
  std::cout << i;  // -> 2345678910
}

Chaining joins, reading operations work:

for(auto&& i : boost::join(boost::join(a,b),c))
  std::cout << i;  // -> 123456789

However, writing doesn't work:

for(auto&& i : boost::join(boost::join(a,b),c)) {
  i += 1; // Fails  :(
  std::cout << i;  
}

My variadic join has the same problem, i.e. works for reading but not for writing:

template<class C> C&& join(C&& c) { return c; }

template<class C, class D, class... Args>
auto join(C&& c, D&& d, Args&&... args)
-> decltype(boost::join(boost::join(std::forward<C>(c), std::forward<D>(d)),
                     join(std::forward<Args>(args)...))) {
return boost::join(boost::join(std::forward<C>(c), std::forward<D>(d)),
                     join(std::forward<Args>(args)...));
}

Mehrdad gave the solution in the comments

template<class C>
auto join(C&& c)
-> decltype(boost::make_iterator_range(std::begin(c),std::end(c))) {
return boost::make_iterator_range(std::begin(c),std::end(c));
}

template<class C, class D, class... Args>
auto join(C&& c, D&& d, Args&&... args)
-> decltype(boost::join(boost::join(boost::make_iterator_range(std::begin(c),std::end(c)),
                                 boost::make_iterator_range(std::begin(d),std::end(d))),
                     join(std::forward<Args>(args)...))) {
  return boost::join(boost::join(boost::make_iterator_range(std::begin(c),std::end(c)),
                                 boost::make_iterator_range(std::begin(d),std::end(d))),
                     join(std::forward<Args>(args)...));
}

Solution

  • There are two overloads of boost::join

    template<typename SinglePassRange1, typename SinglePassRange2>
    joined_range<const SinglePassRange1, const SinglePassRange2>
    join(const SinglePassRange1& rng1, const SinglePassRange2& rng2)
    
    template<typename SinglePassRange1, typename SinglePassRange2>
    joined_range<SinglePassRange1, SinglePassRange2>
    join(SinglePassRange1& rng1, SinglePassRange2& rng2);
    

    When you do this

    for(auto&& i : boost::join(boost::join(a,b), c)) {
               //  ^^^^        ^^^^ temporary here
               //   ||
               //  calls the const ref overload
    

    You get a temporary joined_range and as those can only bind to const references, the first overload is selected which returns a range that doesn't allow modifying.

    You can work around this if you avoid temporaries:

    #include <boost/range.hpp>
    #include <boost/range/join.hpp>
    
    int main()
    {
        std::vector<int> a = {1,2,3}, b = {4,5,6}, c = {7,8,9};
        auto range = boost::join(a,b);
    
        for(int& i : boost::join(range,c)) {
            i += 1;
            std::cout << i;
        }
    }
    

    Live demo.

    I haven't looked into your variadic functions, but the problem is likely similar.