为什么std :: iterator_traits中的几乎所有类型别名都没有默认值? [英] Why don't almost all the type aliases in std::iterator_traits have defaults?

查看:61
本文介绍了为什么std :: iterator_traits中的几乎所有类型别名都没有默认值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在没有boost.iterator之类的库的帮助下创建新的C ++ 20之前的迭代器时,有必要指定类型别名 difference_type value_type 指针引用 iterator_category .根据

When creating a new iterator pre-C++20 without the help of libraries like boost.iterator, it's necessary to specify the type aliases difference_type, value_type, pointer, reference and iterator_category. According to cppreference, with C++20, it's only necessary to specify difference_type and value_type, which I think is great! But why are there defaults for exactly these 3 aliases?

对此我有两件事不了解(在我看来,这是一件疏忽大意的事情):

There are 2 things I don't understand about this (and one thing that seems to me like an oversight):

  1. 为什么没有 value_type difference_type 的默认值?使用 std :: remove_reference_t< reference> 之类的东西作为 value_type 的默认设置是否有意义?作为随机访问迭代器的 difference_type 的默认设置,使用带有两个迭代器的-运算符的结果类型可以说是合理的.
  2. C ++ 20添加 contiguous_iterator_tag .就像使用 input_iterator_tag forward_iterator_tag 一样,我看不到编译器如何正确区分连续迭代器和随机访问迭代器,我猜这就是为什么它显然从不选择 contiguous_iterator_tag 的原因.这是故意的吗?将输入迭代器错误分类为正向迭代器似乎也有些危险,那么为什么不要求程序员自己指定此别名呢?
  3. 关于一点无关的注释,即使程序员明确声明了另一个类别,并为生成一个值,我也不确定静默地为 iterator_category 生成一个值是否是一个好主意.与 concept 完全不同的> iterator_category 似乎也很奇怪.考虑这个不现实的例子:
  1. Why are there no default values for value_type and difference_type? Wouldn't it make sense to use something like std::remove_reference_t<reference> as a default for value_type? As a default for difference_type for random access iterators, it could arguably make sense to use the result type of the - operator taking two iterators.
  2. C++20 adds the contiguous_iterator_tag. Just like with input_iterator_tag versus forward_iterator_tag, I don't see how it should be possible for the compiler to correctly distinguish between a contiguous iterator and a random access iterator, which I guess is why it apparently never selects contiguous_iterator_tag. Is this intended? It also seems somewhat dangerous to misclassify an input iterator as a forward iterator, so why don't require the programmers to specify this alias themselves?
  3. On a somewhat unrelated note, I'm not sure if it's a good idea to silently generate a value for iterator_category even if the programmer has explicitly stated another category, and generating a value for iterator_category that's completely different from the concept seems strange as well. Consider this unrealistic example:

#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;
}

这将打印"input iterator concept"(输入迭代器概念)和输出迭代器标签"因为它缺少比较运算符(该概念不是必需的).如果我添加了注释行,则现在将打印"input iterator concept"(输入迭代器概念)和随机访问迭代器标记",即使它显然不是随机访问迭代器也是如此.公平地说,像这样写错误的 iterator_category (即 random_access_iterator_tag )是一个非常愚蠢的示例,但是我仍然认为检查该概念是否满意是有意义的,特别是在后退"情况下, output_iterator_tag :忘记编写 == 运算符,不应将输入迭代器变成不可用的输出迭代器.检查是否满足相应的概念是否可能并且有意义?

This prints "input iterator concept" and "output iterator tag" because it's missing the comparison operator (which isn't required for the concept). If I add the commented line, this now prints "input iterator concept" and "random access iterator tag", even though it clearly isn't a random access iterator. To be fair, writing the wrong iterator_category (i.e. random_access_iterator_tag) like this is a pretty stupid example, but I still think it would make sense to check if the concept is satisfied, especially in the case of the "fall-back" output_iterator_tag: Forgetting to write the == operator shouldn't turn an input iterator into an unusable output iterator. Would it be possible and make sense to check that the corresponding concepts are satisfied?

修改我的问题中的几点似乎不清楚,或者我做出了一些不正确但未阐明的假设.我将尝试更明确地说明它们,并重新阐述我目前的理解(在阅读了Nicol Bolas的回答之后):

Edit A few points in my question seem to be unclear, or maybe I've made some incorrect but unstated assumptions. I'll try to be more explicit about them and rephrase my current understanding (after reading the answer by Nicol Bolas):

  1. 关于第3点:据我了解,类型 T 可能有一些 std :: iterator_traits< T> :: iterator_category 别名不能为相应的C ++ 20概念或C ++ 17命名需求建模.这是有意的.因此,让我们忘记这一点,因为它可能更适合一个单独的问题.
  2. 我认为如果我没有明确写下来 std :: type_traits 别名(例如,当我只写 value_type 时便定义了 reference >)对于某些迭代器可能是不正确的,并且是明智的默认值.这样对吗?如果这是不正确的,那么我的问题就可以得到答案.
  3. 如果未为输入迭代器 T 定义 T :: reference ,则将 std :: iterator_traits :: reference 定义为 decltype(* std :: declval< T&>()).这是正确的吗?
  4. 如果可以基于 operator * 定义 reference ,那么也可以基于定义 value_type 是没有道理的* ?假设5.是正确的,我能想到的唯一输入迭代器将是 std :: vector< bool> 的迭代器,并且由于这种差异,有一些建议弃用它.因此,大多数输入迭代器都可以使用此定义,而那些不能简单地指定 value_type 的输入迭代器.我想念什么吗?
  5. 关于要点2:通常,无法确定迭代器属于哪一类.使用例如一个输入迭代器(如果它是一个更通用的正向迭代器)将是一个错误.可能是程序员没有指定 iterator_category 的有效迭代器的 type_traits :: iterator_category 不正确.这不会影响概念或命名需求(它们考虑了语义),但是实际上,stl函数可能无法在此迭代器上正常工作,而不会产生(运行时或编译时)错误.因此,我认为要求程序员明确声明类别是一个好主意.这种推理是否有问题或错过了什么?
  1. Regarding Point 3: As I understand it, it's possible that a type T may have some std::iterator_traits<T>::iterator_category alias even if it doesn't model the corresponding C++20 concept or the C++17 named requirement. This is intended. So, let's forget about this, because it's probably a better fit for a separate question.
  2. I think that the std::type_traits aliases defined if I don't explicitly write them down (e.g. reference when I only write value_type) can be incorrect for some iterators and are meant as sensible default values. Is this correct? If this is incorrect, my question is pretty much answered.
  3. If T::reference isn't defined for an input iterator T, then std::iterator_traits::reference is defined as decltype(*std::declval<T&>()). Is this correct?
  4. If reference can be defined based on operator*, wouldn't it make sense to then also define value_type based on *? Assuming that 5. is correct, the only input iterator I can think of where this would go wrong is the iterator from std::vector<bool>, and there were several proposals to deprecate it because of this difference. So most input iterators would work with this definition, and those that didn't could simply specify value_type. Am I missing something?
  5. Regarding Point 2: It's not in general decidable into what category an iterator falls. Using e.g. an input iterator as if it were a more general forward iterator would be a bug. It can happen that the type_traits::iterator_category of a valid iterator where the programmer did not specify the iterator_category is incorrect. This doesn't affect the concept or named requirement (they take semantics into account), but in practical terms, it's possible that stl functions don't work correctly with this iterator, without generating a (run- or compile-time) error. Therefore, I think it would be a good idea to require the programmer to explicitly state the category. Is there a problem in this reasoning or did miss something?

我希望我不要过分地or腐或坚持我的个人观点,但是我真的不知道以上几点是否存在错误,在哪里,我想这不是只是让我感到困惑.

I hope I don't come across as overly pedantic or as insisting on my personal opinion, but I genuinely don't know if and where there's an error in the points above, and I'm guessing that this isn't just confusing to me.

推荐答案

这一点很重要,因为这里要合并某些不同的东西.

It's important to understand something at this point, as certain different things are being conflated here.

在C ++ 20中,迭代器有两个分类:旧的C ++ 17命名需求,以及

In C++20, there are two classifications of iterators: the old C++17 named requirements, and the new C++20 concept-based iterators. Most of the old requirements map to the latter, but the concept requirements allow for more things to be considered iterators than what the C++17 requirements allowed.

std :: iterator_traits 都用于它们 ,因为它们确实使用了许多相同的运动部件.这样做的目的是,应该有可能编写一个满足C ++ 17命名要求和类似C ++ 20概念的迭代器.也就是说,您可以编写出满足Cpp17RandomAccessIterator std :: random_access_iterator 的类型,而不会带来太多麻烦.

std::iterator_traits however is used for both of them, since they do use many of the same moving parts. The point of this is that it should be possible to write an iterator that fulfills both the C++17 named requirement and the similar C++20 concept. That is, you can write a type that satisfies Cpp17RandomAccessIterator and std::random_access_iterator without too much trouble.

之所以提出这一点,是因为正在讨论的许多事情对一套要求的影响远大于另一套.

I bring this up because many of the things under discussion will matter a lot more to one set of requirements than the other.

为什么没有 value_type difference_type 的默认值?使用 std :: remove_reference_t< reference> 之类的默认值作为 value_type 的默认设置是否合理?

Why are there no default values for value_type and difference_type? Wouldn't it make sense to use something like std::remove_reference_t<reference> as a default for value_type?

很显然,这将需要您指定 reference .因此,您仍然必须指定两件事.无论如何, value_type 是迭代器的创建者正在考虑的一种.而且,如果他们正在考虑,可能是因为 reference 需要不是 value_type& 的东西,所以他们仍然需要同时指定两者.

Obviously, that would require you to specify reference. So you'd still have to specify two things. value_type is the one that the creator of the iterator is thinking in terms of anyway. And if they're thinking of it, it's probably because reference needs to be something other than a value_type&, so they'll need to specify both anyway.

C ++ 20添加了contiguous_iterator_tag.就像使用input_iterator_tag与forward_iterator_tag一样,我看不到编译器应该如何正确区分连续迭代器和随机访问迭代器,我猜这就是为什么它显然从不选择contiguous_iterator_tag的原因.这是故意的吗?

C++20 adds the contiguous_iterator_tag. Just like with input_iterator_tag versus forward_iterator_tag, I don't see how it should be possible for the compiler to correctly distinguish between a contiguous iterator and a random access iterator, which I guess is why it apparently never selects contiguous_iterator_tag. Is this intended?

在C ++ 17中,没有诸如连续迭代器"之类的东西.与RandomAccessIterator的含义不同.标准中有一整节说明了RandomAccessIterator的要求,而连续的迭代器"则为连续的迭代器".得到一个段落语句,没有任何其他信息,实际使用的情况也很少.

In C++17, there was no such thing as a "contiguous iterator". Not in the same sense as a RandomAccessIterator. There's a whole section in the standard that explains the requirements of a RandomAccessIterator, while "contiguous iterator" gets a one paragraph statement with no additional information about it and very few actual uses.

当然,连续迭代器"没有迭代器标签.这样做是为了避免添加另一个迭代器标签,并可能使很多代码可能无法正常工作,因为一个连续的迭代器将其自身声明为随机访问.

And of course, "contiguous iterator" gets no iterator tag. This was done deliberately to avoid adding another iterator tag and possibly making a lot of code that could work non-functional because a contiguous iterator instead advertised itself as random access.

C ++ 20改变了一切.它添加了 std :: contiguous_iterator_tag ,但这是因为

C++20 changes things. It adds a std::contiguous_iterator_tag, but it does so because std::contiguous_iterator now has syntactical differences from std::random_access_iterator. Namely, a contiguous iterator must permit conversion into a pointer to its value_type via std::to_pointer. This allows you to turn an iterator pair into a pointer pair without having to dereference a potentially non-dereference-able iterator (such as a past-the-end iterator).

还请注意,迭代器类别的自动分配基于满足C ++ 17命名要求(C ++ 20概念的 not )的基础.由于不存在连续迭代器",因此,命名需求(即使存在,也无法从语法上确定),因此无法自动分配该需求.

Note also that automatic assignment of iterator categories is based on satisfying the C++17 named requirements, not of the C++20 concepts. Since there is no "contiguous iterator" named requirement (and even if there was, it wouldn't be syntactically determinable), there can be no auto assignment of it.

自动分配仅适用于C ++ 17要求的原因是因为C ++ 20概念是根据 std :: iterator_traits 定义的.因此,如果不创建循环定义,它将无法使用这些概念.

The reason automatic assignment only works for the C++17 requirements is because the C++20 concepts are defined in terms of std::iterator_traits. So it cannot use the concepts without creating a circular definition.

在一个不相关的注释上,我不确定即使程序员明确声明了另一个类别,也可以静默生成iterator_category的值是否是一个好主意

On a somewhat unrelated note, I'm not sure if it's a good idea to silently generate a value for iterator_category even if the programmer has explicitly stated another category

这不是标准的作用.如果您指定一个(除了下面提到的一个奇怪的怪癖之外),它只会提供一个.

That's not what the standard does. It only provides one if you don't specify one (outside of one odd quirk mentioned below).

这将打印"input iterator concept"(输入迭代器概念)和输出迭代器标签"因为它缺少比较运算符(该概念不是必需的).

This prints "input iterator concept" and "output iterator tag" because it's missing the comparison operator (which isn't required for the concept).

这是对 iterator_category 的新定义的一个奇怪的怪癖,但是这个怪癖最终确实正确地代表了您类型的不一致性.

This is an odd quirk of the new definition of iterator_category, but the quirk does ultimately correctly represent the incoherence of your type.

主模板 iterator_category 具有 3可能的版本,具体取决于您定义迭代器类型的方式.如果您的迭代器提供除 pointer 之外的所有成员类型别名,则仅使用它们.如果仅提供其中一些,则对仅博览会版本的Cpp17InputIterator进行 concept 检查.如果您的类型适合该类型,则使用您类型的 iterator_category (如果您不提供该类型,那么它将计算一个).

The primary template iterator_category has 3 possible versions, depending on how you defined your iterator type. If your iterator provides all of the member type alises except pointer, then it just uses them. If it only provides some of them, then it does a concept check against an exposition-only version of Cpp17InputIterator. If your type fits that, then it uses your type's iterator_category (and if you don't provide one, then it computes one).

但是,如果您的迭代器不是输入迭代器,那么它将根据基本Cpp17Iterator进行检查.如果合适的话, iterator_traits :: iterator_category 固定固定为 output_iterator_tag .当然,这是一个奇怪的选择.

However, if your iterator isn't an input iterator, then it checks against the basic Cpp17Iterator. If that fits, then iterator_traits::iterator_category is fixed to be output_iterator_tag. That is certainly a strange choice.

如果我添加了注释行,现在将打印"input iterator concept"(输入迭代器概念)和随机访问迭代器标记",即使它显然不是随机访问迭代器.

If I add the commented line, this now prints "input iterator concept" and "random access iterator tag", even though it clearly isn't a random access iterator.

但是您这是一个随机访问迭代器.该系统不是要应该覆盖您所说的内容;如果您的类型与input-iterator不匹配,但仍然是某种迭代器,那将是一个奇怪的事情.

But you said it was a random access iterator. The system isn't supposed to override what you said; that was just a quirk of what happens if your type doesn't match input-iterator but still happens to be some kind of iterator.

无论如何,如果您说谎,您会撒谎.垃圾进,垃圾出.

In any case, if you lie, you lied. Garbage in, garbage out.

我仍然认为检查该概念是否令人满意是有道理的,特别是在后退"的情况下.output_iterator_tag:忘记编写==运算符不应将输入迭代器变成不可用的输出迭代器.

I still think it would make sense to check if the concept is satisfied, especially in the case of the "fall-back" output_iterator_tag: Forgetting to write the == operator shouldn't turn an input iterator into an unusable output iterator.

但是...那是是什么.对于输入迭代器,平等测试不是可选的.如果您无法对其进行相等性测试,则它不是输入迭代器.确实,如果系统按照您的建议进行操作,那正是您将获得的标签:输出迭代器.

But... that's what it is. Equality testing isn't optional for input iterators. If you can't test it for equality, then it not an input iterator. Indeed, if the system did as you suggested, that's exactly the tag you would get: an output iterator.

那你有什么问题?如果您不小心将类型设为输入迭代器,是要系统将其正确分类为符合其行为的类型,还是要继续转发错误的类别?

So what's your problem? If you accidentally failed to make your type an input iterator, do you want the system to correctly categorize it as what it is in accord with its behavior or do you want it to forward your mistaken category onward?

这篇关于为什么std :: iterator_traits中的几乎所有类型别名都没有默认值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆