如何实现可移动的暂存式输入迭代器?
我实现了一个输入迭代器,用于遍历文件并沿途解析记录,每个解析后的记录会暂存在迭代器内部并由其持有。因为记录体积大、数量多,我不想做拷贝,所以把解引用操作定义为:
Record& operator*() { return this->record; }
这段代码可以正常工作:
Record r = std::move(*it); // 无拷贝
但当我尝试用C++20概念做编译时检查,添加:
static_assert(std::input_iterator<Iterator>);
检查失败了——因为std::input_iterator依赖的std::indirectly_readable要求const Iterator可以被解引用。可我没法给operator*加const限定,否则会返回const引用,导致std::move实际执行拷贝:
const Record& operator*() const { return this->record; } Record r = std::move(*it); // 会拷贝,因为无法移动const对象
我发现用static_assert(std::input_or_output_iterator)能通过检查,但我的迭代器本质上不是输出迭代器,这让我很困惑。
另外还有个相关问题:std::move_iterator和这个可变解引用操作不兼容,因为它的operator*()是const的:
reference operator*() const { return ranges::iter_move(__current_); }
这里__current_会绑定到我的Iterator实例的const版本,iter_move尝试解引用时会失败;就算给我的迭代器加了const解引用,也会错误地执行拷贝而非移动。
请问有哪些替代设计方案,既能实现暂存式迭代器的可移动性,又能兼容std::move_iterator?
1. 自定义iter_move重载适配const迭代器
C++20迭代器概念允许通过自定义std::iter_move提供移动语义,无需依赖const迭代器的解引用返回非const引用。可以为迭代器类型重载iter_move:
namespace std { template<> Record&& iter_move(const Iterator& it) noexcept { return std::move(const_cast<Iterator&>(it).record); } }
此时std::move_iterator调用iter_move时,能直接从const迭代器中移动内部记录,同时仍可保留非const的operator*()供直接使用。这里的const_cast是安全的:输入迭代器本身是单次遍历语义,移动后原对象状态允许未指定,而迭代器的const限定仅为语法层面,内部记录在迭代过程中本就需要被替换为下一个记录。
同时,为满足std::indirectly_readable要求,可为const迭代器添加operator*()返回const引用:
const Record& operator*() const { return this->record; } Record& operator*() { return this->record; }
这样std::input_iterator的概念检查可通过,std::move(*it)依然能正常移动(非const迭代器解引用返回非const引用,std::move生成右值引用),std::move_iterator也会通过自定义的iter_move正确执行移动。
2. 将迭代器的reference类型设为右值引用
调整迭代器的关联类型,让reference为Record&&而非Record&,解引用直接返回右值引用:
struct Iterator { using value_type = Record; using reference = Record&&; using pointer = Record*; using difference_type = std::ptrdiff_t; using iterator_category = std::input_iterator_tag; reference operator*() { return std::move(record); } pointer operator->() { return &record; } Iterator& operator++() { // 解析下一个记录到record中 return *this; } Iterator operator++(int) { auto tmp = *this; ++*this; return tmp; } bool operator==(const Iterator& other) const { // 实现迭代器相等判断逻辑 return /* 你的判断条件 */; } private: Record record; };
C++20中std::indirectly_readable允许reference为右值引用,因此std::input_iterator概念检查可通过。此时直接使用Record r = *it;就会触发移动构造,无需显式std::move,std::move_iterator也能正常工作——它的iter_move会直接获取该右值引用。
这种设计的缺点是无法直接对解引用结果做左值操作(比如修改记录),但如果迭代器是纯输入语义(只能读取一次,移动后失效),反而符合预期。
3. 使用代理对象作为解引用结果
定义一个代理类,被移动时转移迭代器内部记录,同时支持const场景下的读取:
class RecordProxy { public: explicit RecordProxy(Record& rec) : rec_(rec) {} operator Record&&() && { return std::move(rec_); } operator const Record&() const& { return rec_; } private: Record& rec_; }; struct Iterator { using value_type = Record; using reference = RecordProxy; using pointer = const Record*; using difference_type = std::ptrdiff_t; using iterator_category = std::input_iterator_tag; reference operator*() const { return RecordProxy{const_cast<Record&>(record)}; } pointer operator->() const { return &record; } Iterator& operator++() { // 解析下一个记录到record中 return *this; } Iterator operator++(int) { auto tmp = *this; ++*this; return tmp; } bool operator==(const Iterator& other) const { // 实现迭代器相等判断逻辑 return /* 你的判断条件 */; } private: Record record; };
const迭代器解引用返回代理对象,执行std::move(*it)时,会调用代理的右值转换运算符,将内部record移动出来;普通读取(如const Record& r = *it;)则会获取const引用。这种方式既满足std::indirectly_readable对const迭代器的要求,又保留了移动语义,同时std::move_iterator也能通过代理对象正确触发移动。
内容的提问来源于stack exchange,提问作者battlmonstr

