如何使我的自定义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(:)循环在Xtypes上工作:

  • 创build成员X::begin()X::end() ,返回一些像迭代器一样的东西

  • 创build一个免费的函数begin(X&)end(X&) ,返回一个像迭代器一样的东西,放在与Xtypes相同的命名空间中.¹

类似的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_exprend_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是一个关联的名字空间。

所以,你可以做任何以下的事情:

  • 定义beginend成员函数
  • 定义将由ADL发现的beginend自由函数(简化版本:将它们放在与类相同的命名空间中)
  • std::beginstd::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::vectorstd::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; }