C++23 std::ranges::enumerate能否自定义索引类型?替代方案有哪些?
问题:如何让枚举视图生成自定义强类型索引?
背景
C++23引入的std::ranges::views::enumerate是备受期待的工具,遍历它时迭代器解引用会得到包含索引和元素引用的元组。根据标准规定,这个索引的类型固定为range_difference_t<Base>,也就是底层范围的差值类型。
但在实际场景中,我们可能会使用自定义容器,比如仅允许用强类型索引访问元素的StrongVector——这种索引是基础整数的包装器,禁止与基础整数的隐式转换,以此避免索引混用的错误。此时std::ranges::enumerate返回的默认索引类型就无法直接用于容器的operator[]或at()方法。
核心疑问
- 能否修改
std::ranges::enumerate,让它生成自定义的强类型索引? - 如果不行,有哪些可行的替代方案?
场景代码示例
template <class T, class Tag> struct StrongT { explicit StrongT(T t) : _t(t) {} // 禁止隐式构造 T base() const { return _t; } // 仅提供显式获取基础值的方法 // 补充必要的算术、比较运算符(如++, ==, <等) private: T _t; }; // 自定义强类型索引 using MyIdx = StrongT<int, struct MyTag>; // 仅允许用MyIdx访问的自定义Vector using MyVec = StrongVector<std::string, MyIdx>; int main () { MyVec v {"hello", "world"}; // 此处无法通过编译:decltype(idx)是int(range_difference_t),不是MyIdx for ( auto [idx, str] : v | std::ranges::enumerate ) { assert(v[idx] == str); } }
解答
关于能否修改std::ranges::enumerate的索引类型
不能。C++标准明确规定std::ranges::enumerate的索引类型为range_difference_t<Base>,这是该视图的既定行为,无法通过参数或模板特化修改。
替代方案
方案一:自定义枚举视图,控制索引类型
自己实现一个支持自定义索引类型的enumerate视图,完全掌控返回的索引类型。示例代码如下:
#include <ranges> #include <utility> #include <iterator> #include <cassert> // 自定义强类型索引模板 template <class T, class Tag> struct StrongT { using value_type = T; explicit StrongT(T t) : _t(t) {} T base() const { return _t; } // 补充必要的运算符 bool operator==(const StrongT& other) const = default; private: T _t; }; using MyIdx = StrongT<int, struct MyTag>; // 自定义Vector实现 template<class T, class Idx> struct StrongVector { std::vector<T> data_; StrongVector(std::initializer_list<T> init) : data_(init) {} T& operator[](Idx idx) { return data_[idx.base()]; } const T& operator[](Idx idx) const { return data_[idx.base()]; } }; using MyVec = StrongVector<std::string, MyIdx>; // 自定义enumerate视图 template<std::ranges::input_range R, class Idx> struct enumerate_view : std::ranges::view_interface<enumerate_view<R, Idx>> { private: R base_ = R(); public: enumerate_view() = default; explicit enumerate_view(R r) : base_(std::move(r)) {} struct iterator { using base_iterator = std::ranges::iterator_t<R>; using difference_type = std::ranges::range_difference_t<R>; using value_type = std::pair<Idx, std::ranges::range_reference_t<R>>; base_iterator it_; difference_type pos_ = 0; iterator() = default; iterator(base_iterator it, difference_type pos) : it_(it), pos_(pos) {} value_type operator*() const { // 将底层差值类型转换为自定义强类型索引 return {Idx(static_cast<typename Idx::value_type>(pos_)), *it_}; } iterator& operator++() { ++it_; ++pos_; return *this; } iterator operator++(int) { auto tmp = *this; ++*this; return tmp; } bool operator==(const iterator& other) const = default; }; iterator begin() { return {std::ranges::begin(base_), 0}; } iterator end() { return {std::ranges::end(base_), std::ranges::distance(base_)}; } }; // 封装成视图适配器,方便管道调用 namespace views { template<class Idx> auto enumerate = [](auto&& r) { return enumerate_view<std::views::all_t<decltype(r)>, Idx>(std::forward<decltype(r)>(r)); }; } int main () { MyVec v {"hello", "world"}; // 使用自定义视图,直接得到MyIdx类型的索引 for ( auto [idx, str] : v | views::enumerate<MyIdx> ) { assert(v[idx] == str); // 正常编译并运行 } }
方案二:遍历中显式转换索引类型
如果不想自定义视图,可以在遍历std::ranges::enumerate时,将返回的默认索引显式转换为自定义强类型。这种方式简单直接,但需要手动处理转换:
int main () { MyVec v {"hello", "world"}; for ( auto [raw_idx, str] : v | std::ranges::enumerate ) { // 显式构造自定义索引 MyIdx idx(static_cast<int>(raw_idx)); assert(v[idx] == str); } }
方案三:给自定义容器添加枚举成员函数
为StrongVector添加一个成员函数,内部封装自定义枚举视图的调用,让使用更简洁:
template<class T, class Idx> struct StrongVector { // ... 其他代码 ... auto enumerate() { return views::enumerate<Idx>(*this); } }; // 使用方式 int main () { MyVec v {"hello", "world"}; for ( auto [idx, str] : v.enumerate() ) { assert(v[idx] == str); } }
内容的提问来源于stack exchange,提问作者supernun
相关产品推荐
相关产品推荐

