簡體   English   中英

如何使用具有兩個范圍的 `transform()` 的范圍版本?

[英]How to use the range version of `transform()` with two ranges?

header <algorithm>包含一個std::transform()版本,它采用兩個輸入序列、一個 output 序列和一個二進制 ZC1C425268E68385D1AB5074C17A94F1Z 作為參數,例如:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

int main()
{
    std::vector<int> v0{1, 2, 3};
    std::vector<int> v1{4, 5, 6};
    std::vector<int> result;
    std::transform(v0.begin(), v0.end(), v1.begin(), std::back_inserter(result),
                   [](auto a, auto b){ return a + b; });
    std::copy(result.begin(), result.end(),
              std::ostream_iterator<int>(std::cout, " "));
    std::cout << '\n';
}

C++20 引入了范圍算法,其中包括std::ranges::views::transform(R, F)及其實現std::ranges::views::transform_view 我可以看到如何在一個范圍內使用這個transform() ,例如:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <ranges>
#include <vector>

int main()
{   
    std::vector<int> v0{1, 2, 3}; 
    for (auto x: std::ranges::views::transform(v0, [](auto a){ return a + 3; })) {
        std::cout << x << ' ';
    }   
    std::cout << '\n';
}   

但是,沒有版本支持多個范圍。 所以,問題就變成了:如何將范圍版本的transform()與兩個(或更多)范圍一起使用? 這種方法的目標是從視圖的惰性評估中受益並避免創建中間序列( result上面的非范圍版本)。 潛在用途可能如下所示(將 function 參數放在前面,以便更容易實現允許兩個以上范圍的潛在解決方案):

for (auto v: envisioned::transform([](auto a, auto b){ return a + b; }, v0, v1) {
    std::cout << v << ' ';
}
std::cout << '\n';

從 C++20 開始, <algorithm>中可用的內容無法直接使用您想要使用transform的方式,您可以在其中獲取任意數量的輸入范圍。 您當然可以自己編寫這樣的算法而無需太多努力。

具有 2 個輸入范圍的示例可以在 C++20 中實現,如下所示:

std::ranges::transform(v0, v1,
                       std::ostream_iterator<int>(std::cout, " "),
                       std::plus{});    

這是一個演示,特別是此處列出的最后一個transform重載。

不幸的是,沒有辦法像這樣編寫等效版本:

for (auto v : std::views::transform(v0, v1, std::plus{})) // no, unfortunately    
  std::cout << v << " ";

但上面的實現做同樣的事情。 它當然可以滿足您不必單獨存儲結果的要求; 然后可以在生成時打印。

您正在尋找的是 range-v3 調用zip_with的算法,以及我們在P2214中提出的以名稱zip_transform添加到 C++23 的算法。 C++20 中沒有這樣的算法。

在此之前,range-v3 版本正是您的用例:

for (auto v : zip_with([](auto a, auto b){ return a + b; }, v0, v1)) {
    std::cout << v << ' ';
}

它可以處理任意數量的范圍。

請注意,這里沒有管道版本,就像沒有常規zip

答案是我設想如何回答這個問題,我認為它仍然包含一些關於如何實際實現視圖的有趣部分。 事實證明, @Barry 的答案中提到的P2214有一個有趣的視圖( zip_transform ),它執行了下面發布的解決方案的中間步驟,但實際上完全涵蓋了進行多范圍transform所需的功能!

使用具有多個范圍的std::ranges::views::transform()似乎基本上有兩個要素:

  1. 某種方式將zip范圍內相應位置的對象放入std::tuple ,可能保留各個值的值類別。
  2. 與其使用 n 元 function 將范圍的元素作為參數,function 寧願使用std::tuple並可能使用它來調用相應的 n 元 ZC1C425268E68785D1AB5074C1F1。

使用這個想法將允許創建一個處理任意數量范圍的transform()版本,盡管首先使用 function object 而不是提取參數包的最后一個元素更容易:

auto transform(auto&& fun, auto&&... ranges)
{
    return std::ranges::views::transform(zip(std::forward<decltype(ranges)>(ranges)...),
                                         [fun = std::forward<decltype(fun)>(fun)]
                                         (auto&& t){ return std::apply(fun, std::forward<decltype(t)>(t)); });
}

此實現使用的zip視圖可以根據std::tuple來實現:

template <typename... Range>
struct zip_view
    : std::ranges::view_base
{
    template <typename V>
    struct rvalue_view
    {
        std::shared_ptr<std::decay_t<V>> view;
        rvalue_view() = default;
        rvalue_view(V v): view(new std::decay_t<V>(std::move(v))) {}
        auto begin() const { return this->view->begin(); }
        auto end() const { return this->view->end(); }
    };
    template <typename T>
    using element_t = std::conditional_t<
        std::is_rvalue_reference_v<T>,
        rvalue_view<T>,
        T
        >;
    using storage_t = std::tuple<element_t<Range>...>;
    using value_type = std::tuple<std::ranges::range_reference_t<std::remove_reference_t<Range>>...>;
    using reference = value_type;
    using difference_type = std::common_type_t<std::ranges::range_difference_t<Range>...>;
    storage_t ranges;
    
    template <typename> struct base;
    template <std::size_t... I>
    struct base<std::integer_sequence<std::size_t, I...>>
    {
        using value_type = zip_view::value_type;
        using reference = zip_view::value_type;
        using pointer = value_type*;
        using difference_type = std::common_type_t<std::ranges::range_difference_t<Range>...>;
        using iterator_category = std::common_type_t<std::random_access_iterator_tag,
                                                     typename std::iterator_traits<std::ranges::iterator_t<Range>>::iterator_category...>;

        using iterators_t = std::tuple<std::ranges::iterator_t<Range>...>;
        iterators_t iters;

        reference operator*() const { return {*std::get<I>(iters)...}; }
        reference operator[](difference_type n) const { return {std::get<I>(iters)[n]...}; }
        void increment() { (++std::get<I>(iters), ...); }
        void decrement() { (--std::get<I>(iters), ...); }
        bool equals(base const& other) const {
            return ((std::get<I>(iters) == std::get<I>(other.iters)) || ...);
        }
        void advance(difference_type n){ ((std::get<I>(iters) += n), ...); }
        
        base(): iters() {}
        base(const storage_t& s, auto f): iters(f(std::get<I>(s))...) {}
    };

    struct iterator
        : base<std::make_index_sequence<sizeof...(Range)>>
    {
        using base<std::make_index_sequence<sizeof...(Range)>>::base;
        iterator& operator++() { this->increment(); return *this; }
        iterator  operator++(int) { auto rc(*this); operator++(); return rc; }
        iterator& operator--() { this->decrement(); return *this; }
        iterator  operator--(int) { auto rc(*this); operator--(); return rc; }
        iterator& operator+= (difference_type n) { this->advance(n); return *this; }
        iterator& operator-= (difference_type n) { this->advance(-n); return *this; }
        bool      operator== (iterator const& other) const { return this->equals(other); }
        auto      operator<=> (iterator const& other) const {
            return std::get<0>(this->iters) <=> std::get<0>(other.iters);
        }
        friend iterator operator+ (iterator it, difference_type n) { return it += n; }
        friend iterator operator+ (difference_type n, iterator it) { return it += n; }
        friend iterator operator- (iterator it, difference_type n) { return it -= n; }
        friend difference_type operator- (iterator it0, iterator it1) {
            return std::get<0>(it0.iters) - std::get<0>(it1.iters);
        }
    };

    zip_view(): ranges() {}
    template <typename... R>
    zip_view(R&&... ranges): ranges(std::forward<R>(ranges)...) {}

    iterator begin() const { return iterator(ranges, [](auto& r){ return std::ranges::begin(r); }); }
    iterator end() const { return iterator(ranges, [](auto& r){ return std::ranges::end(r); }); }
};

auto zip(auto&&... ranges)
    -> zip_view<decltype(ranges)...>
{
    return {std::forward<decltype(ranges)>(ranges)...};
}

這個實現對value_typereference類型以及如何跟蹤不同的范圍做出了一些決定。 其他選擇可能更合理( P2214做出的選擇略有不同,可能更好)。 此實現中唯一棘手的一點是在std::tuple上操作,這需要包含索引的參數包或std::tuple上的一組合適的算法。

有了所有這些,就可以很好地使用多范圍transform ,例如:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <memory>
#include <ranges>
#include <utility>
#include <tuple>
#include <type_traits>
#include <vector>
    
// zip_view, zip, and transform as above

int main()
{
    std::vector<int> v0{1, 2, 3};
    std::vector<int> v1{4, 5, 6};
    std::vector<int> v2{7, 8, 9};
    for (auto x: transform([](auto a, auto b, auto c){ return a + b + c; }, v0, v1, v2)) {
        std::cout << x << ' ';
    }
    std::cout << '\n';
}

暫無
暫無

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

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