簡體   English   中英

boost::range::join 多個范圍

[英]boost::range::join for multiple ranges

我想做以下事情:

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
}

我嘗試使用boost::range::join ,效果很好:

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

鏈接連接,讀取操作工作:

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

但是,寫作不起作用:

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

我的可變連接有同樣的問題,即適用於閱讀但不適用於寫作:

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 在評論中給出了解決方案

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)...));
}

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);

當你這樣做時

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

您會獲得一個臨時的joined_range並且由於它們只能綁定到const 引用,因此選擇了第一個重載,它返回一個不允許修改的范圍。

如果您避免臨時工作,您可以解決這個問題:

#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;
    }
}

現場演示。

我沒有研究過你的可變參數函數,但問題可能是相似的。

這是一個完整的解決方案,可以在 GCC 12 上正常工作。對於 GCC 10 和 11, subranges function 可用於獲取子范圍數組,然后可用作| std::views::join的 lhs 參數 | 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;
}

這是一個適用於具有公共引用類型的兩個不同范圍的實現。 您可以使用蠻力方法將其擴展到 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 我對可變參數的實現並不樂觀,盡管我確信比我聰明的人能夠弄清楚。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM