C ++ 17中新的基于范围的for循环如何帮助Ranges TS?
委员会改变了基于范围的循环:
-
C ++ 11:
{ auto && __range = range_expression ; for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }
-
到C ++ 17:
{ auto && __range = range_expression ; auto __begin = begin_expr ; auto __end = end_expr ; for ( ; __begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }
人们说,这将使实施范围TS更容易。 你能举几个例子吗?
C ++ 11/14范围for
overconstrained …
WG21的论文是P0184R0 ,其动机如下:
现有的基于范围的for循环是过度约束的。 结束迭代器不会增加,减less或取消引用。 要求它是一个迭代器没有实际的用途。
从您发布的Standardese中可以看到,范围的end
迭代器仅用于循环条件__begin != __end;
。 因此, end
只需要平等就可以begin
,而且不需要可以解除引用或增加。
…为分隔的迭代器扭曲operator==
。
那么这有什么缺点呢? 那么,如果你有一个定点范围(Cstring,文本行等),那么你必须把循环条件放到迭代器的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() { // "Hello World", no exclamation mark for (auto const& c : StringRange<'!'>{"Hello World!"}) std::cout << c; }
现场示例与g ++ -std = c ++ 14,(使用gcc.godbolt.org的程序集 )
StringIterator<>
的上述operator==
在其参数中是对称的,并且不依赖于range-for是begin != end
还是end != begin
(否则你可以将代码作弊和削减一半)。
对于简单的迭代模式,编译器能够优化operator==
内部的卷积逻辑。 事实上,对于上面的例子, operator==
被简化为一个单一的比较。 但是,这将继续工作的范围和filter的长pipe道? 谁知道。 这很可能需要英雄的优化水平。
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() { // "Hello World", no exclamation mark for (auto const& c : StringRange<'!'>{"Hello World!"}) std::cout << c; }
使用g ++ -std = c ++ 1z的实例 (使用gcc.godbolt.org的程序集 ,与前面的例子几乎相同)。
…实际上将支持完全一般的,原始的“D式”范围。
WG21文件N4382有以下build议:
C.6范围门面和适配器实用程序[future.facade]
1直到用户创build自己的迭代器types变得微不足道,迭代器的全部潜力才会保持不变。 范围抽象使得可以实现。 使用正确的库组件,用户应该可以使用最小的接口定义一个范围(例如,
current
,done
和next
成员),并自动生成迭代器types。 这样的范围门面类模板留作未来的工作。
基本上,这等于D风格的范围(这些原语被称为empty
, front
和popFront
)。 只有这些原语的分隔string范围看起来像这样:
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; } };
如果不知道原始范围的基础表示,如何从中提取迭代器? 如何适应这个范围,可以使用范围for
? 这里有一种方法(请参阅@EricNiebler的一系列博客文章 )和@TC的评论:
#include <iostream> // adapt any primitive range with current/done/next to Iterator/Sentinel pair with begin/end 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() { // "Hello World", no exclamation mark for (auto const& c : RangeAdaptor<PrimitiveStringRange<'!'>>{"Hello World!"}) std::cout << c; }
使用g ++ -std = c ++ 1z的实例 (使用gcc.godbolt.org的程序集 )
结论 :哨兵不仅仅是将分隔符压入types系统的一个可爱的机制,它们足以支持原始的“D风格”范围 (本身可能没有迭代器的概念)作为新C的零开销抽象++ 1z范围。
新的规范允许__begin
和__end
是不同的types,只要__end
可以与__begin
进行比较以获得不等式。 __end
甚至不需要是一个迭代器,可以是一个谓词。 这里有个结构定义begin
和end
成员的愚蠢示例,后者是一个谓词而不是迭代器:
#include <iostream> #include <string> // a struct to get the first word of a string struct FirstWord { std::string data; // declare a predicate to make ' ' a string ender struct EndOfString { bool operator()(std::string::iterator it) { return (*it) != '\0' && (*it) != ' '; } }; std::string::iterator begin() { return data.begin(); } EndOfString end() { return EndOfString(); } }; // declare the comparison operator bool operator!=(std::string::iterator it, FirstWord::EndOfString p) { return p(it); } // test int main() { for (auto c : {"Hello World !!!"}) std::cout << c; std::cout << std::endl; // print "Hello World !!!" for (auto c : FirstWord{"Hello World !!!"}) // works with gcc with C++17 enabled std::cout << c; std::cout << std::endl; // print "Hello" }