The Solution section in gnzlbg's SO question boost::range::join for multiple ranges implies it can join many ranges in one client code call to a custom function variadic template that calls boost::join
and boost::make_iterator_range
. According to that question, answer, and comments, the prior can join 2 ranges and the latter is needed to ensure the non-const
overload of the prior is used. Any containers after the 2nd one are supposedly perfect-forwarded via std::forward
. But my client code can only successfully call it with a maximum of 3 arguments. Anything more fails to compile. What’s wrong and how to fix it? And is there any Boost entity now that joins many ranges?
I’ve copied and pasted that OP’s implementation, editing it here only for whitespace readability and adding the relevant headers:
#include <utility>
#include <boost/range/join.hpp>
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)...)
);
}
and added my client code:
#include <deque>
#include <array>
#include <vector>
#include <iostream>
int main()
{
std::deque<int> deq { 0, 1, 2, 3, 4 };
std::array<int, 4> stl_arr { 5, 6, 7, 8 };
int c_arr[3] { 9, 10, 11 };
std::vector<int> vec { 12, 13 };
for (auto& i : join(deq, stl_arr, c_arr))
{
++i;
std::cout << i << ", "; // OK, prints 1 thru 12
}
//join(deq, stl_arr, c_arr, vec); // COMPILER ERROR
}
There are two things going on. The first is that the following declarations won’t work as intended:
template<class C>
auto join(C&& c)
-> decltype(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)...)
// ^^^^-- (1)
)
);
The crux of the matter is that at spot (1) the second overload of join
is not in scope. With three arguments no problem manifests because the Args
pack has length 1, so the resulting join(std::forward<Arg0>(arg0))
expansion uses the first overload which is in scope.
With four arguments or more the resulting join(std::forward<Arg0>(arg0), ..., std::forward<ArgN>(argN))
expansion needs the second overload but it is not in scope inside the late return type of the selfsame.
One way to fix that is to turn the set of function templates into a set of member function templates because class scope is more lenient:
struct join_type {
template<class C>
auto operator()(C&& c) const
-> 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 operator()(C&& c, D&& d, Args&&... args) const
-> 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))
),
(*this)(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))
),
(*this)(std::forward<Args>(args)...)
);
}
};
constexpr join_type join {};
Note that what matters is the class scope, not that we have chosen to use operator()
as the name of our member function templates. You can just as well use static member function templates named foo
and have a normal function template outside of the class that forwards to it.
Now we can expose the second problem in that the implementation is buggy and will only work with odd numbers of arguments! Even join(a, b)
won’t work, but that bug could have been hidden previously by ADL (i.e. clients would end up effectively calling boost::join(a, b)
which obviously works) (Live On Coliru).
Let’s rewrite the fold:
struct join_type {
template<class C>
auto operator()(C&& c) const
-> decltype(boost::make_iterator_range(begin(c), end(c)))
{
return boost::make_iterator_range(begin(c), end(c));
}
template<typename First, typename Second, typename... Rest>
auto operator()(First&& first, Second&& second, Rest&&... rest) const
-> decltype( (*this)(boost::join(boost::make_iterator_range(begin(first), end(first)), boost::make_iterator_range(begin(second), end(second))), std::forward<Rest>(rest)...) )
{
return (*this)(boost::join(boost::make_iterator_range(begin(first), end(first)), boost::make_iterator_range(begin(second), end(second))), std::forward<Rest>(rest)...);
}
};
constexpr join_type join {};
Live On Coliru