我有一个过滤器,应该有条件地应用于某个范围,并且条件仅在执行时已知。
我可以很容易地做到这一点,将条件放入过滤器中,但这会导致高频检查和结果代码的复杂化(真正的代码并不像示例中那么简单)。
演示:
#include <iostream>
#include <ranges>
#include <vector>
int main()
{
std::vector<int> v = { 0, 1, 2, 3, 4, 5, 6 };
const bool strategy_check_for_zero = true;
{
// Works just fine, but I want to get rid off high frequent check in lambda
auto selected = v | std::views::filter([=](const auto& v) { return !strategy_check_for_zero || v != 0; });
std::ranges::copy(selected, std::ostream_iterator<int>{std::cout, ", "});
std::cout << '\n';
}
{
auto selected2 = std::ranges::subrange(v);
if (strategy_check_for_zero) {
// Impossible because "selected" has abother type
selected2 = v | std::views::filter([=](const auto& v) { return v != 0; });
}
std::ranges::copy(selected2, std::ostream_iterator<int>{std::cout, ", "});
std::cout << '\n';
}
{
// Impossible because v and filtered v have different types
auto selected3 = strategy_check_for_zero ?
v | std::views::filter([=](const auto& v) { return v != 0; }) :
v;
std::ranges::copy(selected3, std::ostream_iterator<int>{std::cout, ", "});
std::cout << '\n';
}
}
当然,向量中的真实数据更加复杂,难以复制,而且向量的大小也很大。
问题是,在下面的示例中,是否可以通过第二种(最好)或至少第三种方式来完成此操作,而无需像
std::ranges::to<std::vector>
和类型擦除这样昂贵且脆弱的方法?
简而言之,如果我有许多条件改变了后续复杂算法要处理的范围,那么提供过滤的推荐方法是什么(这不仅意味着
std::views::filter
,还意味着许多其他类型的过滤可以对代码的后续步骤进行硬嵌入标志检查,例如 std::views::drop
等))范围?
我会一一应用我的所有条件,由于它们交互的复杂性而缩小要使用的集合,因此我正在寻找的方法应该允许一种更新范围。这可能吗?
更难;有时我想删除一些过滤器,如果它导致空集,所以我需要在做出决定之前知道应用过滤器的结果,我准备好采取它。
并且,如果在编译时已知条件,解决方案是否会改变(更容易)?
正如您所指出的,过滤视图与非过滤视图的类型不同(并且类型不可转换)。
您始终可以定义一个可以在构造函数中执行任意工作的函数对象,以达到您(大概)试图实现的性能。
class Filter {
public:
Filter() {
m_strategy_check_for_zero = true /* complex logic */;
}
bool operator()(const auto& elem) const { return !m_strategy_check_for_zero || elem != 0; }
private:
bool m_strategy_check_for_zero;
};
int main()
{
bool strategy_check_for_zero = false;
std::vector<int> v = { 0, 1, 2, 3, 4, 5, 6 };
auto selected = v | std::views::filter(Filter{});
std::ranges::copy(selected, std::ostream_iterator<int>{std::cout, ", "});
return 0;
}
对于通过运行时选择表达两个范围的更普遍的问题,这里是
choice
视图的实现。 它对我们的项目非常有帮助,并且适用于大多数情况(没有声称它已准备好在全球范围内部署)。
#include <cassert>
#include <ranges>
#include <iterator>
#include <vector>
#include <algorithm>
#include <iostream>
////////////////////////////////////////////////////////////////////////////////
template<std::ranges::view V1, std::ranges::view V2>
class choice_view : public std::ranges::view_interface<choice_view<V1, V2>> {
using Iter1 = std::ranges::iterator_t<V1>;
using Iter2 = std::ranges::iterator_t<V2>;
using Sentinel1 = std::ranges::sentinel_t<V1>;
using Sentinel2 = std::ranges::sentinel_t<V2>;
public:
class Iterator {
static constexpr bool allForward = std::ranges::forward_range<V1> && std::ranges::forward_range<V2>;
static constexpr bool allBidirectional =
std::ranges::bidirectional_range<V1> && std::ranges::bidirectional_range<V2>;
static constexpr bool allRandomAccess =
std::ranges::random_access_range<V1> && std::ranges::random_access_range<V2>;
friend class choice_view::Sentinel;
public:
using value_type = std::common_type_t<std::ranges::range_value_t<V1>, std::ranges::range_value_t<V2>>;
using difference_type =
std::common_type_t<std::ranges::range_difference_t<V1>, std::ranges::range_difference_t<V2>>;
using reference_type =
std::common_reference_t<std::ranges::range_reference_t<V1>, std::ranges::range_reference_t<V2>>;
Iterator(){};
Iterator(Iter1 itr1, Iter2 itr2, const choice_view* parent)
: m_iter1{std::move(itr1)}
, m_iter2{std::move(itr2)}
, m_parent{parent}
, m_use_first{m_parent->m_use_first} {};
reference_type operator*() const { return m_use_first ? *m_iter1 : *m_iter2; }
bool operator==(const Iterator& other) const
requires(std::equality_comparable<Iter1> && std::equality_comparable<Iter2>)
{
assert(m_parent == other.m_parent);
return m_use_first ? m_iter1 == other.m_iter1 : m_iter2 == other.m_iter2;
}
bool operator!=(const Iterator& other) const
requires(std::equality_comparable<Iter1> && std::equality_comparable<Iter2>)
{
assert(m_parent == other.m_parent);
return m_use_first ? m_iter1 != other.m_iter1 : m_iter2 != other.m_iter2;
}
Iterator& operator++() {
if (m_use_first) {
++m_iter1;
} else {
++m_iter2;
}
return *this;
}
void operator++(int) { ++(*this); }
Iterator operator++(int)
requires(allForward)
{
Iterator tmp = *this;
++(*this);
return tmp;
}
Iterator& operator--()
requires(allBidirectional)
{
if (m_use_first) {
--m_iter1;
} else {
--m_iter2;
}
return *this;
}
Iterator operator--(int)
requires(allBidirectional)
{
Iterator tmp = *this;
--(*this);
return tmp;
}
// Note: All the operators must be defined separately, since it is not guaranteed
// that operator <=> is defined for Iterator. Concrete example: boost::container::static_vector.
bool operator<(const Iterator& other) const
requires(allRandomAccess)
{
assert(m_parent == other.m_parent);
return m_use_first ? m_iter1 < other.m_iter1 : m_iter2 < other.m_iter2;
}
bool operator<=(const Iterator& other) const
requires(allRandomAccess)
{
assert(m_parent == other.m_parent);
return m_use_first ? m_iter1 <= other.m_iter1 : m_iter2 <= other.m_iter2;
}
bool operator>(const Iterator& other) const
requires(allRandomAccess)
{
assert(m_parent == other.m_parent);
return m_use_first ? m_iter1 > other.m_iter1 : m_iter2 > other.m_iter2;
}
bool operator>=(const Iterator& other) const
requires(allRandomAccess)
{
assert(m_parent == other.m_parent);
return m_use_first ? m_iter1 >= other.m_iter1 : m_iter2 >= other.m_iter2;
}
reference_type operator[](difference_type x) const
requires(allRandomAccess)
{
return m_use_first ? reference_type(m_iter1[std::iter_difference_t<Iter1>(x)])
: reference_type(m_iter2[std::iter_difference_t<Iter2>(x)]);
}
Iterator& operator+=(difference_type x)
requires(allRandomAccess)
{
if (m_use_first) {
m_iter1 += std::iter_difference_t<Iter1>(x);
} else {
m_iter2 += std::iter_difference_t<Iter2>(x);
}
return *this;
}
Iterator& operator-=(difference_type x)
requires(allRandomAccess)
{
if (m_use_first) {
m_iter1 -= std::iter_difference_t<Iter1>(x);
} else {
m_iter2 -= std::iter_difference_t<Iter2>(x);
}
return *this;
}
difference_type operator-(const Iterator& other) const
requires(std::sized_sentinel_for<Iter1, Iter1> && std::sized_sentinel_for<Iter2, Iter2>)
{
assert(m_parent == other.m_parent);
return m_use_first ? difference_type(m_iter1 - other.m_iter1) : difference_type(m_iter2 - other.m_iter2);
}
friend Iterator operator+(const Iterator& i, difference_type x)
requires(allRandomAccess)
{
auto r = i;
r += x;
return r;
}
friend Iterator operator+(difference_type x, const Iterator& i)
requires(allRandomAccess)
{
auto r = i;
r += x;
return r;
}
friend Iterator operator-(const Iterator& i, difference_type x)
requires(allRandomAccess)
{
auto r = i;
r -= x;
return r;
}
friend Iterator operator-(difference_type x, const Iterator& i)
requires(allRandomAccess)
{
auto r = i;
r -= x;
return r;
}
private:
Iter1 m_iter1;
Iter2 m_iter2;
const choice_view* m_parent;
bool m_use_first;
};
class Sentinel {
public:
Sentinel() = default;
Sentinel(Sentinel1 sentinel1, Sentinel2 sentinel2, const choice_view* parent)
: m_sentinel1{std::move(sentinel1)}
, m_sentinel2{std::move(sentinel2)}
, m_parent{parent}
, m_use_first{m_parent->m_use_first} {};
bool operator==(const Iterator& itr) const {
assert(itr.m_parent == m_parent);
return m_use_first ? itr.m_iter1 == m_sentinel1 : itr.m_iter2 == m_sentinel2;
}
private:
Sentinel1 m_sentinel1;
Sentinel2 m_sentinel2;
const choice_view* m_parent;
bool m_use_first;
};
static_assert(std::sentinel_for<Sentinel, Iterator>);
choice_view() = default;
choice_view(V1 view1, V2 view2, bool useFirst)
: m_view1{std::move(view1)}, m_view2{std::move(view2)}, m_use_first{useFirst} {}
auto begin() { return Iterator{m_view1.begin(), m_view2.begin(), this}; }
auto end() {
if constexpr (std::ranges::common_range<V1> && std::ranges::common_range<V2>) {
return Iterator{m_view1.end(), m_view2.end(), this};
} else {
return Sentinel{m_view1.end(), m_view2.end(), this};
}
}
auto size() const
requires(std::ranges::sized_range<const V1> && std::ranges::sized_range<const V2>)
{
using CT = std::common_type_t<decltype(std::ranges::size(m_view1)), decltype(std::ranges::size(m_view2))>;
return m_use_first ? CT(std::ranges::size(m_view1)) : CT(std::ranges::size(m_view2));
}
private:
V1 m_view1;
V2 m_view2;
bool m_use_first;
};
// Choice view that can be used to dynamically select between two compatible views based on a run-time value.
inline constexpr auto choice =
[]<std::ranges::viewable_range R1, std::ranges::viewable_range R2>(bool useFirst, R1&& rng1, R2&& rng2) {
return choice_view(std::views::all(std::forward<R1>(rng1)), std::views::all(std::forward<R2>(rng2)), useFirst);
};
////////////////////////////////////////////////////////////////////////////////
int main()
{
bool strategy_check_for_zero = false;
std::vector<int> v{0,1,2,3};
auto selected = choice(strategy_check_for_zero,
v | std::views::filter([=](const auto& v) { return v != 0; }),
v);
std::ranges::copy(selected, std::ostream_iterator<int>{std::cout, ", "});
return 0;
}