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