C ++ 17中新的基于范围的for循环如何帮助Ranges TS?

编程入门 行业动态 更新时间:2024-10-19 02:26:05
本文介绍了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 code $
  • 到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范围 - 对于是过度约束的。 ..

    WG21的这篇文章是

    现有的基于范围的for循环是过度约束的。最后的迭代器永远不会增加,减少或取消引用。要求它是一个迭代器没有实际意义。

    正如您从Standardese中看到的那样, end 范围的迭代器仅用于循环条件 __ begin!= __end; 。因此, end 只需要与 begin 相等,不需要可解引用或增量。 $ b ...为定界迭代器扭曲 operator == 。 那么这有什么缺点呢?那么,如果你有一个定位范围(C字符串,文本行等),那么你必须把循环条件放到迭代器的运算符== ,就像这样

    #include< iostream> 模板< char Delim = 0> struct StringIterator { char const * ptr = nullptr; 朋友自动运算符==(StringIterator lhs,StringIterator rhs){ return lhs.ptr? (rhs.ptr ||(* lhs.ptr == Delim)):(!rhs.ptr ||(* rhs.ptr == Delim)); 自动运算符!=(StringIterator lhs,StringIterator rhs){ return!(lhs == rhs); } auto&运算符*(){return * ptr; } auto&运算符++(){++ ptr;返回*这个; } }; 模板< char Delim = 0> class StringRange { StringIterator< Delim>它; public: StringRange(char const * ptr):it {ptr} {} auto begin(){return it; } auto end(){return StringIterator< Delim> {}; } }; $ b $ int main() { //Hello World,没有感叹号 for(auto const& c:StringRange<'!'> { Hello World!)) std :: cout<< C; }

    Live示例 与g ++ -std = c ++ 14,(

    上面的运算符== 对于 StringIterator<> 在其参数中是对称的,并且不依赖于range-for是否是开始!=结束或结束!=开始(否则你可以欺骗和削减一半的代码)。对于简单的迭代模式,编译器能够优化 operator == 中的卷积逻辑。事实上,对于上面的例子,运算符== 被简化为一个单一的比较。但是,这将继续工作的范围和过滤器的长管道?谁知道。这可能需要英雄式的优化级别。 C ++ 17将放宽约束,这将简化分隔范围...

    那么简化体现在哪里呢?在运算符== 中,现在有额外的重载采取一个迭代器/哨兵对(两个顺序,对称)。所以运行时逻辑变成编译时逻辑。

    #include< iostream> 模板< char Delim = 0> struct StringSentinel {}; struct StringIterator { char const * ptr = nullptr; 模板< char Delim> 朋友自动运算符==(StringIterator lhs,StringSentinel< Delim> rhs){ return * lhs.ptr == Delim; } 模板< char Delim> 朋友自动运算符==(StringSentinel< Delim> lhs,StringIterator rhs){ return rhs == lhs; } 模板< char Delim> friend auto operator!=(StringIterator lhs,StringSentinel< Delim> rhs){ return!(lhs == rhs); } 模板< char Delim> 朋友自动运算符!=(StringSentinel< Delim> lhs,StringIterator rhs){ return!(lhs == rhs); } auto&运算符*(){return * ptr; } auto&运算符++(){++ ptr;返回*这个; } }; 模板< char Delim = 0> class StringRange { StringIterator it; public: StringRange(char const * ptr):it {ptr} {} auto begin(){return it; } auto end(){return StringSentinel< Delim> {}; } }; $ b $ int main() { //Hello World,没有感叹号 for(auto const& c:StringRange<'!'> { Hello World!)) std :: cout<< C; }

    Live Example 使用g ++ -std = c ++ 1z( assembly 使用gcc.godbolt,与前面的例子几乎相同)。 ...并且实际上将支持完全一般的,原始的D风格范围。

    WG21纸 N4382 有以下建议:

    C.6范围外观和适配器工具[future.facade]

    1直到用户创建自己的迭代器类型为止,变得微不足道,迭代器的完整潜力仍然未被实现。范围抽象可以实现。使用正确的库组件,用户可以用来定义一个带有最小接口的范围(例如, current , done 和 next 成员),并自动生成迭代器类型。这样的范围外观类模板被留作未来的工作。

    基本上,这等于D风格范围(这些原语被称为空,前和 popFront ) 。只有这些原语的分隔字符串范围看起来像这样:

    模板< 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; } };如果不知道基本范围的底层表示,如何从中提取迭代器?如何适应这个范围可以使用范围 - 为?以下是一种方法(另请参阅@EricNiebler的 一系列博客文章 )和@TC的评论:

    #include< iostream> //使用iterator / Sentinel对中的当前/完成/接下来的开始/结束来修改任何原始范围 template< class Derived> 结构RangeAdaptor:private派生 {使用Derived :: Derived; struct Sentinel {}; struct Iterator { Derived * rng; 朋友自动运算符==(Iterator it,Sentinel){return it.rng-> done(); } 朋友自动运算符==(Sentinel,Iterator it){return it.rng-> done(); } 自动运算符!=(Iterator lhs,Sentinel rhs){return!(lhs == rhs); } 朋友自动运算符!=(Sentinel lhs,Iterator rhs){return!(lhs == rhs); } auto& operator *(){return rng-> current(); } auto& operator ++(){rng-> next();返回*这个; } }; auto begin(){return Iterator {this}; } auto end(){return Sentinel {}; } }; $ b b main() { //Hello World,没有感叹号 for(auto const& c:RangeAdaptor< PrimitiveStringRange<'!'> ;> {Hello World!}) std :: cout<< C; }

    实例 使用g ++ -std = c ++ 1z( 程序集 使用gcc.godbolt) b 结论:哨兵不仅仅是一个可以将分隔符分成类型系统的可爱机制,它们足够普遍以足够 支持原语D-style范围 (其本身可能没有迭代器的概念)作为新的C ++ 1z范围的零开销抽象。

    The committee changed the range-based for loop from:

    • C++11:

      { auto && __range = range_expression ; for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }

    • to C++17 :

      { auto && __range = range_expression ; auto __begin = begin_expr ; auto __end = end_expr ; for ( ; __begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }

    And people said that this will make implementing Ranges TS easier. Can you give me some examples?

    解决方案

    C++11/14 range-for was overconstrained...

    The WG21 paper for this is P0184R0 which has the following motivation:

    The existing range-based for loop is over-constrained. The end iterator is never incremented, decremented, or dereferenced. Requiring it to be an iterator serves no practical purpose.

    As you can see from the Standardese that you posted, the end iterator of a range is only used in the loop-condition __begin != __end;. Hence end only needs to be equality comparable to begin, and it does not need to be dereferenceable or incrementable.

    ...which distorts operator== for delimited iterators.

    So what disadvantage does this have? Well, if you have a sentinel-delimited range (C-string, line of text, etc.), then you have to shoehorn the loop-condition into the iterator's operator==, essentially like this

    #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; }

    Live Example with g++ -std=c++14, (assembly using gcc.godbolt)

    The above operator== for StringIterator<> is symmetric in its arguments and does not rely on whether the range-for is begin != end or end != begin (otherwise you could cheat and cut the code in half).

    For simple iteration patterns, the compiler is able to optimize the convoluted logic inside operator==. Indeed, for the above example, the operator== is reduced to a single comparison. But will this continue to work for long pipelines of ranges and filters? Who knows. It is likely to require heroic optimization levels.

    C++17 will relax the constraints which will simplify delimited ranges...

    So where exactly does the simplification manifest itself? In operator==, which now has extra overloads taking an iterator/sentinel pair (in both orders, for symmetry). So the run time logic becomes compile time logic.

    #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; }

    Live Example using g++ -std=c++1z (assembly using gcc.godbolt, which is almost identical to the previous example).

    ...and will in fact support fully general, primitive "D-style" ranges.

    WG21 paper N4382 has the following suggestion:

    C.6 Range Facade and Adaptor Utilities [future.facade]

    1 Until it becomes trivial for users to create their own iterator types, the full potential of iterators will remain unrealized. The range abstraction makes that achievable. With the right library components, it should be possible for users to define a range with a minimal interface (e.g., current, done, and next members), and have iterator types automatically generated. Such a range facade class template is left as future work.

    Essentially, this is equal to D-style ranges (where these primitives are called empty, front and popFront). A delimited string range with only these primitives would look something like this:

    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; } };

    If one does not know the underlying representation of a primitive range, how to extract iterators from it? How to adapt this to a range that can be used with range-for? Here's one way (see also the series of blog posts by @EricNiebler) and the comments from @T.C.:

    #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; }

    Live Example using g++ -std=c++1z (assembly using gcc.godbolt)

    Conclusion: sentinels are not just a cute mechanism to press delimiters into the type system, they are general enough to support primitive "D-style" ranges (which themselves may have no notion of iterators) as a zero-overhead abstraction for the new C++1z range-for.

更多推荐

C ++ 17中新的基于范围的for循环如何帮助Ranges TS?

本文发布于:2023-11-29 20:24:49,感谢您对本站的认可!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:中新   TS   Ranges

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!