为何std::contiguous_iterator要求值类型为引用?迭代器类型适配疑问
std::contiguous_iterator的问题 来自cppreference的
contiguous_iterator概念定义:
template< class I > concept contiguous_iterator = std::random_access_iterator<I> && std::derived_from</*ITER_CONCEPT*/<I>, std::contiguous_iterator_tag> && std::is_lvalue_reference_v<std::iter_reference_t<I>> && std::same_as<std::iter_value_t<I>, std::remove_cvref_t<std::iter_reference_t<I>>> && requires(const I& i) { { std::to_address(i) } -> std::same_as<std::add_pointer_t<std::iter_reference_t<I>>>; };
我有一个概念上类似字节vector的容器(实际是torch tensor),想要提供遍历连续固定长度数据段的方式——数据段是通过索引第0维度得到的torch tensor。数据是连续存储的,每个迭代元素本质是指向容器中固定大小数据的span,我认为可以实现std::contiguous_iterator,但不确定迭代器中的pointer和reference类型该如何定义。
简化后的容器代码如下:
class Container : std::vector<std::byte> { public: Container(std::vector<std::byte> data, std::size_t chunk_size); class Iterator { public: using difference_type = std::ptrdiff_t; using value_type = std::span<std::byte>; // 长度为_chunk_size的span using pointer = ?; using reference = ?; using iterator_concept = std::random_access_iterator_tag; }; std::size_t _chunk_size; };
理想情况下pointer和reference都想用std::span<std::byte>,但这会违反contiguous_iterator概念的最后三个条件。如果块大小是编译期已知的,用std::array<std::byte, chunk_size>作为value_type,搭配对应的指针和引用类型就能满足要求,但我不知道在运行时块大小的场景下该怎么实现。
核心问题分析
contiguous_iterator有几个硬性要求:
reference必须是左值引用类型(对应std::is_lvalue_reference_v检查)value_type要和reference移除cv和引用后的类型完全一致std::to_address(i)必须返回对应reference的指针类型
而std::span是值类型,没法直接作为左值引用满足要求——这就是运行时块大小场景的核心矛盾:编译期固定大小的std::array可以直接取左值引用,但运行时大小的span做不到。
可行实现方案
1. 自定义可被引用的"块视图"类型
不要直接用std::span作为value_type,而是自定义一个轻量视图类,它持有指向容器数据的指针和块大小,并且能返回合法的左值引用:
class Container : std::vector<std::byte> { public: Container(std::vector<std::byte> data, std::size_t chunk_size) : std::vector<std::byte>(std::move(data)), _chunk_size(chunk_size) {} // 自定义块视图,封装数据指针和块大小 class ChunkView { private: std::byte* _data; std::size_t _size; public: ChunkView(std::byte* data, std::size_t size) : _data(data), _size(size) {} // 隐式转换为span,兼容原有使用习惯 operator std::span<std::byte>() const { return {_data, _size}; } // 提供元素直接访问 std::byte& operator[](std::size_t idx) { return _data[idx]; } const std::byte& operator[](std::size_t idx) const { return _data[idx]; } std::size_t size() const { return _size; } std::byte* data() { return _data; } const std::byte* data() const { return _data; } }; class Iterator { public: using difference_type = std::ptrdiff_t; using value_type = ChunkView; using pointer = ChunkView*; using reference = ChunkView&; // 注意这里要改成contiguous_iterator_tag,而非random_access using iterator_concept = std::contiguous_iterator_tag; Iterator(std::byte* ptr, std::size_t chunk_size) : _ptr(ptr), _chunk_size(chunk_size) {} // 实现随机访问迭代器的所有操作 Iterator& operator++() { _ptr += _chunk_size; return *this; } Iterator operator++(int) { auto tmp = *this; ++*this; return tmp; } Iterator& operator--() { _ptr -= _chunk_size; return *this; } Iterator operator--(int) { auto tmp = *this; --*this; return tmp; } Iterator& operator+=(difference_type n) { _ptr += n * _chunk_size; return *this; } Iterator operator+(difference_type n) const { auto tmp = *this; tmp += n; return tmp; } Iterator& operator-=(difference_type n) { _ptr -= n * _chunk_size; return *this; } Iterator operator-(difference_type n) const { auto tmp = *this; tmp -= n; return tmp; } difference_type operator-(const Iterator& other) const { return (_ptr - other._ptr) / _chunk_size; } // 默认生成比较运算符 bool operator==(const Iterator& other) const = default; std::strong_ordering operator<=>(const Iterator& other) const = default; // 解引用返回左值引用,需缓存视图确保引用有效 reference operator*() { _current_view = ChunkView(_ptr, _chunk_size); return _current_view; } pointer operator->() { _current_view = ChunkView(_ptr, _chunk_size); return &_current_view; } // 实现std::to_address的要求 friend std::byte* to_address(const Iterator& it) { return it._ptr; } private: std::byte* _ptr; std::size_t _chunk_size; // 缓存当前视图,用于返回合法左值引用 mutable ChunkView _current_view{nullptr, 0}; }; Iterator begin() { return Iterator(data(), _chunk_size); } Iterator end() { return Iterator(data() + size(), _chunk_size); } private: std::size_t _chunk_size; };
2. 放弃严格满足contiguous_iterator,改用random_access_iterator
如果不需要依赖contiguous_iterator的优化算法,只需要随机访问能力,可以直接用std::span<std::byte>作为value_type,这种方式更简单:
class Container : std::vector<std::byte> { public: Container(std::vector<std::byte> data, std::size_t chunk_size) : std::vector<std::byte>(std::move(data)), _chunk_size(chunk_size) {} class Iterator { public: using difference_type = std::ptrdiff_t; using value_type = std::span<std::byte>; using pointer = void*; // 部分算法可能需要,也可忽略 using reference = std::span<std::byte>; using iterator_concept = std::random_access_iterator_tag; Iterator(std::byte* ptr, std::size_t chunk_size) : _ptr(ptr), _chunk_size(chunk_size) {} // 实现随机访问迭代器的所有操作(和方案1类似) Iterator& operator++() { _ptr += _chunk_size; return *this; } // ...省略其他算术、比较操作... reference operator*() const { return {_ptr, _chunk_size}; } bool operator==(const Iterator& other) const = default; std::strong_ordering operator<=>(const Iterator& other) const = default; private: std::byte* _ptr; std::size_t _chunk_size; }; Iterator begin() { return Iterator(data(), _chunk_size); } Iterator end() { return Iterator(data() + size(), _chunk_size); } private: std::size_t _chunk_size; };
关键说明
- 方案1通过自定义
ChunkView类型,让reference成为合法左值引用,完全满足contiguous_iterator的所有条件,能使用依赖该概念的优化算法。 - 方案2更简洁,但只能满足
random_access_iterator,无法享受contiguous_iterator带来的性能优化,但依然可以正常使用范围for、大部分标准算法。 - 不要试图让
std::span作为左值引用——span本身是值语义的视图,它的引用是对span对象的引用,而非对底层数据块的逻辑引用,不符合contiguous_iterator对reference的语义要求。
内容的提问来源于stack exchange,提问作者rdong8

