【发布时间】: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,其中a 是X 类型和*r,其中r 是X& 类型可能会有所不同,但也许我错过了有点微妙。
也许这里有一点回旋余地,但不多;在某些时候,您需要准备好创建一个T,如果您实际上在容器中没有一个T,以便您可以提供对它的引用。
但最重要的是
第 24.2.5 节,第。 6(
a和b是X类型的值): 如果a和b都是可解引用的,那么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) 聚合类型容器的视图,它只有一些值。 (很可能,底层容器和视图都是元组,其中视图元组的类型列表是底层容器类型的子集,可能以不同的顺序)。
我相信其他人可以轻松添加到此列表中;这些只是我在过去几个月中以某种方式破解的那些。
【问题讨论】: