ere*_*eOn 230 c++ customization for-loop c++11
像许多人一样,我一直在尝试C + 11带来的不同功能.我最喜欢的一个是"基于范围的循环".
我明白那个:
for(Type& v : a) { ... }
Run Code Online (Sandbox Code Playgroud)
相当于:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
Run Code Online (Sandbox Code Playgroud)
而这begin()只是返回a.begin()标准容器.
但是,如果我想让我的自定义类型"基于范围的循环" - 意识到什么?
如果我只是专注begin()和end()?
如果我的自定义类型属于命名空间xml,我应该定义xml::begin()还是std::begin()?
简而言之,这样做的准则是什么?
Yak*_*ont 163
自问题(和大多数答案)发布在此缺陷报告的决议中以来,标准已更改.
现在,for(:)循环适用于您的类型X的方法有两种:
创建成员X::begin()并X::end()返回一个像迭代器一样的东西
创建一个免费的功能begin(X&)和end(X&)这回的东西,就像一个迭代器,在同一个命名空间为您的类型X.¹
const变化类似.这对于实现缺陷报告更改的编译器和不执行缺陷报告更改的编译器都有效.
返回的对象实际上不必是迭代器.for(:)与C++标准的大多数部分不同,该循环被指定扩展为等效于:
for( range_declaration : range_expression )
Run Code Online (Sandbox Code Playgroud)
变为:
{
auto && __range = range_expression ;
for (auto __begin = begin_expr,
__end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
Run Code Online (Sandbox Code Playgroud)
以变量开头的变量__仅用于展示,begin_expr而且end_expr是调用begin/.的end魔力
对开始/结束返回值的要求很简单:必须先重载pre- ++,确保初始化表达式有效,!=可以在布尔上下文中使用的二进制文件*,返回可以赋值的初始化的一元range_declaration,并公开公共析构函数.
以与迭代器不兼容的方式这样做可能是一个坏主意,因为如果你这样做,C++的未来迭代可能会相对比较破坏你的代码.
顺便说一下,标准的未来修订很可能允许end_expr返回不同的类型begin_expr.这是有用的,因为它允许易于优化的"延迟结束"评估(如检测空终止)与手写C环一样有效,以及其他类似的优点.
¹请注意,for(:)循环将任何临时auto&&值存储在变量中,并将其作为左值传递给您.您无法检测是否正在迭代临时(或其他右值); 这样的重载不会被for(:)循环调用.参见n4527的[stmt.ranged] 1.2-1.3.
²可以调用begin/ end方法,也可以调用ADL-only free function begin/ end,或者魔术来支持C风格的数组.请注意,std::begin除非range_expression返回类型为对象namespace std或依赖于对象的对象,否则不会调用它.
在c ++ 17中,范围表达式已更新
{
auto && __range = range_expression ;
auto __begin = begin_expr;
auto __end = end_expr;
for (;__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
Run Code Online (Sandbox Code Playgroud)
与类型__begin和__end已解耦.
这允许结束迭代器与begin不是同一类型.您的结束迭代器类型可以是"sentinel",它只支持!=begin迭代器类型.
一个有用的原因的一个实际例子是你的结束迭代器可以读取"检查你char*是否指向'0'"时==的a char*.这允许C++ range-for表达式在迭代空终止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;
}
};
Run Code Online (Sandbox Code Playgroud)
在没有完全C++ 17支持的编译器中的实例 for循环手动扩展.
Ste*_*sop 52
标准的相关部分是6.5.4/1:
如果_RangeT是一个类类型,则在类_RangeT的范围内查找unquali fi--id begin和end,就像通过类成员访问查找(3.4.5)一样,如果其中任何一个(或两者)找到至少一个声明,则开始- expr和end-expr分别为
__range.begin()和__range.end();- 否则,begin-expr和end-expr分别是
begin(__range)和end(__range)依赖于参数的查找(3.4.2)查找开始和结束的地方.出于此名称查找的目的,名称空间std是关联的名称空间.
因此,您可以执行以下任何操作:
begin和end成员函数begin和end释放ADL将发现的函数(简化版本:将它们放在与类相同的命名空间中)std::begin和std::endstd::beginbegin()无论如何都要调用成员函数,所以如果你只实现上面的一个,那么无论你选择哪一个,结果应该是相同的.对于基于范围的for循环来说,这是相同的结果,对于没有自己的魔法名称解析规则的凡人代码也是如此,因此只需要using std::begin;进行非限定的调用begin(a).
但是,如果实现了成员函数和 ADL函数,那么基于范围的for循环应该调用成员函数,而凡人将调用ADL函数.最好确保在这种情况下他们做同样的事情!
如果您正在编写的东西实现了容器接口,那么它将具有begin()和end()成员函数,这应该就足够了.如果它是一个不是容器的范围(如果它是不可变的,或者如果你不知道前面的大小,这将是一个好主意),你可以自由选择.
在您列出的选项中,请注意您不能超载std::begin().您可以为用户定义的类型专门化标准模板,但除此之外,向namespace std添加定义是未定义的行为.但无论如何,专门的标准函数是一个糟糕的选择,只是因为缺少部分函数专业化意味着你只能为单个类而不是类模板.
csj*_*ter 43
我写了我的答案,因为有些人可能对没有STL包含的简单现实生活例子更满意.
由于某种原因,我有自己的普通数据阵列实现,我想使用基于范围的for循环.这是我的解决方案:
template <typename DataType>
class PodArray {
public:
class iterator {
public:
iterator(DataType * ptr): ptr(ptr){}
iterator operator++() { ++ptr; return *this; }
bool operator!=(const iterator & other) const { 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 ...
};
Run Code Online (Sandbox Code Playgroud)
然后是用法示例:
PodArray<char> array;
// fill up array in some way
for(auto& c : array)
printf("char: %c\n", c);
Run Code Online (Sandbox Code Playgroud)
BЈо*_*вић 33
我应该专门研究begin()和end()吗?
据我所知,这已经足够了.您还必须确保递增指针将从开始到结束.
下一个例子(它缺少开始和结束的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;
}
}
Run Code Online (Sandbox Code Playgroud)
这是另一个以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;
}
}
Run Code Online (Sandbox Code Playgroud)
Chr*_*ord 13
如果您想直接与背面类的迭代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;
}
Run Code Online (Sandbox Code Playgroud)