像现在的许多人一样,我一直在尝试 C++11 带来的不同功能。我的最爱之一是“基于范围的 for 循环”。
我明白那个:
for(Type& v : a) { ... }
相当于:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
而 begin()
只返回 a.begin()
用于标准容器。
但是,如果我想让 我的自定义类型“基于范围的 for 循环”感知 呢?
我应该只专注 begin()
和 end()
吗?
如果我的自定义类型属于命名空间 xml
,我应该定义 xml::begin()
还是 std::begin()
?
简而言之,这样做的指导方针是什么?
原文由 ereOn 发布,翻译遵循 CC BY-SA 4.0 许可协议
自从问题(和大多数答案)发布 在此缺陷报告的解决方案中 以来,标准已更改。
使
for(:)
循环在您的类型X
上工作的方法现在是以下两种方法之一:创建成员
X::begin()
和X::end()
返回类似于迭代器的东西创建一个自由函数
begin(X&)
和end(X&)
返回类似于迭代器的东西,在与您的类型相同的命名空间中X
与
const
变化类似。这将适用于实现缺陷报告更改的编译器和不实现缺陷报告更改的编译器。返回的对象不一定是迭代器。与 C++ 标准的大多数部分不同,
for(:)
循环被 指定为扩展为相当于:变成:
where the variables beginning with
__
are for exposition only, andbegin_expr
andend_expr
is the magic that callsbegin
/end
.²对开始/结束返回值的要求很简单:您必须重载 pre-
++
,确保初始化表达式有效,二进制!=
可以在布尔上下文中使用,一元*
返回可以分配初始化的东西range_declaration
并公开一个公共析构函数。以与迭代器不兼容的方式这样做可能是一个坏主意,因为如果你这样做,C++ 的未来迭代可能会相对比较随意地破坏你的代码。
顺便说一句,该标准的未来修订版很有可能允许
end_expr
返回与begin_expr
不同的类型。这很有用,因为它允许“延迟”评估(如检测空终止),易于优化,与手写 C 循环一样高效,以及其他类似优点。¹ 请注意,
for(:)
循环将任何临时存储在auto&&
变量中,并将其作为左值传递给您。您无法检测是否正在迭代临时(或其他右值);这样的重载不会被for(:)
循环调用。参见 n4527 中的 [stmt.ranged] 1.2-1.3。² Either call the
begin
/end
method, or ADL-only lookup of free functionbegin
/end
, or magic for C-样式数组支持。请注意std::begin
不会被调用,除非range_expression
返回类型为namespace std
或依赖于相同类型的对象。在 c++17 中,range-for 表达式已更新
与
__begin
和__end
的类型已解耦。这允许结束迭代器与开始的类型不同。您的结束迭代器类型可以是“哨兵”,它仅支持带有开始迭代器类型的
!=
。为什么这很有用的一个实际示例是,您的最终迭代器可以读取“检查您的
char*
以查看它是否指向'0'
”当==
带有char*
。这允许 C++ range-for 表达式在迭代以 null 结尾的char*
缓冲区时生成最佳代码。活生生的例子。
最小的测试代码是:
这是一个简单的例子。
你的代码:
这是一个示例,您可以如何将无法控制的类型扩充为可迭代。
在这里,我将指针作为迭代器返回,隐藏了我在引擎盖下有一个向量的事实。
对于您拥有的类型,您可以添加方法:
这里我重用了
vector
的迭代器。为了简洁起见,我使用auto
;在 c++11 中,我必须更加冗长。这是一个快速而肮脏的可迭代范围视图:
使用 c++17 模板类推导。
打印 3 4 5,跳过前 2。