简体   繁体   English

boost::range::join 多个范围

[英]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;
    }
}

Live demo.现场演示。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM