[英]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()
似乎基本上有兩個要素:
std::tuple
,可能保留各個值的值類別。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_type
和reference
類型以及如何跟蹤不同的范圍做出了一些決定。 其他選擇可能更合理( 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.