問題描述
委員會改變了基于范圍的 for 循環:
C++11:
<代碼>{汽車&&__range = range_expression ;for (auto __begin = begin_expr, __end = end_expr;__開始!= __結束;++__開始){range_declaration = *__begin;循環語句}}
到 C++17:
<代碼>{汽車&&__range = range_expression ;自動 __begin = begin_expr ;自動 __end = end_expr ;for ( ; __begin != __end; ++__begin) {range_declaration = *__begin;循環語句}}
人們說這將使實施 Ranges TS 更容易.你能給我舉一些例子嗎?
C++11/14 range-for
was overconstrained...
WG21 論文是 P0184R0 其動機如下:
<塊引用>現有的基于范圍的 for 循環受到過度約束.結束迭代器永遠不會遞增、遞減或取消引用.需要它作為一個迭代器沒有實際用途.
從您發布的 Standardese 中可以看出,范圍的 end
迭代器僅用于循環條件 __begin != __end;
.因此,end
只需要與 begin
相等,不需要可解引用或可遞增.
...它扭曲了分隔迭代器的 operator==
.
那么這有什么缺點呢?好吧,如果您有一個標記分隔的范圍(C 字符串、文本行等),那么您必須將循環條件硬塞到迭代器的 operator==
中,本質上是這樣
實時示例 使用 g++ -std=c++14,(組裝使用 gcc.godbolt.org)
上述 StringIterator<>
的 operator==
在其參數上是對稱的,并且不依賴于 range-for 是否為 begin != end
或 end != begin
(否則你可以作弊并將代碼切成兩半).
對于簡單的迭代模式,編譯器能夠優化operator==
內部的復雜邏輯.事實上,對于上面的例子,operator==
被簡化為一個單一的比較.但這會繼續適用于范圍和過濾器的長管道嗎?誰知道.很可能需要英雄般的優化級別.
C++17 將放寬約束,這將簡化分隔范圍...
那么簡化究竟體現在什么地方呢?在 operator==
中,現在有額外的重載采用迭代器/哨兵對(在兩個順序中,為了對稱).所以運行時邏輯變成了編譯時邏輯.
現場示例 使用 g++ -std=c++1z(組裝,使用 gcc.godbolt.org,幾乎與上一個例子).
...實際上將支持完全通用的原始D 風格"范圍.
WG21 論文 N4382 有以下建議:
<塊引用>C.6 Range Facade 和適配器實用程序 [future.facade]
1 直到它用戶創建自己的迭代器類型變得微不足道,完整的迭代器的潛力將仍未實現.范圍抽象使之成為可能.使用正確的庫組件,它應該是用戶可以用最少的界面定義一個范圍(例如,current
、done
和 next
成員),并具有迭代器類型自動生成.這樣的范圍外觀類模板保留為未來的工作.
本質上,這等于 D 風格的范圍(這些原語被稱為 empty
、front
和 popFront
).只有這些原語的分隔字符串范圍看起來像這樣:
如果不知道原始范圍的底層表示,如何從中提取迭代器?如何將其調整為可與 range-for
一起使用的范圍?這是一種方法(另請參閱@EricNiebler 的系列博文) 和@TC 的評論:
現場示例 使用 g++ -std=c++1z(組裝使用 gcc.godbolt.org)
結論:哨兵不僅是一種將分隔符壓入類型系統的可愛機制,它們還足夠通用支持原始的D 風格"范圍(它們本身可能沒有迭代器的概念)作為新的 C++1z range-for 的零開銷抽象.>
The committee changed the range-based for loop from:
C++11:
to C++17 :
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
Live Example with g++ -std=c++14, (assembly using gcc.godbolt.org)
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.
Live Example using g++ -std=c++1z (assembly using gcc.godbolt.org, 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
, andnext
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:
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.:
Live Example using g++ -std=c++1z (assembly using gcc.godbolt.org)
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?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!