[英]How to use the range version of `transform()` with two ranges?
The header <algorithm>
contains a version of std::transform()
taking a two input sequences, an output sequence, and a binary function as parameters, eg: 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 introduced range algoirthms which does include std::ranges::views::transform(R, F)
and its implementation std::ranges::views::transform_view
. C++20 引入了范围算法,其中包括
std::ranges::views::transform(R, F)
及其实现std::ranges::views::transform_view
。 I can see how to use this transform()
with one range, eg:我可以看到如何在一个范围内使用这个
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';
}
However, there is no version supporting more than one range.但是,没有版本支持多个范围。 So, the question becomes: How to use the range version of
transform()
with two (or more) ranges?所以,问题就变成了:如何将范围版本的
transform()
与两个(或更多)范围一起使用? On objective of this approach is to benefit from the lazy evaluation of views and avoid the creation of an intermediate sequence ( result
in the non-ranges version above).这种方法的目标是从视图的惰性评估中受益并避免创建中间序列(
result
上面的非范围版本)。 A potential use could look like this (putting the function argument in front to make it easier for a potential solution allowing even more than two ranges):潜在用途可能如下所示(将 function 参数放在前面,以便更容易实现允许两个以上范围的潜在解决方案):
for (auto v: envisioned::transform([](auto a, auto b){ return a + b; }, v0, v1) {
std::cout << v << ' ';
}
std::cout << '\n';
The way you would like to use transform
, where you take an arbitrary number of input ranges is not possible directly with what's available in <algorithm>
as of C++20.从 C++20 开始,
<algorithm>
中可用的内容无法直接使用您想要使用transform
的方式,您可以在其中获取任意数量的输入范围。 You can of course write such an algorithm yourself without too much effort.您当然可以自己编写这样的算法而无需太多努力。
The example with 2 input ranges can be implemented in C++20 like this:具有 2 个输入范围的示例可以在 C++20 中实现,如下所示:
std::ranges::transform(v0, v1,
std::ostream_iterator<int>(std::cout, " "),
std::plus{});
Here's a demo , and this is specifically the last overload of transform
listed here .这是一个演示,特别是此处列出的最后一个
transform
重载。
There is unfortunately no way to write the equivalent version like this:不幸的是,没有办法像这样编写等效版本:
for (auto v : std::views::transform(v0, v1, std::plus{})) // no, unfortunately
std::cout << v << " ";
but the above implementation does the same thing.但上面的实现做同样的事情。 It certainly satisfies your requirements of not having to store the results separately;
它当然可以满足您不必单独存储结果的要求; then can be printed as they're generated.
然后可以在生成时打印。
What you're looking for is the algorithm that range-v3 calls zip_with
and what we are proposing in P2214 to add to C++23 under the name zip_transform
.您正在寻找的是 range-v3 调用
zip_with
的算法,以及我们在P2214中提出的以名称zip_transform
添加到 C++23 的算法。 There is no such algorithm in C++20. C++20 中没有这样的算法。
Until then, the range-v3 version is exactly your use-case:在此之前,range-v3 版本正是您的用例:
for (auto v : zip_with([](auto a, auto b){ return a + b; }, v0, v1)) {
std::cout << v << ' ';
}
It can handle an arbitrary number of ranges.它可以处理任意数量的范围。
Note that there is no piping version here, just as there is not with regular zip
.请注意,这里没有管道版本,就像没有常规
zip
。
The answer blow is how I envisioned to answer the question and I think it still contains some interesting bits on how to actually implement a view.答案是我设想如何回答这个问题,我认为它仍然包含一些关于如何实际实现视图的有趣部分。 It turns out that P2214 mentioned in @Barry's answer has an interesting view (
zip_transform
) which does an intermediate step of the solution posted below but actually fully covers the functionality needed to do a multi-range transform
!事实证明, @Barry 的答案中提到的P2214有一个有趣的视图(
zip_transform
),它执行了下面发布的解决方案的中间步骤,但实际上完全涵盖了进行多范围transform
所需的功能!
It seems there are essentially two ingredients to using std::ranges::views::transform()
with multiple ranges:使用具有多个范围的
std::ranges::views::transform()
似乎基本上有两个要素:
std::tuple
, probably retaining the value category of the respective values.std::tuple
,可能保留各个值的值类别。std::tuple
and possibly use that to call a corresponding n-ary function.std::tuple
并可能使用它来调用相应的 n 元 ZC1C425268E68785D1AB5074C1F1。 Using this idea would allow creating a version of transform()
dealing with an arbitrary number of ranges, although it is easier to take the function object first rather than extract the last element of a parameter pack:使用这个想法将允许创建一个处理任意数量范围的
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)); });
}
The zip
view used by this implementation can be implemented in terms of std::tuple
:此实现使用的
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)...};
}
This implementation makes some decisions about the value_type
and the reference
type and how to keep track of the different ranges.这个实现对
value_type
和reference
类型以及如何跟踪不同的范围做出了一些决定。 Other choices may be more reasonable ( P2214 makes slightly different, probably better, choices).其他选择可能更合理( P2214做出的选择略有不同,可能更好)。 The only tricky bit in this implementation is operating on the
std::tuple
s which requires a parameter pack containing indices or a suitable set of algorithms on std::tuple
s.此实现中唯一棘手的一点是在
std::tuple
上操作,这需要包含索引的参数包或std::tuple
上的一组合适的算法。
With all of that in place a multi-range transform
can be used nicely, eg:有了所有这些,就可以很好地使用多范围
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.