【发布时间】:2021-05-28 09:01:13
【问题描述】:
在不借助 boost.iterator 等库的情况下创建 C++20 之前的新迭代器时,需要指定类型别名 difference_type、value_type、pointer、reference 和 iterator_category .
According 到 cppreference,使用 C++20,只需要指定 difference_type 和 value_type,我认为这很棒!
但是为什么这 3 个别名有默认值呢?
对此我有两件事不明白(还有一件在我看来像是疏忽):
- 为什么
value_type和difference_type没有默认值?使用std::remove_reference_t<reference>之类的东西作为value_type的默认值不是很有意义吗? 作为随机访问迭代器的difference_type的默认值,使用带有两个迭代器的-运算符的结果类型可以说是有意义的。 - C++20 添加了
contiguous_iterator_tag。就像input_iterator_tag与forward_iterator_tag一样,我看不出编译器应该如何正确区分连续迭代器和随机访问迭代器,我猜这就是它显然从不选择contiguous_iterator_tag的原因。这是故意的吗?将输入迭代器误分类为前向迭代器似乎也有些危险,那么为什么不要求程序员自己指定这个别名呢? - 在一个有点不相关的注释中,我不确定是否为
iterator_category静默生成一个值是否是个好主意,即使程序员已明确声明另一个类别,并为iterator_category生成一个与concept似乎也很奇怪。考虑这个不切实际的例子:
#include <iostream>
#include <iterator>
// With the == operator, this is an input iterator, but nothing else.
struct WeirdIterator {
// Not an output iterator because you can't assign to a const reference
const int& operator*() const { return 42; }
WeirdIterator& operator++() { return *this; } // unimportant
WeirdIterator operator++(int) { return *this; } // unimportant
// bool operator==(const WeirdIterator&) const = default;
using iterator_category = std::random_access_iterator_tag;
using value_type = int;
using difference_type = int;
};
void iteratorConcept(std::input_iterator auto) {
std::cout << "input iterator concept" << std::endl;
}
void iteratorConcept(std::random_access_iterator auto) {
std::cout << "random access iterator concept" << std::endl;
}
void iteratorTag(std::output_iterator_tag) {
std::cout << "output iterator tag" << std::endl;
}
void iteratorTag(std::input_iterator_tag) {
std::cout << "input iterator tag" << std::endl;
}
void iteratorTag(std::random_access_iterator_tag) {
std::cout << "random access iterator tag" << std::endl;
}
int main() {
WeirdIterator iter;
iteratorConcept(iter);
iteratorTag(std::iterator_traits<WeirdIterator>::iterator_category{});
return 0;
}
这会打印“输入迭代器概念”和“输出迭代器标记”,因为它缺少比较运算符(概念不需要)。
如果我添加注释行,现在将打印“输入迭代器概念”和“随机访问迭代器标记”,即使它显然不是随机访问迭代器。公平地说,像这样写错iterator_category(即random_access_iterator_tag)是一个非常愚蠢的例子,但我仍然认为检查这个概念是否满足是有意义的,尤其是在“回退”的情况下" output_iterator_tag:忘记编写 == 运算符不应将输入迭代器变成不可用的输出迭代器。检查相应的概念是否得到满足是否可能并且有意义?
编辑 我的问题中有几点似乎不清楚,或者我做了一些不正确但未说明的假设。我将尝试更明确地说明它们并重新表述我目前的理解(在阅读 Nicol Bolas 的回答后):
- 关于第 3 点:据我了解,
T类型可能有一些std::iterator_traits<T>::iterator_category别名,即使它没有模拟相应的 C++20 概念或 C++17 命名要求。这是有意的。所以,让我们忘记这一点,因为它可能更适合单独的问题。 - 我认为,如果我没有明确写下
std::type_traits别名(例如,当我只写reference时,我只写value_type)定义的别名对于某些迭代器来说可能是不正确的,并且是合理的默认值。它是否正确?如果这是不正确的,我的问题已经得到了很好的回答。 - 如果没有为输入迭代器
T定义T::reference,则std::iterator_traits::reference定义为decltype(*std::declval<T&>())。这是正确的吗? - 如果可以基于
operator*定义reference,那么基于*也定义value_type是否有意义?假设 5. 是正确的, 我能想到的唯一输入迭代器是来自std::vector<bool>的迭代器,由于这种差异,有几个建议弃用它。所以大多数输入迭代器都可以使用这个定义,而那些不能简单地指定value_type。我错过了什么吗? - 关于第 2 点:通常无法确定迭代器属于哪个类别。
使用例如一个输入迭代器就好像它是一个更通用的前向迭代器将是一个错误。程序员未指定
iterator_category的有效迭代器的type_traits::iterator_category可能不正确。这不会影响概念或命名要求(它们考虑了语义),但实际上,stl 函数可能无法与此迭代器一起正常工作,而不会产生(运行时或编译时)错误。因此,我认为要求程序员明确说明类别是个好主意。这种推理有问题还是遗漏了什么?
我希望我不会显得过于迂腐或坚持我的个人观点,但我真的不知道上述几点是否以及哪里有错误,我猜这不是只是让我感到困惑。
【问题讨论】:
-
给定一个随机的迭代器
T,它的value_type是如何默认定义的? -
@Sam Varshavchik 也许我在这里遗漏了一些明显的东西,但
std::remove_reference_t<decltype(*std::declval<T>())>不是一个好的候选人吗? -
value_type的std::ostreambuf_iterator是void。然而,它的operator*()并没有返回空值。 -
我应该指定我在谈论输入迭代器(即所有不仅是输出迭代器的迭代器),我的默认值为
value_type:我认为reference的默认值为@ 987654374@ 用于仅作为输出迭代器但基于operator*用于所有其他迭代器的迭代器,所以对我来说,对value_type执行相同操作似乎有些合理。
标签: c++ iterator language-lawyer c++20