C ++ 11/14范围-过于for
受限...
为此,WG21论文为P0184R0,其动机如下:
现有的基于范围的for循环受到过度约束。最终迭代器永远不会递增,递减或取消引用。要求它是迭代器没有实际目的。
从发布的Standardese中可以看到,end
范围的迭代器仅在loop-condition中使用__begin != __end;
。因此,end
只需要等价于begin
,就可以相等,并且不需要是可解除引用或可递增的。
...这会使operator==
定界的迭代器失真。
那么这有什么缺点呢?好吧,如果您有一个定点分隔的范围(C字符串,文本行等),则必须将循环条件塞入迭代器的operator==
,基本上是这样的
#include <iostream>
template <char Delim = 0>
struct StringIterator
{
char const* ptr = nullptr;
friend auto operator==(StringIterator lhs, StringIterator rhs) {
return lhs.ptr ? (rhs.ptr || (*lhs.ptr == Delim)) : (!rhs.ptr || (*rhs.ptr == Delim));
}
friend auto operator!=(StringIterator lhs, StringIterator rhs) {
return !(lhs == rhs);
}
auto& operator*() { return *ptr; }
auto& operator++() { ++ptr; return *this; }
};
template <char Delim = 0>
class StringRange
{
StringIterator<Delim> it;
public:
StringRange(char const* ptr) : it{ptr} {}
auto begin() { return it; }
auto end() { return StringIterator<Delim>{}; }
};
int main()
{
for (auto const& c : StringRange<'!'>{"Hello World!"})
std::cout << c;
}
带有g ++ -std = c ++ 14的实时示例,(汇编使用gcc.godbolt.org)
上面的operator==
forStringIterator<>
参数是对称的,并且不取决于range-for是begin != end
or end != begin
(否则您可以作弊并将代码减半)。
对于简单的迭代模式,编译器能够优化里面的复杂逻辑operator==
。实际上,对于以上示例,将operator==
简化为单个比较。但是,这对于范围和过滤器的长管道会继续起作用吗?谁知道。它可能需要英雄般的优化级别。
C ++ 17将放宽约束,从而简化定界范围...
那么,简化在何处体现呢?在中operator==
,现在有一个额外的重载,采用一个迭代器/前哨对(两个对称)。因此,运行时逻辑变为编译时逻辑。
#include <iostream>
template <char Delim = 0>
struct StringSentinel {};
struct StringIterator
{
char const* ptr = nullptr;
template <char Delim>
friend auto operator==(StringIterator lhs, StringSentinel<Delim> rhs) {
return *lhs.ptr == Delim;
}
template <char Delim>
friend auto operator==(StringSentinel<Delim> lhs, StringIterator rhs) {
return rhs == lhs;
}
template <char Delim>
friend auto operator!=(StringIterator lhs, StringSentinel<Delim> rhs) {
return !(lhs == rhs);
}
template <char Delim>
friend auto operator!=(StringSentinel<Delim> lhs, StringIterator rhs) {
return !(lhs == rhs);
}
auto& operator*() { return *ptr; }
auto& operator++() { ++ptr; return *this; }
};
template <char Delim = 0>
class StringRange
{
StringIterator it;
public:
StringRange(char const* ptr) : it{ptr} {}
auto begin() { return it; }
auto end() { return StringSentinel<Delim>{}; }
};
int main()
{
for (auto const& c : StringRange<'!'>{"Hello World!"})
std::cout << c;
}
活实施例使用的g ++ -std = C ++ 1Z(组件使用gcc.godbolt.org,这几乎是相同的前面的例子)。
...并且实际上将支持完全通用的原始“ D样式”范围。
WG21文件N4382具有以下建议:
C.6范围外观和适配器实用程序[future.facade]
1在用户创建自己的迭代器类型变得很简单之前,迭代器的全部潜力都将无法实现。范围抽象使这成为可能。有了合适的库组件,它应该是用户能够定义与最少的接口(例如,范围
current
,done
和next
成员),和具有的迭代器类型自动生成。这样的范围外观类模板将留作将来的工作。
从本质上讲,这是等于d式的范围(其中,这些原语被称为empty
,front
和popFront
)。仅包含这些原语的定界字符串范围将如下所示:
template <char Delim = 0>
class PrimitiveStringRange
{
char const* ptr;
public:
PrimitiveStringRange(char const* c) : ptr{c} {}
auto& current() { return *ptr; }
auto done() const { return *ptr == Delim; }
auto next() { ++ptr; }
};
如果不知道基本范围的基本表示形式,如何从中提取迭代器?如何使它适应可以与range-一起使用的范围for
?这是一种方法(另请参阅@EricNiebler的系列博客文章)和@TC的评论:
#include <iostream>
template <class Derived>
struct RangeAdaptor : private Derived
{
using Derived::Derived;
struct Sentinel {};
struct Iterator
{
Derived* rng;
friend auto operator==(Iterator it, Sentinel) { return it.rng->done(); }
friend auto operator==(Sentinel, Iterator it) { return it.rng->done(); }
friend auto operator!=(Iterator lhs, Sentinel rhs) { return !(lhs == rhs); }
friend auto operator!=(Sentinel lhs, Iterator rhs) { return !(lhs == rhs); }
auto& operator*() { return rng->current(); }
auto& operator++() { rng->next(); return *this; }
};
auto begin() { return Iterator{this}; }
auto end() { return Sentinel{}; }
};
int main()
{
for (auto const& c : RangeAdaptor<PrimitiveStringRange<'!'>>{"Hello World!"})
std::cout << c;
}
活实施例使用的g ++ -std = C ++ 1Z(组件使用gcc.godbolt.org)
结论:哨兵不仅仅是将定界符压入类型系统的一种可爱机制,它们还通用到足以支持原始的“ D样式”范围(它们本身可能没有迭代器的概念)作为新C的零开销抽象。 ++ 1z范围。