C++20实现pairwise_filter_view:视图适配与推导指南疑问
实现
pairwise_filter_view的范围适配与推导指南 适配规则的技术落地
先明确三条规则的编译期实现逻辑:
- 输入为视图:直接复用原视图类型,无需额外转换
- 非可视图化/非前向范围:通过Concepts触发编译报错
- 前向范围但非视图:自动转为
views::all_t<R>,同时拒绝右值临时输入(防止容器销毁导致视图悬空)
步骤1:用Concepts约束合法输入
先定义概念筛选符合要求的输入类型,把非法输入拦截在编译期:
#include <ranges> #include <concepts> // 约束:必须是前向范围且可被视图化 template <typename R> concept ValidPairwiseFilterInput = std::ranges::forward_range<R> && std::ranges::viewable_range<R>; // 约束:禁止右值临时对象输入(避免悬空指针) template <typename R> concept NonRValueInput = !std::is_rvalue_reference_v<R>;
步骤2:实现pairwise_filter适配器与视图类
核心是适配器对象处理输入转换,视图类实现元素过滤逻辑:
// 核心视图类:继承view_interface获得默认begin/end等接口 template <std::ranges::view V, typename Pred> class pairwise_filter_view : public std::ranges::view_interface<pairwise_filter_view<V, Pred>> { private: V base_; Pred pred_; // 自定义迭代器:核心逻辑是跳过不满足谓词的连续元素对 struct iterator { using iterator_category = typename std::iterator_traits<typename V::iterator>::iterator_category; using value_type = typename std::iterator_traits<typename V::iterator>::value_type; typename V::iterator curr_; typename V::iterator end_; Pred pred_; iterator(typename V::iterator curr, typename V::iterator end, Pred pred) : curr_(curr), end_(end), pred_(std::move(pred)) { // 初始化时跳过首段不满足条件的元素对 skip_invalid(); } void skip_invalid() { while (curr_ != end_) { auto next = curr_; if (++next == end_) break; if (pred_(*curr_, *next)) break; ++curr_; } } value_type operator*() const { return *curr_; } iterator& operator++() { ++curr_; skip_invalid(); return *this; } bool operator==(const iterator& other) const { return curr_ == other.curr_; } }; public: pairwise_filter_view(V base, Pred pred) : base_(std::move(base)), pred_(std::move(pred)) {} auto begin() { return iterator{base_.begin(), base_.end(), pred_}; } auto end() { return iterator{base_.end(), base_.end(), pred_}; } }; // 适配器对象:用于管道操作(容器|pairwise_filter(...)) template <typename Pred> struct pairwise_filter_adaptor { Pred pred_; // 仅接受符合约束的输入 template <ValidPairwiseFilterInput R, NonRValueInput R> constexpr auto operator()(R&& r) const { // 判断输入是否为视图,非视图则转为views::all_t<R> using V = std::conditional_t<std::ranges::view<std::remove_cvref_t<R>>, std::remove_cvref_t<R>, std::ranges::views::all_t<R>>; return pairwise_filter_view<V, Pred>(std::forward<R>(r), pred_); } }; // 工厂函数:简化适配器创建 template <typename Pred> constexpr auto pairwise_filter(Pred pred) { return pairwise_filter_adaptor<Pred>{std::move(pred)}; }
步骤3:添加推导指南自动推导模板参数
推导指南让编译器自动识别输入类型,无需用户手动指定模板参数:
// 通用推导指南:非视图的可视图化范围转为views::all_t<R> template <std::ranges::viewable_range R, typename Pred> pairwise_filter_view(R&&, Pred) -> pairwise_filter_view<std::ranges::views::all_t<R>, Pred>; // 特化推导指南:输入为视图时直接复用原视图类型(优先级高于通用指南) template <std::ranges::view V, typename Pred> pairwise_filter_view(V, Pred) -> pairwise_filter_view<V, Pred>;
- 特化指南优先匹配视图输入,完全符合规则1
- 通用指南处理普通前向范围,自动转为视图类型,符合规则3
步骤4:编译期错误处理示例
非法输入会触发清晰的编译错误:
// 错误:右值临时容器被NonRValueInput拦截 // auto bad = std::string{"yyxx"} | pairwise_filter([](char a, char b){return !(a=='y'&&b=='y');}); // 错误:输入范围不是前向范围(std::istream_view是输入范围) // auto bad2 = std::views::istream<char>(std::cin) | pairwise_filter([](char a, char b){return true;});
测试代码
#include <string> #include <iostream> int main() { std::string s = "yyabyycd"; // 管道操作:自动将std::string转为views::all_t<std::string> auto filtered = s | pairwise_filter([](char a, char b) { return !(a == 'y' && b == 'y'); }); // 输出:abycd for (char c : filtered) { std::cout << c; } std::cout << '\n'; // 输入为视图:直接复用原视图类型 auto v = std::views::all(s); auto filtered_v = v | pairwise_filter([](char a, char b) { return !(a == 'y' && b == 'y'); }); }
关键细节说明
- 推导指南优先级:特化推导指南会优先匹配视图输入,确保视图类型不被额外转换
- 悬空指针防护:
NonRValueInput概念直接拒绝右值临时容器,从根源避免视图持有已销毁对象的指针 - 视图化转换:用
std::conditional_t实现类型分支,非视图的前向范围自动转为views::all_t<R>,符合规则3 - Concepts约束:所有合法性检查都在编译期完成,错误信息清晰,符合现代C++的设计理念
内容的提问来源于stack exchange,提问作者ABu
相关产品推荐
相关产品推荐

