[英]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:我尝试使用boost::range::join
,效果很好:
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, ie 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 Mehrdad 在评论中给出了解决方案
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)...));
}
There are two overloads of boost::join 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.您会获得一个临时的joined_range
并且由于它们只能绑定到const 引用,因此选择了第一个重载,它返回一个不允许修改的范围。
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;
}
}
I haven't looked into your variadic functions, but the problem is likely similar.我没有研究过你的可变参数函数,但问题可能是相似的。
Here's a complete solution, which works correctly on GCC 12. For GCC 10 & 11, the subranges
function can be used to obtain an array of subranges, which can then be used as the lhs argument to | std::views::join
这是一个完整的解决方案,可以在 GCC 12 上正常工作。对于 GCC 10 和 11, subranges
function 可用于获取子范围数组,然后可用作| std::views::join
的 lhs 参数| std::views::join
. | std::views::join
。
EDIT : These functions only return on ranges that have a common iterator type.编辑:这些函数只返回具有共同迭代器类型的范围。 If you don't have a common iterator type, one option is to create a new container from the ranges (which is probably not what you want), or to create a custom type with different sub-ranges (which can't be used with std::views::join
).如果您没有通用的迭代器类型,一个选择是从范围创建一个新容器(这可能不是您想要的),或者创建一个具有不同子范围的自定义类型(不能使用与std::views::join
)。
#include <ranges>
#include <vector>
#include <iostream>
#include <tuple>
#include <array>
#include <algorithm>
namespace detail {
template<std::size_t N, typename... Ts>
struct has_common_type_helper {
using T1 = std::decay_t<std::tuple_element_t<N-1, std::tuple<Ts...>>>;
using T2 = std::decay_t<std::tuple_element_t<N-2, std::tuple<Ts...>>>;
static constexpr bool value = std::same_as<T1, T2> && has_common_type_helper<N-1, Ts...>::value;
};
template<typename... Ts>
struct has_common_type_helper<0, Ts...> : std::false_type {
static_assert(std::is_void_v<Ts...>, "Undefined for an empty parameter pack");
};
template<typename... Ts>
struct has_common_type_helper<1, Ts...> : std::true_type {};
template<typename T> struct iterator_types;
template<std::ranges::range... Ts>
struct iterator_types<std::tuple<Ts...>> {
using type = std::tuple<std::ranges::iterator_t<Ts>...>;
};
}
template<typename T>
struct has_common_type;
template<typename T1, typename T2>
struct has_common_type<std::pair<T1,T2>> {
static constexpr bool value = std::same_as<std::decay_t<T1>, std::decay_t<T2>>;
};
template <typename... Ts>
struct has_common_type<std::tuple<Ts...>> : detail::has_common_type_helper<sizeof...(Ts), Ts...> {};
template <typename T>
inline constexpr bool has_common_type_v = has_common_type<T>::value;
template<std::size_t I = 0, typename Array, typename... Ts, typename Func> requires (I == sizeof...(Ts))
void init_array_from_tuple(Array& a, const std::tuple<Ts...>& t, Func fn)
{
}
template<std::size_t I = 0, typename Array, typename... Ts, typename Func> requires (I < sizeof...(Ts))
void init_array_from_tuple(Array& a, const std::tuple<Ts...>& t, Func fn)
{
a[I] = fn(std::get<I>(t));
init_array_from_tuple<I+1>(a, t, fn);
}
template<std::ranges::range... Ranges>
auto subranges(Ranges&&... rngs)
{
using IteratorTypes = detail::iterator_types<std::tuple<Ranges...>>::type;
static_assert(has_common_type_v<IteratorTypes>);
using SubrangeT = std::ranges::subrange<std::tuple_element_t<0, IteratorTypes>>;
auto subrngs = std::array<SubrangeT, sizeof...(Ranges)>{};
auto t = std::tuple<Ranges&&...>{std::forward<Ranges>(rngs)...};
auto fn = [](auto&& rng) {
return std::ranges::subrange{rng.begin(), rng.end()};
};
init_array_from_tuple(subrngs, t, fn);
return subrngs;
}
#if __GNUC__ >= 12
template<std::ranges::range... Ranges>
auto join(Ranges&&... rngs)
{
return std::ranges::owning_view{subranges(std::forward<Ranges>(rngs)...) | std::views::join};
}
#endif
int main()
{
std::vector<int> v1{1,2,3};
std::vector<int> v2{4};
std::vector<int> v3{5,6};
#if __GNUC__ >= 12
std::ranges::copy(join(v1,v2,v3,v1), std::ostream_iterator<int>(std::cout, " "));
#else
auto subrngs = subranges(v1,v2,v3,v1);
std::ranges::copy(subrngs | std::views::join, std::ostream_iterator<int>(std::cout, " "));
#endif
std::cout << '\n';
return 0;
}
Here's an implementation that works for two different ranges with a common reference type.这是一个适用于具有公共引用类型的两个不同范围的实现。 You can extend it to 3 ranges using a brute-force approach.您可以使用蛮力方法将其扩展到 3 个范围。
#include <ranges>
#include <vector>
#include <list>
#include <iostream>
#include <algorithm>
template<std::ranges::range Range1, std::ranges::range Range2>
auto join2(Range1&& rng1, Range2&& rng2)
{
using Ref1 = std::ranges::range_reference_t<Range1>;
using Ref2 = std::ranges::range_reference_t<Range2>;
using Ref = std::common_reference_t<Ref1, Ref2>;
class Iter {
public:
using value_type = std::remove_cv_t<std::remove_reference_t<Ref>>;
using difference_type = std::ptrdiff_t;
Iter() = default;
Iter(Range1&& rng1_, Range2&& rng2_, bool begin)
: m_it1{begin ? rng1_.begin() : rng1_.end()}
, m_it2{begin ? rng2_.begin() : rng2_.end()}
, m_e1{rng1_.end()} {}
bool operator==(const Iter& rhs) const {
return m_it1 == rhs.m_it1 && m_it2 == rhs.m_it2;
}
Ref operator*() const {
return m_it1 != m_e1 ? *m_it1 : *m_it2;
}
Iter& operator++() {
(m_it1 != m_e1) ? (void)++m_it1 : (void)++m_it2;
return *this;
}
Iter operator++(int) {
Iter ret = *this;
++(*this);
return ret;
}
private:
std::ranges::iterator_t<Range1> m_it1;
std::ranges::iterator_t<Range2> m_it2;
std::ranges::iterator_t<Range1> m_e1;
};
static_assert(std::forward_iterator<Iter>);
auto b = Iter{std::forward<Range1>(rng1), std::forward<Range2>(rng2), true};
auto e = Iter{std::forward<Range1>(rng1), std::forward<Range2>(rng2), false};
return std::ranges::subrange<Iter>{b, e};
}
int main()
{
std::vector<int> v{1,2,3};
std::list<int> l{4,5,6};
std::ranges::copy(join2(v,l), std::ostream_iterator<int>(std::cout, " "));
std::cout << '\n';
return 0;
}
PS I am not optimistic about a variadic implementation, although I'm sure someone smarter than me would be able to figure it out. PS 我对可变参数的实现并不乐观,尽管我确信比我聪明的人能够弄清楚。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.