C ++ 17中新的基于范围的for循环如何帮助Ranges TS? [英] How the new range-based for loop in C++17 helps Ranges TS?
问题描述
委员会改变了基于范围的循环: C ++ 11: 到C + +17: 有人说这会使执行范围TS变得更容易。你能举几个例子吗? 正如您从Standardese中看到的那样, Live示例 与g ++ -std = c ++ 14,( 上面的 那么简化体现在哪里呢?在 Live Example 使用g ++ -std = c ++ 1z( assembly 使用gcc.godbolt.org,与前面的例子几乎相同)。 WG21纸 N4382 有以下建议: C.6范围外观和适配器工具[future.facade] 基本上,这等于D风格范围(这些原语被称为 实例 使用g ++ -std = c ++ 1z( 程序集 使用gcc.godbolt.org) b 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? 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 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 Live Example with g++ -std=c++14, (assembly using gcc.godbolt.org) The above For simple iteration patterns, the compiler is able to optimize the convoluted logic inside So where exactly does the simplification manifest itself? In Live Example using g++ -std=c++1z (assembly using gcc.godbolt.org, which is almost identical to the previous example). 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.,
Essentially, this is equal to D-style ranges (where these primitives are called 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- 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?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
{
auto&& __range = range_expression;
for(auto __begin = begin_expr,__end = end_expr;
__begin!= __end; ++ __ begin){
range_declaration = * __ begin;
loop_statement
code $
{
auto&& __range = range_expression;
auto __begin = begin_expr;
auto __end = end_expr;
for(; __begin!= __end; ++ __ begin){
range_declaration = * __ begin;
loop_statement
C ++ 11/14范围 -
对于
是过度约束的。 ..
现有的基于范围的for循环是过度约束的。最后的
迭代器永远不会增加,减少或取消引用。要求
它是一个迭代器没有实际意义。
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;
}
运算符==
对于 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;
}
...并且实际上将支持完全一般的,原始的D风格范围。
变得微不足道,迭代器的完整
潜力仍然未被实现。范围抽象
可以实现。使用正确的库组件,用户可以用
来定义一个带有最小接口的范围(例如,
current
, done
和 next
成员),并自动生成迭代器类型
。这样的范围外观类模板被留作
未来的工作。
空,
前和
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;
}
结论:哨兵不仅仅是一个可以将分隔符分成类型系统的可爱机制,它们足够普遍以足够 支持原语D-style范围 (其本身可能没有迭代器的概念)作为新的C ++ 1z范围的零开销抽象。
{
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
{
auto && __range = range_expression ;
auto __begin = begin_expr ;
auto __end = end_expr ;
for ( ; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
C++11/14 range-
for
was overconstrained...
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.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;
}
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). 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...
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;
}
...and will in fact support fully general, primitive "D-style" ranges.
current
, done
, and next
members), and have iterator types
automatically generated. Such a range facade class template is left as
future work.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; }
};
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;
}