c++ - 返回对 InputIterator 内部状态的引用是否合法?

标签 c++ stl c++17 language-lawyer

让我们有一个输入迭代器 it符合 Cpp17InputIterator .我们能否保证引用值*it我们做之后还是一样it++ ?例如,

const auto &old_ref = *it;
auto old_val = ref;
++it; // old_ref might be affected by this

assert(old_ref == old_val); // Is this guaranteed for Cpp17InputIterator?
tableit的旧拷贝不需要是可解引用的。但这是否意味着从 it 获得的旧引用也不能取消引用?可以 *it返回对迭代器内部状态的引用?

最佳答案

如果迭代器无效,则永远不应假设引用仍然有效。某些迭代器实现可能是这种情况,但这样做违反了迭代器概念,并且不会对所有迭代器通用。
迭代器在内部实现为 std::optional<T> 是完全合法的。可能返回 T 的引用并重建 T每次迭代之间。在 Inputiterator 上尤其如此s,不需要多 channel 支持(例如生成器范围)。
例如,执行以下操作的迭代器是完全合法的:

template <typename T>
auto some_special_iterator<T>::operator*() -> T&
{
  return *m_value; // returns a reference to the currently stored T
}

template <typename T>
auto some_special_iterator<T>::operator++() -> some_special_iterator&
{
  m_value.clear(); // Destroys the object which someone may be holding a reference to
  m_value.emplace( ... ); // Invalidates any existing references by constructing a new object

  return (*this);
}
使用指向已销毁对象的指针或引用是 未定义的行为 ,即使该指针或引用指向新对象的相同存储。对新构造的对象唯一合法的指针或引用是 new 返回的那些。 (例如展示位置 new )或 std::launder .

因为这被标记为 :直接来自标准的引号中没有太多可以将其定义为非法的,因为 InputIterator 没有任何保证。允许保留的引用保持有效的概念。
所以为了证明这是未定义的行为,我们需要向后工作。首先:
来自 defns.undefined

Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data


因此,我们需要检查上面的迭代器示例是否正确遵守 InputIterator的概念定义,其中重要的部分是operator++行为:
来自 input.iterators 中的表:

Requires: r is dereferenceable.
Postconditions: r is dereferenceable or r is past-the-end; any copies of the previous value of r are no longer required either to be dereferenceable or to be in the domain of ==.


(强调我的)
在上述要求中,条件是迭代器r在上面的示例中被支持之前是可取消引用的,就像后置条件也将被支持一样。
有趣的是我加粗的部分:“r 先前值的任何拷贝不再需要可取消引用或位于 == 的域中”
这意味着迭代器本身的任何现有拷贝可能不再是可取消引用的,也可能无法在与另一个迭代器相同的范围内正确执行比较。这是正式意思是的部分迭代器的所有拷贝可能已失效 (注意:“可能”,因为迭代器不需要无效——但应该假设无效)。
C++ 标准的文档没有明确声明任何持有的引用仍然有效,因为这不是进程的定义行为;但是,如果在调用 operator++ 之后迭代器本身不再被视为“可取消引用” ,那么还应该假设它的引用不再有效。由于措辞没有说明在此之后保留引用保证保持有效,因此必须假定它是未定义的行为,因为上面来自 defns.undefined 的段落。 .
上面说明的示例是一个符合迭代器的实现,其中这种期望会导致实际的未定义行为,这符合这种解释。

另一方面,小心使用 const auto&带有输入迭代器。operator*()在输入迭代器上只需要返回一个 It::reference可转换为 T 的类型;它实际上根本不需要成为引用。请注意,这在前向迭代器中得到了加强,而且“reference 必须是对 T 的引用”,但输入迭代器并非如此。
使用 const auto&这里实际上可能会导致您无意中const -lifetime-extend 一个临时代理对象,而不是持有一个真正的引用。

关于c++ - 返回对 InputIterator 内部状态的引用是否合法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64755149/

相关文章:

c++ - 使用模板化函数参数的隐式类型转换

c++ - 在 constexpr-if 条件下比较 constexpr 函数参数导致错误

c++ - C++ 17:从模板中的Callable推导签名

c++ - 如何在这种情况下安全地使用 new 和 delete

c++ - 使用C++标准库以对数时间进行堆化

c++ - 在 std::vector 中表示原始字节的最佳方式?

c++ - 序列容器的重新分级删除功能

c++ - 如何在 vscode 中禁用 C++17 可用的结构化绑定(bind)的警告?

c++ - 为什么模板别名特化取决于引用它的上下文?

c++ - 匹配可迭代类型(具有 begin()/end() 的数组和类)