You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现take_while_inclusive:包含不满足谓词元素的std::views::take_while变体

实现支持编译期的views::take_while_inclusive视图(满足单次遍历需求)

我正在用std::ranges开发一个简单解析器,想通过try_parse_ints(str) | take_while(is_valid_int)的方式解析字符串中的整数,直到全部转换完成或出现转换失败。但出错时,我希望能获取导致错误的最后一个结果并返回给用户。

因此我需要实现一个views::take_while_inclusive,它会在返回第一个不满足谓词的元素之后才停止迭代。

我曾尝试用现有std::views实现,通过在谓词中隐藏static变量的技巧写出如下代码:

constexpr auto take_while_inclusive(auto&& predicate) {
    const auto custom_predicate =
        [predicate =
             std::forward<decltype(predicate)>(predicate)](auto&& value) {
            static bool found = true;
            return std::exchange(found, predicate(value));
        };
    return std::views::take_while(custom_predicate);
}

但我知道take_while的谓词应当是const的,且这种实现依赖全局static状态,存在多实例冲突问题。同时我还有两个核心需求:

  • 要像标准std::views那样实时生成范围,且仅遍历输入范围一次
  • 支持编译期使用

符合标准的自定义视图实现

下面是一个完全符合C++20范围标准的实现,满足所有需求:

#include <ranges>
#include <iterator>
#include <concepts>

namespace views {
template<std::ranges::input_range V, std::predicate<std::ranges::range_value_t<V>> Pred>
requires std::ranges::view<V>
class take_while_inclusive_view : public std::ranges::view_interface<take_while_inclusive_view<V, Pred>> {
private:
    V base_;
    Pred pred_;

    template<bool Const>
    class iterator {
    private:
        using Base = std::conditional_t<Const, const V, V>;
        using BaseIter = std::ranges::iterator_t<Base>;
        BaseIter current_;
        BaseIter end_;
        Pred* pred_;
        bool done_ = false;

    public:
        using iterator_concept = std::input_iterator_tag;
        using value_type = std::ranges::range_value_t<Base>;
        using difference_type = std::ranges::range_difference_t<Base>;

        iterator() = default;
        constexpr iterator(BaseIter current, BaseIter end, Pred* pred)
            : current_(std::move(current)), end_(std::move(end)), pred_(pred), done_(false) {}

        constexpr value_type operator*() const { return *current_; }
        constexpr BaseIter operator->() const requires std::has_arrow<BaseIter> { return current_; }

        constexpr iterator& operator++() {
            if (!done_) {
                if (current_ != end_ && !(*pred_)(*current_)) {
                    done_ = true;
                } else {
                    ++current_;
                }
            }
            return *this;
        }

        constexpr iterator operator++(int) {
            auto tmp = *this;
            ++*this;
            return tmp;
        }

        friend constexpr bool operator==(const iterator& lhs, const iterator& rhs) {
            if (lhs.done_ || rhs.done_) {
                return lhs.done_ == rhs.done_;
            }
            return lhs.current_ == rhs.current_;
        }
    };

public:
    take_while_inclusive_view() = default;
    constexpr take_while_inclusive_view(V base, Pred pred)
        : base_(std::move(base)), pred_(std::move(pred)) {}

    constexpr auto begin() { return iterator<false>(std::ranges::begin(base_), std::ranges::end(base_), &pred_); }
    constexpr auto begin() const requires std::ranges::range<const V> && std::predicate<const Pred, std::ranges::range_value_t<const V>> {
        return iterator<true>(std::ranges::begin(base_), std::ranges::end(base_), const_cast<Pred*>(&pred_));
    }

    constexpr auto end() { return iterator<false>(std::ranges::end(base_), std::ranges::end(base_), &pred_); }
    constexpr auto end() const requires std::ranges::range<const V> {
        return iterator<true>(std::ranges::end(base_), std::ranges::end(base_), const_cast<Pred*>(&pred_));
    }
};

namespace detail {
struct take_while_inclusive_fn {
    template<std::ranges::input_range R, std::predicate<std::ranges::range_value_t<R>> Pred>
    requires std::ranges::viewable_range<R>
    constexpr auto operator()(R&& r, Pred pred) const {
        return take_while_inclusive_view<std::views::all_t<R>, Pred>(std::views::all(std::forward<R>(r)), std::move(pred));
    }

    template<std::predicate Pred>
    constexpr auto operator()(Pred pred) const {
        return [pred = std::move(pred)](std::ranges::input_range auto&& r) requires std::ranges::viewable_range<decltype(r)> {
            return take_while_inclusive_view<std::views::all_t<decltype(r)>, Pred>(std::views::all(std::forward<decltype(r)>(r)), std::move(pred));
        };
    }
};
} // namespace detail

inline constexpr detail::take_while_inclusive_fn take_while_inclusive;
} // namespace views

实现特点

  1. 编译期支持:所有核心成员函数都标记为constexpr,只要输入范围和谓词支持编译期操作,就能在编译期完成遍历和筛选。
  2. 单次遍历:迭代器仅遍历输入范围一次,每次++操作仅移动一次底层迭代器,符合输入迭代器的性能要求。
  3. 无全局状态:迭代状态(是否已遇到不满足谓词的元素)存储在迭代器实例内部,不会出现多视图实例冲突问题。
  4. 标准兼容性:继承自std::ranges::view_interface,完全满足std::ranges::view概念,可以和其他标准视图自由组合。

使用示例

constexpr bool is_valid_int(int x) {
    return x >= 0;
}

constexpr std::array<int, 5> nums = {1, 2, -3, 4, 5};

// 结果包含1, 2, -3
constexpr auto result = nums | views::take_while_inclusive(is_valid_int);

内容的提问来源于stack exchange,提问作者fourmisnuee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 05:09:58