如何使我的自定义types使用“基于范围的循环”?
像许多人一样,我一直在尝试C + 11带来的不同function。 我最喜欢的是“基于范围的循环”。
我明白那个:
for(Type& v : a) { ... }
相当于:
for(auto iv = begin(a); iv != end(a); ++iv) { Type& v = *iv; ... }
而begin()
只是返回标准容器的a.begin()
。
但是,如果我想让我的自定义types“基于范围的循环”意识 ?
我应该专门begin()
和end()
?
如果我的自定义types属于命名空间xml
,我应该定义xml::begin()
或std::begin()
吗?
总之,有什么指导方针呢?
这个标准已经被修改,因为这个问题(以及大部分的答案)都是在这个缺陷报告的解决scheme中发布的 。
现在可以通过以下两种方法之一来使for(:)
循环在X
types上工作:
-
创build成员
X::begin()
和X::end()
,返回一些像迭代器一样的东西 -
创build一个免费的函数
begin(X&)
和end(X&)
,返回一个像迭代器一样的东西,放在与X
types相同的命名空间中.¹
类似的const
变化。 这既适用于实现缺陷报告更改的编译器,也适用于不适用的编译器。
返回的对象不必实际是迭代器。 for(:)
循环与C ++标准的大多数部分不同,被指定为扩展到相当于 :
for( range_declaration : range_expression )
变为:
{ auto && __range = range_expression ; for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }
其中以__
开始的variables仅用于说明,而begin_expr
和end_expr
是调用begin
/ end
魔法。
对begin / end返回值的要求很简单:必须重载pre ++
,确保初始化expression式是有效的,二进制!=
可以在布尔上下文中使用,一元*
返回可以赋值的东西 – 初始化range_declaration
与,并揭露一个公共的析构函数。
这样做的方式与迭代器不兼容可能是一个坏主意,因为未来的C ++迭代可能相对比较傲慢,如果你这样做的话就会破坏你的代码。
end_expr
,标准的将来修订版可能会允许end_expr
返回与begin_expr
不同的types。 这是有用的,因为它允许容易优化的“懒惰结束”评估(如检测空终止),和手写C循环一样高效,以及其他类似的优点。
¹请注意, for(:)
循环将任何临时值存储在auto&&
variables中,并将其作为左值传递给您。 你不能检测你是否迭代临时(或其他右值)。 这样的重载不会被for(:)
循环调用。 参见n4527中的[stmt.ranged] 1.2-1.3。
²要么调用begin
/ end
方法,要么只用ADL查找自由函数begin
/ end
, 或者调用C型arrays支持的魔法。 请注意, std::begin
不会被调用,除非range_expression
返回namespace std
的types对象或依赖于它。
在c ++ 17中 ,range-forexpression式已被更新
{ auto && __range = range_expression ; auto __begin = begin_expr; auto __end = end_expr for (;__begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }
__begin
和__end
的types已经被解耦。
这允许结束迭代器不是开始的相同types。 你的结束迭代器types可以是一个“sentinel”,它只支持!=
开始迭代器types。
一个实际的例子,为什么这是有用的是你的结束迭代器可以读取“检查你的char*
,看看它是否指向'0'
” ==
与char*
。 这允许C ++ range-forexpression式在迭代以null结尾的char*
缓冲区时生成最优代码。
struct null_sentinal_t { template<class Rhs, std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0 > friend bool operator==(Rhs const& ptr, null_sentinal_t) { return !*ptr; } template<class Rhs, std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0 > friend bool operator!=(Rhs const& ptr, null_sentinal_t) { return !(ptr==null_sentinal_t{}); } template<class Lhs, std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0 > friend bool operator==(null_sentinal_t, Lhs const& ptr) { return !*ptr; } template<class Lhs, std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0 > friend bool operator!=(null_sentinal_t, Lhs const& ptr) { return !(null_sentinal_t{}==ptr); } friend bool operator==(null_sentinal_t, null_sentinal_t) { return true; } friend bool operator!=(null_sentinal_t, null_sentinal_t) { return false; } };
没有完整的C ++ 17支持的编译器中的实例 ; for
循环手动扩展。
标准的相关部分是6.5.4 / 1:
如果_RangeT是一个类的types,那么在类_RangeT的范围内查找未经限定的ID开始和结束,就好像通过类成员访问查找(3.4.5)一样,如果(或者两者)至less有一个声明, – expr和end-expr分别是
__range.begin()
和__range.end()
;– 否则,begin-expr和end-expr分别是
begin(__range)
和end(__range)
,其中begin和end分别用参数相关查找(3.4.2)查找。 为了查找这个名字,namespace std是一个关联的名字空间。
所以,你可以做任何以下的事情:
- 定义
begin
和end
成员函数 - 定义将由ADL发现的
begin
和end
自由函数(简化版本:将它们放在与类相同的命名空间中) - 专
std::begin
和std::end
std::begin
调用begin()
成员函数,所以如果你只实现上面的一个,那么结果应该是一样的,不pipe你select哪一个。 对于基于范围的for循环来说,这也是一样的结果,对于没有自己的魔法名称parsing规则的凡人代码也是如此,所以using std::begin;
接下来是一个不合格的电话begin(a)
。
如果你实现了成员函数和 ADL函数,那么基于范围的for循环应该调用成员函数,而凡人都会调用ADL函数。 最好确保他们在这种情况下做同样的事情!
如果你正在编写的东西实现了容器接口,那么它已经有了begin()
和end()
成员函数,这应该足够了。 如果这个范围不是一个容器(如果这个容器是不可变的,或者如果你不知道容器大小的话,这是一个好主意),你可以自由select。
在你select的选项中,注意你不能重载std::begin()
。 您被允许为用户定义的types专门化标准模板,但除此之外,向namespace std添加定义是未定义的行为。 但是,无论如何,专业的标准function是一个不好的select,如果仅仅是因为缺乏部分function专业化意味着你只能做一个单一的类,而不是一个类模板。
我应该专门开始()和结束()?
据我所知,这就够了。 你还必须确保增加指针会从开始到结束。
下一个例子(缺less开始和结束的const版本)编译和工作正常。
#include <iostream> #include <algorithm> int i=0; struct A { A() { std::generate(&v[0], &v[10], [&i](){ return ++i;} ); } int * begin() { return &v[0]; } int * end() { return &v[10]; } int v[10]; }; int main() { A a; for( auto it : a ) { std::cout << it << std::endl; } }
这里是以begin / end为函数的另一个例子。 由于ADL,它们必须与类相同的名称空间:
#include <iostream> #include <algorithm> namespace foo{ int i=0; struct A { A() { std::generate(&v[0], &v[10], [&i](){ return ++i;} ); } int v[10]; }; int *begin( A &v ) { return &v.v[0]; } int *end( A &v ) { return &v.v[10]; } } // namespace foo int main() { foo::A a; for( auto it : a ) { std::cout << it << std::endl; } }
我写了我的答案,因为有些人可能更喜欢简单的现实生活中没有包含STL的例子。
由于某种原因,我有自己纯粹的数据数组实现,我想使用基于循环的范围。 这是我的解决scheme:
template <typename DataType> class PodArray { public: class iterator { public: iterator(DataType * ptr): ptr(ptr){} iterator operator++() { ++ptr; return *this; } bool operator!=(const iterator & other) { return ptr != other.ptr; } const DataType& operator*() const { return *ptr; } private: DataType* ptr; }; private: unsigned len; DataType *val; public: iterator begin() const { return iterator(val); } iterator end() const { return iterator(val + len); } // rest of the container definition not related to the question ... };
然后用法示例:
PodArray<char> array; // fill up array in some way for(auto& c : array) printf("char: %c\n", c);
如果你想直接用它的std::vector
或std::map
成员来支持一个类的迭代,下面是代码:
#include <iostream> using std::cout; using std::endl; #include <string> using std::string; #include <vector> using std::vector; #include <map> using std::map; ///////////////////////////////////////////////////// /// classes ///////////////////////////////////////////////////// class VectorValues { private: vector<int> v = vector<int>(10); public: vector<int>::iterator begin(){ return v.begin(); } vector<int>::iterator end(){ return v.end(); } vector<int>::const_iterator begin() const { return v.begin(); } vector<int>::const_iterator end() const { return v.end(); } }; class MapValues { private: map<string,int> v; public: map<string,int>::iterator begin(){ return v.begin(); } map<string,int>::iterator end(){ return v.end(); } map<string,int>::const_iterator begin() const { return v.begin(); } map<string,int>::const_iterator end() const { return v.end(); } const int& operator[](string key) const { return v.at(key); } int& operator[](string key) { return v[key]; } }; ///////////////////////////////////////////////////// /// main ///////////////////////////////////////////////////// int main() { // VectorValues VectorValues items; int i = 0; for(int& item : items) { item = i; i++; } for(int& item : items) cout << item << " "; cout << endl << endl; // MapValues MapValues m; m["a"] = 1; m["b"] = 2; m["c"] = 3; for(auto pair: m) cout << pair.first << " " << pair.second << endl; }