我或多或少得出的结论是,不可能编写一个其 value_type 未直接存储在容器中的符合容器。我认为这是不幸的,因为我经常希望我有容器,其中值类型要么是部分计算的,要么是由不连续的部分组装而成的(下面的例子,但与问题没有直接关系)。我知道如何编写使用代理对象的迭代器,尽管这很烦人。但我现在想知道 C++ 标准中是否真的有这些野兽的空间。这里可能有太多的废话;tl;dr 版本很简单:第 24.2.5 节第 1 和第 6 段的真正含义是什么,违反表观含义会在多大程度上破坏标准算法?或者,换句话说,如何解释它们以允许代理迭代器?
正如 Pete Becker 所指出的,实际上没有什么可以强迫我的容器符合标准库容器的要求。但是为了使用具有许多标准算法的容器,它必须具有至少具有 a 的一致迭代器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
满足前向迭代器的要求,如果……— if
X
是可变迭代器,是对;reference
的引用T
ifX
是一个 const 迭代器,reference
是对const T
确实没有直接要求*a
返回reference
(其中a
是 type X
)。要求是:
如果可取消引用,则来自表 107(输入迭代器)
*a
必须“可转换为 T” 。a
来自表 106(迭代器)的
*r
类型必须具有类型且可取消引用。reference
r
X&
但是,表 106 还指定++r
返回X&
,因此*++r
必须是reference
。此外,(根据表 107)*a++
必须是reference
,尽管(表 109)a[n]
只需要“可转换为参考”。我不得不说,我看不出*a
哪里a
是类型X
,*r
哪里r
是类型X&
可能会有所不同,但也许我错过了一些微妙之处。
也许这里有一点回旋余地,但不多;在某些时候,您需要准备好创建一个T
,如果您实际上在容器中没有一个,以便您可以提供对它的引用。
但关键是
§ 24.2.5,第。6(
a
andb
是 type 的值X
):如果a
andb
都是可解引用的,那么a == b
当且仅当*a
and*b
绑定到同一个对象。
我找不到 的正式定义bound to
,但在我看来,制作不可寻址对象的迭代器的常用策略是创建一个代理对象,通常存储在迭代器本身内。在这种情况下,除了禁止两个不同的迭代器对象之间进行相等比较之外,即使它们在逻辑上指示相同的位置,也需要非常宽泛地理解“绑定到”的含义以任何方式解释 24.2.5/6在容器中。
另一方面,我注意到应该知道的 Dietmar Kühl 在回答这个问题时说:
C++ 2011 的要求放宽了,迭代器不一定需要产生左值
那么,迭代器可以返回代理,还是不能?如果可以,这种代理的性质是什么?我认为这样的迭代器不符合标准的推理在哪里失败了?
正如所承诺的,一些容器的有效 value_types 不会连续存储在容器中:
1) 一个紧凑的关联容器,其键和值类型可以更有效地存储在两个单独的向量中。(将键保存在向量中还可以提高缓存友好性,并减少分配开销。)
2) Avector<T>
伪装成map<integer_type, T>
,简化了与其他map<X, T>
类型的互操作性。
3)通过压缩其他几个容器形成的逻辑容器,产生一个逻辑值类型,它是tuple
对压缩容器的值类型的引用。(在某些应用程序中,一个或多个压缩容器可能被完全计算,或者作为其他值的函数,或者作为序列号。)
4) 聚合类型容器的视图,它只有一些值。(很可能,底层容器和视图都是元组,其中视图元组的类型列表是底层容器类型的子集,可能以不同的顺序)。
我相信其他人可以轻松添加到此列表中;这些只是我在过去几个月中以某种方式破解的那些。