该委员会将基于范围的for循环从:
{
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
}
}
人们说,这将使Ranges TS的实现更加容易。能给我一些例子吗?
最佳答案
C++ 11/14范围-for
过度约束...
WG21的工作文件是P0184R0,它具有以下动机:
从发布的Standardese中可以看到,范围的end
迭代器仅在循环条件__begin != __end;
中使用。因此,end
仅需要与begin
可比较的相等性,并且不需要是可解除引用的或可递增的。
...会扭曲带分隔符的迭代器的operator==
。
那么这有什么缺点呢?好吧,如果您有一个定点分隔的范围(C字符串,文本行等),则必须将循环条件塞入迭代器的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的Live Example((使用gcc.godbolt.org的assembly)
上面的
operator==
的StringIterator<>
在参数上是对称的,并且不依赖于range-for是begin != end
还是end != begin
(否则您可以作弊并将代码减半)。对于简单的迭代模式,编译器能够优化
operator==
内部的卷积逻辑。实际上,对于以上示例,operator==
被简化为单个比较。但是,这对于范围和过滤器的长管道会继续起作用吗?谁知道。它可能需要英雄般的优化级别。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的Live Example(使用gcc.godbolt.org的assembly,与之前的示例几乎相同)。
...并且实际上将支持完全通用的原始“D样式”范围。
WG21纸N4382有以下建议:
本质上,这等于D样式范围(这些原语称为
empty
,front
和popFront
)。仅包含这些原语的定界字符串范围将如下所示: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; }
};
如果不知道基本范围的基本表示形式,如何从中提取迭代器?如何使它适应可以与range-
for
一起使用的范围?这是一种方法(另请参阅@EricNiebler的series of blog posts)和@ 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;
}
使用g++ -std = c++ 1z的Live Example(使用gcc.godbolt.org的assembly)
结论:哨兵不仅仅是将分隔符按入类型系统的一种可爱机制,它们对于support primitive "D-style" ranges(它们本身可能没有迭代器的概念)来说已经足够通用,可以作为新C++ 1z范围的零开销抽象。对于。