自定义deref视图适配vector但无法适配array/initializer_list问题排查
问题:自定义deref视图无法适配std::array与std::initializer_list
我基于ranges-v3库实现了一个deref视图,用于调用元素的.get()方法解引用std::reference_wrapper。该视图处理std::vector时表现正常,但无法应用于std::array或std::initializer_list容器。
完整代码
#include <array> #include <iostream> #include <random> #include <range/v3/all.hpp> #include <string> #include <type_traits> #include <unordered_set> #include <vector> #include "fmt/format.h" #include "fmt/ranges.h" template <typename T> decltype(auto) deref(T&& t) { return std::forward<T>(t).get(); } template <ranges::range Range> class deref_view : public ranges::view_base { public: struct iterator; deref_view() = default; deref_view(ranges::range auto&& base) : m_base(base) {} iterator begin() { return ranges::begin(m_base); } iterator end() { return ranges::end(m_base); } private: Range m_base; }; template <ranges::range Range> struct deref_view<Range>::iterator : public ranges::iterator_t<Range> { using base = ranges::iterator_t<Range>; using value_type = std::remove_cvref_t<decltype(deref(*(std::declval<Range>().begin())))>; using difference_type = ranges::range_difference_t<Range>; iterator() = default; iterator(const base& b) : base{b} {} iterator operator++(int) { return static_cast<base&>(*this)++; } iterator& operator++() { ++static_cast<base&>(*this); return (*this); } decltype(auto) operator*() const { return deref(*static_cast<base>(*this)); } }; template <ranges::range Range> deref_view(Range&&) -> deref_view<ranges::cpp20::views::all_t<Range>>; struct deref_fn { template <typename Rng> auto operator()(Rng&& rng) const { return deref_view{ranges::views::all(std::forward<Rng>(rng))}; } template <typename Rng> friend auto operator|(Rng&& rng, deref_fn const&) { return deref_view{ranges::views::all(std::forward<Rng>(rng))}; } }; namespace ranges::views { constexpr deref_fn deref{}; } int main() { std::vector v{1, 2, 3, 4, 5, 6}; // auto list = {std::ref(v), std::ref(v)}; // 无法工作 auto list = std::array{std::ref(v), std::ref(v)}; // 无法工作 // auto list = std::vector{std::ref(v), std::ref(v)}; // 可以正常工作 for (const auto& arr : list | ranges::views::deref) { fmt::print("{}\n", arr); } return 0; }
处理std::array时的错误信息
<source>: In instantiation of 'struct deref_view<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >::iterator': <source>:120:49: required from here <source>:66:29: error: base type 'ranges::iterator_t<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >' {aka 'std::reference_wrapper<std::vector<int> >*'} fails to be a struct or class type 66 | struct deref_view< Range >::iterator: public ranges::iterator_t< Range > { | ^~~~~~~~ <source>: In function 'int main()': <source>:120:49: error: no match for 'operator!=' (operand types are 'deref_view<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >::iterator' and 'deref_view<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >::iterator') 120 | for(const auto& arr : list | ranges::views::deref) { | ^~~~~ <source>: In instantiation of 'decltype(auto) deref_view<Range>::iterator::operator*() const [with Range = ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> >]': <source>:120:49: required from here <source>:83:53: error: invalid 'static_cast' from type 'const deref_view<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >::iterator' to type 'deref_view<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >::iterator::base' {aka 'std::reference_wrapper<std::vector<int> >*'} 83 | decltype(auto) operator*() const { return deref(*static_cast< base >(*this)); } | ^~~~~~~~~~~~~~~~~~~~~~~~~~
提问核心
我在ranges处理array和vector的机制上遗漏了哪些细节?(该问题在std::ranges中同样存在)
解答
错误根源:迭代器类型的本质差异
报错的直接原因是:
std::vector的迭代器是类模板实例(比如std::vector<T>::iterator),属于用户定义类型,可以被继承;- 而
std::array的迭代器在多数编译器实现中是原生指针(因为std::array是固定大小的连续内存,编译器会做优化),指针是C++内置类型,不能作为基类被继承,这就是代码中struct iterator : public ranges::iterator_t<Range>报错的核心原因。
两种解决方案
方案1:用组合替代继承实现迭代器
放弃继承基础迭代器的方式,改为在自定义迭代器中持有基础迭代器的实例(组合模式),这样无论是类类型迭代器还是原生指针都能兼容。
修改后的iterator结构体代码:
template <ranges::range Range> struct deref_view<Range>::iterator { using base = ranges::iterator_t<Range>; using value_type = std::remove_cvref_t<decltype(deref(*std::declval<base>()))>; using difference_type = ranges::range_difference_t<Range>; using pointer = std::add_pointer_t<value_type>; using reference = decltype(deref(*std::declval<base>())); using iterator_category = typename std::iterator_traits<base>::iterator_category; iterator() = default; explicit iterator(base it) : m_it(std::move(it)) {} // 后置++ iterator operator++(int) { auto temp = *this; ++m_it; return temp; } // 前置++ iterator& operator++() { ++m_it; return *this; } // 相等比较 bool operator==(const iterator& other) const { return m_it == other.m_it; } // 解引用 decltype(auto) operator*() const { return deref(*m_it); } // 根据需要实现其他迭代器操作:--, +=, -=, []等(取决于基础迭代器的类别) private: base m_it; };
同时需要修改deref_view的begin和end方法,显式构造自定义迭代器:
iterator begin() { return iterator(ranges::begin(m_base)); } iterator end() { return iterator(ranges::end(m_base)); }
方案2:复用ranges::views::transform简化实现
实际上,deref视图的本质就是对范围中的每个元素应用deref函数,完全可以直接复用ranges-v3提供的transform视图,无需自己从零实现视图和迭代器,避免重复造轮子的错误。
简化后的代码:
#include <array> #include <iostream> #include <range/v3/all.hpp> #include <vector> #include "fmt/format.h" #include "fmt/ranges.h" template <typename T> decltype(auto) deref(T&& t) { return std::forward<T>(t).get(); } struct deref_fn { template <typename Rng> auto operator()(Rng&& rng) const { return ranges::views::all(std::forward<Rng>(rng)) | ranges::views::transform(deref); } template <typename Rng> friend auto operator|(Rng&& rng, deref_fn const&) { return ranges::views::all(std::forward<Rng>(rng)) | ranges::views::transform(deref); } }; namespace ranges::views { constexpr deref_fn deref{}; } int main() { std::vector v{1, 2, 3, 4, 5, 6}; auto list = std::array{std::ref(v), std::ref(v)}; // auto list = {std::ref(v), std::ref(v)}; // 现在也能正常工作 // auto list = std::vector{std::ref(v), std::ref(v)}; for (const auto& arr : list | ranges::views::deref) { fmt::print("{}\n", arr); } return 0; }
总结
- 核心遗漏点:不同容器的迭代器类型可能存在本质差异,
std::vector迭代器是类类型,而std::array迭代器可能是原生指针,原生指针无法被继承; - 自定义视图时,优先选择组合模式封装基础迭代器,或者直接复用现有标准视图(如
transform),避免因迭代器类型差异导致的兼容性问题。
内容的提问来源于stack exchange,提问作者Michael A
相关产品推荐
相关产品推荐

