16

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)...));
}
Enlico
  • 17,834
  • 5
  • 39
  • 78
gnzlbg
  • 6,867
  • 5
  • 47
  • 99
  • 3
    This *might* work (but credits go to jrok, since he explains the reason behind the problem): `boost::join(boost::join(boost::make_iterator_range(a.begin(), a.end()), boost::make_iterator_range(b.begin(), b.end())), boost::make_iterator_range(c.begin(), c.end()))` – user541686 Jan 16 '13 at 20:14
  • @Mehrdad Thanks, it works! I'll update the question with the solution. Could you explain why it works? boost::make_iterator_range also returns an lvalue so the const ref overload of join should be selected, right? – gnzlbg Jan 16 '13 at 20:22
  • 2
    Yes. It's because of the fact that the *range* `a` is *the container itself*, and a const container like `vector` propagates its const-ness onto its elements. On the other hand, an *iterator range* isn't a container -- it's just a pair of iterators. And the const-ness of an iterator is completely unrelated to the const-ness of what the iterators point to. On another note, while I'm not sure about this, I think replacing `boost::make_iterator_range(a.begin(), a.end())` with `boost::make_iterator_range(a)` might also work. – user541686 Jan 16 '13 at 20:24
  • Just for the the record - your `join` template works too if you use only lvalues (after you rename it, the call with two arguments is ambiguous with `boost::join`). – jrok Jan 16 '13 at 20:42
  • @jrok Do you mean the wrapper marked as the solution or the previous one? (the previous one has the same initial problem) – gnzlbg Jan 16 '13 at 21:04
  • The previous one - it works with the same workaround as in my answer. – jrok Jan 16 '13 at 21:06
  • @Mehrdad Thanks! And yes, `boost::make_iterator_range(a)` works (see solution). – gnzlbg Jan 16 '13 at 21:08

1 Answers1

13

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.

jrok
  • 52,730
  • 9
  • 104
  • 139
  • 1
    it is the same problem for the variadic ranges. The imo best solution would be to make a patch to boost.range, providing overload for rvalue references. – Arne Mertz Jan 16 '13 at 20:17
  • Thanks! Agreed, when chaining two boost::join's the function call produces a temporary that binds to a const lvalue reference (there is no boost::join overload for rvalue references). 'Our' workaround works. However, inside the variadic wrapper recursion would produce only temporaries, making it harder to achieve. – gnzlbg Jan 16 '13 at 20:17