C++输入迭代器不可写入的实现验证与技术问询
输入迭代器实现相关问题
我实现了如下输入迭代器:
template <typename T> struct input_iter: base_it<T> { private: typename base_it<T>::pointer _ptr; public: constexpr input_iter<T>() = default; constexpr explicit input_iter<T>(typename base_it<T>::pointer ptr = nullptr) : _ptr { ptr } {} constexpr ~input_iter<T>() = default; constexpr input_iter<T>(const input_iter<T>& other) = default; constexpr input_iter<T>(input_iter<T>&& other) noexcept = default; [[nodiscard]] constexpr auto operator=(typename base_it<T>::pointer ptr) -> input_iter<T>& { _ptr = ptr; return *this; } constexpr auto operator=(const input_iter<T>&) -> input_iter<T>& = default; constexpr auto operator=(input_iter<T>&&) noexcept -> input_iter<T>& = default; [[nodiscard]] constexpr auto operator*() const noexcept -> const typename base_it<T>::reference { return *_ptr; } [[nodiscard]] constexpr auto operator->() const noexcept -> const typename base_it<T>::pointer { return _ptr; } constexpr auto operator++() noexcept -> input_iter& { ++this->_ptr; return *this; } constexpr void operator++(int) noexcept { ++(*this); } [[nodiscard]] constexpr friend auto operator==(const input_iter& lhs, const input_iter& rhs) noexcept -> bool { return lhs._ptr == rhs._ptr; } [[nodiscard]] constexpr friend auto operator!=(const input_iter& lhs, const input_iter& rhs) noexcept -> bool { return not (lhs == rhs); } };
假设base_it是std::iterator的别名,由于输入迭代器需要满足indirectly_readable要求,且我希望限制对底层容器/范围的写入操作,因此将base_it定义为:
template <typename T> using base_it = std::iterator<std::input_iterator_tag, const T>;
(注意别名中用的是const T而非T)
此时执行*it_begin = 7;会得到预期的编译错误,但我有三个问题:
- 这种实现方式是否符合C++ idiom?
- 是否有更优的替代方案?
- 如何编写
static_assert来验证该写入表达式无法编译?尝试的代码未成功,希望得到实现方法。
问题解答
1. 这种实现是否符合C++ idiom?
不完全符合现代C++的惯用写法,核心问题在于依赖了废弃特性:
std::iterator在C17中被标记为废弃,C20正式移除。它原本用于自动推导迭代器关联类型,但现代C++更推荐显式定义这些类型,避免依赖过时工具。- 你的
operator*返回const typename base_it<T>::reference,但由于base_it的value_type是const T,其reference本身已经是const T&,这里的const属于冗余修饰,虽不影响功能但不够简洁。
不过,通过迭代器返回const引用禁止写入的核心思路,是符合C++只读迭代器的设计习惯的。
2. 更优的替代方案
推荐两种现代C++实现方式:
方案一:显式定义迭代器关联类型
直接在input_iter中声明所有必要的关联类型,彻底摆脱std::iterator:
#include <iterator> template <typename T> struct input_iter { // 显式定义迭代器关联类型 using iterator_category = std::input_iterator_tag; using value_type = const T; using difference_type = std::ptrdiff_t; using pointer = const T*; using reference = const T&; private: pointer _ptr = nullptr; public: constexpr input_iter() = default; constexpr explicit input_iter(pointer ptr) : _ptr{ptr} {} // 其余默认构造、析构、赋值函数保持原实现不变 [[nodiscard]] constexpr reference operator*() const noexcept { return *_ptr; } [[nodiscard]] constexpr pointer operator->() const noexcept { return _ptr; } // 自增、比较运算符保持原实现不变 };
这种方式清晰直观,符合C++17及后续标准要求,避免了废弃特性的使用。
方案二:复用标准库迭代器适配器(场景允许时)
如果你的迭代器只是对原生指针的只读包装,可直接使用C++20引入的std::input_iterator适配器,无需手动实现:
#include <iterator> // 示例:将const T*包装为标准输入迭代器 const int arr[] = {1,2,3}; auto it = std::input_iterator<const int*>(std::begin(arr));
标准库适配器已满足所有迭代器概念要求,且经过充分测试,能减少手动实现的潜在bug。
3. 编写static_assert验证写入操作无法编译
可以利用SFINAE特性编写检测模板,断言写入表达式非法:
#include <type_traits> // 辅助模板:检测迭代器解引用结果是否可被指定类型赋值 template <typename Iter, typename T> constexpr bool is_assignable_to_deref = std::is_assignable_v<decltype(*std::declval<Iter>()), T>; // 针对input_iter进行验证 static_assert(!is_assignable_to_deref<input_iter<int>, int>, "input_iter 不允许通过解引用写入数据");
原理说明:
std::declval<Iter>()模拟创建一个input_iter<int>对象*std::declval<Iter>()获取解引用结果的类型std::is_assignable_v判断该类型是否能被int赋值static_assert断言结果为false,若写入操作变为合法,编译会直接触发断言错误
内容的提问来源于stack exchange,提问作者Alex Vergara
相关产品推荐
相关产品推荐

