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

自定义可管道视图适配器带Lambda捕获时与join管道编译失败

自定义split_when视图适配器带捕获Lambda编译失败问题排查

问题概述

自定义的可管道split_when视图适配器,无捕获Lambda版本可正常运行,甚至能配合std::views::join工作,但使用带捕获Lambda时编译失败,报错提示operator|不匹配,类型不兼容。


正常运行示例

无捕获Lambda版本

auto main() -> int {
    auto v = std::vector<int>{1, 2, 3, 4, 5};
    auto split = v | so::views::splitWhen([](int n) { return n % 2 == 0; });

    for (auto range : split) {
        for (auto e : range) {
            std::cout << e << ' ';
        }
        std::cout << '\n';
    }
}

输出:

1 2
3 4
5

配合std::views::join版本

auto main() -> int {
    auto v = std::vector<int>{1, 2, 3, 4, 5};
    auto split = v | so::views::splitWhen([](int n) { return n % 2 == 0; })
                   | std::views::join;

    for (auto e : split) {
        std::cout << e << ' ';
    }
}

输出:

1 2 3 4 5

带捕获Lambda的错误示例

auto main() -> int {
    auto v = std::vector<int>{1, 2, 3, 4, 5};
    auto divisor = 2;
    auto split = v | so::views::splitWhen([divisor](int n) { return divisor % 2 == 0; })
                   | std::views::join;

    for (auto e : split) {
        std::cout << e << ' ';
    }
}

编译错误信息

error: no match for 'operator|' (operand types are 'so::views::SplitWhenView<std::ranges::ref_view<std::vector<int> >, main()::<lambda(int)> >' and 'const std::ranges::views::_Join')
note:   'std::ranges::views::__adaptor::_RangeAdaptorClosure' is not a base of 'so::views::SplitWhenView<std::ranges::ref_view<std::vector<int> >, main()::<lambda(int)> >'

完整实现代码

自定义bind_back实现

namespace so {
    template <class ConstFn, class... Args>
    constexpr auto bind_back(ConstFn fn, Args&& ... args) {
        using F = ConstFn;

        if constexpr (std::is_pointer_v<F> or
                      std::is_member_pointer_v<F>)
            static_assert(fn != nullptr);

        return
        [fn, ... bound_args(
                std::forward<Args>(args))]<class... T>
        (
                T &&... call_args
        )
        {
            return std::invoke(fn, std::forward<T>(call_args)..., bound_args...);
        };
    }
}

P2387辅助工厂

namespace so::views {
    template <typename F>
    class closure : public std::ranges::range_adaptor_closure<closure<F>> {
        F f;
    public:
        constexpr closure(F f) : f(f) { }

        template <std::ranges::viewable_range R>
        requires std::invocable<F const&, R>
        constexpr decltype(auto) operator()(R&& r) const {
            return f(std::forward<R>(r));
        }
    };

    template <typename F>
    class adaptor {
        F f;
    public:
        constexpr adaptor(F f) : f(f) { }

        template <typename... Args>
        constexpr decltype(auto) operator()(Args&&... args) const {
            if constexpr (std::invocable<F const&, Args...>) {
                return f(std::forward<Args>(args)...);
            } else {
                return closure(bind_back(f, std::forward<Args>(args)...));
            }
        }
    };
}

SplitWhenView及迭代器实现

namespace so::views {
    template <std::ranges::input_range View, std::predicate<std::ranges::range_value_t<View>> F> requires std::ranges::view<View>
    class SplitWhenView
            : public std::ranges::view_interface<SplitWhenView<View, F>> {

        View base_;
        F predicate_;

    public:

        SplitWhenView() = default;
        SplitWhenView(View base, F predicate) : base_(std::move(base)), predicate_(std::move(predicate)) {}

        auto base() { return base_; }

        template <std::input_iterator It, class Predicate>
        class SplitWhenIterator;

        using iterator = SplitWhenIterator<std::ranges::iterator_t<View>, F*>;

        auto begin() { return SplitWhenIterator(std::ranges::begin(base_), std::ranges::end(base_), &predicate_); }
        auto end() { return SplitWhenIterator(std::ranges::end(base_), std::ranges::end(base_), &predicate_); }

    };

    template <std::ranges::input_range View, std::predicate<std::ranges::range_value_t<View>> F> requires
    std::ranges::view<View>
    template <std::input_iterator It, class Predicate>
    class SplitWhenView<View, F>::SplitWhenIterator {

        It current_, end_;
        Predicate predicate_;

    public:

        using value_type = std::ranges::subrange<It>;
        using difference_type = std::ranges::range_difference_t<View>;

        SplitWhenIterator() = default;

        SplitWhenIterator(It begin, It end, Predicate func)
                : current_{begin}, end_{end}, predicate_{func} {}

        auto operator++() -> SplitWhenIterator& {
            current_ = std::ranges::find_if(current_, end_, *predicate_);
            if (current_ != end_)
                ++current_;

            return *this;
        }

        auto operator++(int) -> auto {
            if constexpr (std::forward_iterator<It>) {
                auto tmp = *this;
                ++*this;
                return tmp;
            } else
                ++*this;
        }

        auto operator*() const -> std::ranges::subrange<It> {
            auto next = std::ranges::find_if(current_, end_, *predicate_);
            if (next != end_) return {current_, std::next(next)};
            return {current_, next};
        }

        auto operator<=>(const SplitWhenIterator& rhs) const -> auto {
            return std::tie(current_, end_) <=> std::tie(rhs.current_, rhs.end_);
        }

        auto operator==(const SplitWhenIterator& rhs) const -> bool {
            return std::tie(current_, end_) == std::tie(rhs.current_, rhs.end_);
        }
    };

    template <std::ranges::range R, std::predicate<std::ranges::range_value_t<R>> F>
    SplitWhenView(R&&, F) -> SplitWhenView<std::views::all_t<R>, F>;
}

splitWhen适配器定义

namespace so::views {
    inline constexpr adaptor splitWhen =
            []<std::ranges::viewable_range Range, std::predicate<std::ranges::range_value_t<Range>> Predicate>(
                    Range&& range, Predicate&& predicate
            ) {
                return SplitWhenView(std::forward<Range>(range), std::forward<Predicate>(predicate));
            };
}

问题原因分析

核心问题出在SplitWhenIterator的设计:

  1. 原实现中,迭代器直接存储了谓词predicate_的副本。对于无捕获Lambda(无状态),所有副本行为等价,迭代器的operator==仅比较current_和end_不会触发问题;但带捕获Lambda是有状态的,不同副本的谓词可能行为不同,此时operator==忽略predicate_的比较,违反了C++迭代器的equality_comparable概念要求(相等的迭代器必须有一致的解引用/递增行为)。
  2. 由于迭代器不满足input_iterator概念,导致SplitWhenView无法满足std::ranges::input_range,而std::views::join要求输入的范围必须是input_range,因此编译器找不到匹配的operator|重载。

解决方法

修改迭代器的设计,让其存储指向SplitWhenView中谓词的指针,而非副本,这样所有迭代器共享同一个谓词实例,无需比较谓词即可满足迭代器的相等性要求:

具体修改步骤

  1. 调整SplitWhenIterator的模板参数和成员:
    • 将迭代器的模板参数Predicate改为指针类型(对应SplitWhenView中F的指针)
    • 成员变量predicate_改为指针类型,不再存储副本
  2. 修改SplitWhenView的iterator类型定义:
    using iterator = SplitWhenIterator<std::ranges::iterator_t<View>, F*>;
    
  3. 修改SplitWhenView的begin/end方法:
    传递谓词的地址给迭代器:
    auto begin() { return SplitWhenIterator(std::ranges::begin(base_), std::ranges::end(base_), &predicate_); }
    auto end() { return SplitWhenIterator(std::ranges::end(base_), std::ranges::end(base_), &predicate_); }
    
  4. 修改迭代器中谓词的调用方式:
    解引用指针后调用谓词,例如:
    current_ = std::ranges::find_if(current_, end_, *predicate_);
    

修改后的代码既避免了带捕获Lambda的复制问题,又满足了迭代器的概念要求,带捕获Lambda的场景即可正常编译运行。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 18:57:02