委员会改变了基于范围的for循环:
C ++ 11:
{
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
到C ++ 17:
{
auto && __range = range_expression ;
auto __begin = begin_expr ;
auto __end = end_expr ;
for ( ; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
人们说这将使Ranges TS更容易实现。你能举个例子吗?
答案 0 :(得分:47)
for
过度约束...... WG21的论文是P0184R0,其动机如下:
现有的基于范围的for循环过度约束。结束 迭代器永远不会递增,递减或取消引用。要求 它是一个迭代器没有任何实际意义。
从您发布的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;
}
Live Example使用g ++ -std = c ++ 14,(assembly使用gcc.godbolt.org)
以上operator==
StringIterator<>
在其参数中是对称的,并不依赖于范围是begin != end
还是end != begin
(否则你可以作弊和削减代码一半)。
对于简单的迭代模式,编译器能够优化operator==
内的复杂逻辑。实际上,对于上面的例子,operator==
被简化为单一的比较。但这会继续适用于范围和过滤器的长管道吗?谁知道。它可能需要英雄的优化级别。
那么简化的确切表现在哪里?在operator==
中,现在有额外的重载采用迭代器/ sentinel对(在两个顺序中,对称)。因此运行时逻辑变为编译时逻辑。
#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使用g ++ -std = c ++ 1z(assembly使用gcc.godbolt.org,这与前面的例子几乎相同)。
WG21论文N4382有以下建议:
C.6范围外观和适配器实用程序[future.facade]
1直到它 用户创建自己的迭代器类型变得微不足道 迭代器的潜力仍未实现。范围抽象 使这成就可以实现。使用正确的库组件,它应该是 用户可以定义具有最小接口的范围(例如,
current
,done
和next
成员),并拥有迭代器类型 自动生成。这样的范围外观类模板保留为 未来的工作。
基本上,这等于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; }
};
如果不知道原始范围的基础表示,如何从中提取迭代器?如何使其适应范围 - 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;
}
Live Example使用g ++ -std = c ++ 1z(assembly使用gcc.godbolt.org)
结论:哨兵不仅仅是将分隔符按入类型系统的可爱机制,它们通常足以使support primitive "D-style" ranges(它们本身可能没有迭代器的概念)为零 - 用于新C ++ 1z范围的 - 头部抽象 -
答案 1 :(得分:38)
新规范允许__begin
和__end
具有不同的类型,只要__end
可以与__begin
进行不等式比较。 __end
甚至不需要是迭代器,也可以是谓词。这是一个带有定义begin
和end
成员的结构的愚蠢示例,后者是谓词而不是迭代器:
#include <iostream>
#include <string>
// a struct to get the first word of a string
struct FirstWord {
std::string data;
// declare a predicate to make ' ' a string ender
struct EndOfString {
bool operator()(std::string::iterator it) { return (*it) != '\0' && (*it) != ' '; }
};
std::string::iterator begin() { return data.begin(); }
EndOfString end() { return EndOfString(); }
};
// declare the comparison operator
bool operator!=(std::string::iterator it, FirstWord::EndOfString p) { return p(it); }
// test
int main() {
for (auto c : {"Hello World !!!"})
std::cout << c;
std::cout << std::endl; // print "Hello World !!!"
for (auto c : FirstWord{"Hello World !!!"}) // works with gcc with C++17 enabled
std::cout << c;
std::cout << std::endl; // print "Hello"
}