【问题标题】:Can a container's iterator yield something other than an lvalue?容器的迭代器可以产生左值以外的东西吗?
【发布时间】:2012-12-13 23:50:06
【问题描述】:

我或多或少得出结论,不可能编写一个其 value_type 不直接存储在容器中的符合容器。我认为这很不幸,因为我经常希望我有容器,其中值类型要么是部分计算的,要么是由不连续的部分组装而成的(下面的示例,但与问题没有直接关系)。我知道如何编写使用代理对象的迭代器,尽管这很烦人。但我现在想知道 C++ 标准中是否真的有这些野兽的空间。这里可能有太多的废话; tl;dr 版本很简单:第 24.2.5 节的第 1 和第 6 段的真正含义是什么,违反表面含义会在多大程度上破坏标准算法?或者,换句话说,如何解释它们以允许代理迭代器?

正如 Pete Becker 所指出的,实际上没有什么可以强迫我的容器符合标准库容器的要求。但是为了使用具有许多标准算法的容器,它必须具有至少具有forward_iterator_tag 的一致迭代器,或者它必须对此撒谎,但仍设法满足特定算法强加的操作(如果不是正式的)要求在它的迭代器上。

这是我的推理:

表 96(§ 23.2.1),容器要求,包括:

Expression     Return type         Assertion/note
------------   -------------       ---------------------
X::iterator    iterator type       any iterator category
               whose value         that meets the
               type is T           forward iterator
                                   requirements.
                                   Convertible to
                                   const_iterator.

 a.begin()     iterator;
               const_iterator for
               constant a.

现在,前向迭代器:

第 24.2.5 节,第。 1:

一个类或指针类型X满足前向迭代器的要求,如果……

——如果X是一个可变迭代器,reference是对T的引用;如果X 是一个常量迭代器,reference 是对const T 的引用

确实没有直接要求*a 返回reference(其中a 的类型为X)。要求是:

如果 a 可取消引用,则来自表 107(输入迭代器)*a 必须“可转换为 T”。

来自表 106(迭代器)*r 必须具有 reference 类型,其中 r 的类型为 X& 并且是可取消引用的。

但是,表 106 还指定 ++r 返回 X&,因此 *++r 必须是 reference。此外,(根据表 107),*a++ 必须是 reference,尽管(表 109)a[n] 只需要“可转换为引用”。我不得不说,我看不出*a,其中aX 类型和*r,其中rX& 类型可能会有所不同,但也许我错过了有点微妙。

也许这里有一点回旋余地,但不多;在某些时候,您需要准备好创建一个T,如果您实际上在容器中没有一个T,以便您可以提供对它的引用。

但最重要的是

第 24.2.5 节,第。 6(abX 类型的值): 如果ab 都是可解引用的,那么a == b 当且仅当*a*b 绑定到同一个对象。

我找不到bound to 的正式定义,但在我看来,制作不可寻址对象的迭代器的常用策略是创建一个代理对象,通常存储在迭代器本身内。在这种情况下,除了禁止两个不同的迭代器对象之间进行相等比较之外,即使它们在逻辑上指示相同的位置,也需要非常宽泛地理解“绑定到”的含义以任何方式解释 24.2.5/6在容器中。

另一方面,我注意到应该知道的 Dietmar Kühl 在回复this question 时说:

C++ 2011 的要求放宽了,迭代器不一定需要产生左值

那么,迭代器可以返回代理,还是不能?如果可以,这种代理的性质是什么?我认为这样的迭代器不符合标准的推理在哪里失败了?


正如所承诺的,一些容器的有效 value_types 不会连续存储在容器中:

1) 一个紧凑的关联容器,其键和值类型可以更有效地存储在两个单独的向量中。 (将键保存在向量中还可以提高缓存友好性,并减少分配开销。)

2) 伪装成map<integer_type, T>vector<T>,简化了与其他map<X, T> 类型的互操作性。

3) 通过压缩其他几个容器形成的逻辑容器,生成一个逻辑 value_type,它是对压缩容器的值类型的引用的tuple。 (在某些应用程序中,一个或多个压缩容器可能会被完全计算,或者作为其他值的函数,或者作为序列号。)

4) 聚合类型容器的视图,它只有一些值。 (很可能,底层容器和视图都是元组,其中视图元组的类型列表是底层容器类型的子集,可能以不同的顺序)。

我相信其他人可以轻松添加到此列表中;这些只是我在过去几个月中以某种方式破解的那些。

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    不要通过考虑“符合标准的容器”来限制自己:标准中没有任何东西取决于拥有一个。将容器要求视为描述标准中定义的容器要求的简写方式。而已。只要您的容器生成的迭代器是有效的,您就可以使用所有相应的算法,并且大概可以使用您自己编写的算法。

    【讨论】:

    • 我编辑了这个问题,以便更清楚地说出来;我记得当我开始写它时,我想到了那个反对意见。我的问题是我看不到如何编写符合要求的前向迭代器;容器问题或多或少是一个副作用,这就是为什么iterator 这个词出现在问题中。
    【解决方案2】:

    最好的模型是std::vector< bool >。它尽可能地接近合规性,但它的迭代器确实产生了代理。

    标准甚至指定std::vector<bool>::reference 是一个类。然而容器需求表指定X::reference 产生“T 的左值”。因此,它是否严格不合规。

    但迭代器并未绑定到T。迭代器value_type 必须是T 并咨询输入迭代器要求,reference 必须转换为value_type

    正如 Pete Becker 所提到的,需求表相当宽泛,各个算法指定了他们需要什么。只有需要 reference 才能真正成为参考的算法才会中断,这只是在说明显而易见的事情。

    【讨论】:

    • 是的,我看过std::vector<bool>。但如果这是最好的模型,那就令人失望了。我本来希望能够写出不会被 Herb Sutter 蹂躏的东西。 (“如果其他人写过vector,它就会被称为'nonconforming'和'nonstandard'”)
    • 好的,我模糊地编辑了问题的第一段,以使我想要得到答案的内容更清楚。如果你想看看另一个(英雄!)尝试部署代理迭代器,你可以查看justsoftwaresolutions.co.uk/articles/pair_iterators.pdf
    • @rici 有什么回旋余地?迭代器要么返回代理,要么返回真实引用。我的观点是vector<bool> 领先于时代,正是因为它超越了获取存储和返回指针。
    • 所以我认为您的观点是“绑定到同一个对象”应该以某种哲学甚至柏拉图式的意义来解释,而不是意味着它们是对相同对象的引用 对象?
    • @rici 对不起,我应该承认没有完全阅读这个问题。您已经涵盖了大部分分析。是的,我认为如果代理容器要成为真正的容器并提供真正的前向迭代器(或更好),该标准还有很长的路要走。然而,这更多的是文书工作而不是技术发展。除了value_type 之外,iterator::referencecontainer::reference 存在的原因是它们可以有很大的不同。但是隐式转换的作用需要明确。完全通用的算法应使用static_cast 以避免堆叠用户定义的转换。
    猜你喜欢
    • 2020-07-03
    • 2014-08-12
    • 1970-01-01
    • 2011-08-14
    • 1970-01-01
    • 2011-07-12
    • 2014-07-22
    • 2020-01-27
    • 1970-01-01
    相关资源
    最近更新 更多