自从问题(和大多数答案)被张贴在这个缺陷报告的决议中,标准已经改变了。
让for(:)循环在你的类型X上工作的方法现在有两种:
创建成员X::begin()和X::end(),返回类似迭代器的东西
创建一个自由函数begin(X&)和end(X&),返回类似迭代器的东西,在与类型X.¹相同的命名空间中
const变量也是一样。这既适用于实现缺陷报告更改的编译器,也适用于不实现缺陷报告更改的编译器。
返回的对象不一定是迭代器。for(:)循环,
for( range_declaration : range_expression )
与c++标准的大多数部分不同,它被指定扩展为等价于:
{
auto && __range = range_expression ;
for (auto __begin = begin_expr,
__end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
其中以__开头的变量仅用于说明,begin_expr和end_expr是调用begin/end.²的魔法
对begin/end返回值的要求很简单:必须重载pre-++,确保初始化表达式有效,可以在布尔上下文中使用的二进制!=,返回可以用于赋值初始化range_declaration的值的一元*,以及公开公共析构函数。
以一种与迭代器不兼容的方式这样做可能是一个坏主意,因为如果您这样做了,c++未来的迭代可能相对不会破坏您的代码。
顺便说一句,标准的未来修订很有可能允许end_expr返回与begin_expr不同的类型。这很有用,因为它允许“惰性端”计算(如检测空终止),这很容易优化,与手写的C循环一样高效,以及其他类似的优点。
¹注意for(:)循环将任何临时值存储在一个auto&&变量中,并将其作为左值传递给你。你无法检测你是否正在迭代一个临时值(或其他右值);这样的重载不会被for(:)循环调用。看到[支撑。范围从n4527的1.2-1.3。
²要么调用begin/end方法,要么只查找adl的自由函数begin/end,要么神奇地支持c风格的数组。注意,std::begin不会被调用,除非range_expression返回一个类型在命名空间std或依赖于same的对象。
在c++17中,range-for表达式已经更新
{
auto && __range = range_expression ;
auto __begin = begin_expr;
auto __end = end_expr;
for (;__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
__begin和__end类型已经解耦。
这允许结束迭代器与开始迭代器的类型不同。end迭代器类型可以是只支持begin迭代器类型的!=的“哨兵”。
为什么这样做很有用的一个实际例子是,当==带有char*时,你的end迭代器可以读取“检查你的char*,看看它是否指向'0'”。这允许c++范围表达式在遍历以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;
}
};
活生生的例子。
最小测试代码是:
struct cstring {
const char* ptr = 0;
const char* begin() const { return ptr?ptr:""; }// return empty string if we are null
null_sentinal_t end() const { return {}; }
};
cstring str{"abc"};
for (char c : str) {
std::cout << c;
}
std::cout << "\n";
这里有一个简单的例子。
namespace library_ns {
struct some_struct_you_do_not_control {
std::vector<int> data;
};
}
你的代码:
namespace library_ns {
int* begin(some_struct_you_do_not_control& x){ return x.data.data(); }
int* end(some_struct_you_do_not_control& x){ return x.data.data()+x.data.size(); }
int const* cbegin(some_struct_you_do_not_control const& x){ return x.data.data(); }
int* cend(some_struct_you_do_not_control const& x){ return x.data.data()+x.data.size(); }
int const* begin(some_struct_you_do_not_control const& x){ return cbegin(x); }
int const* end(some_struct_you_do_not_control const& x){ return cend(x); }
}
这是一个例子,你可以将一个你无法控制的类型增强为可迭代的。
在这里,我返回指针作为迭代器,隐藏了我有一个向量的事实。
对于你拥有的类型,你可以添加方法:
struct egg {};
struct egg_carton {
auto begin() { return eggs.begin(); }
auto end() { return eggs.end(); }
auto cbegin() const { return eggs.begin(); }
auto cend() const { return eggs.end(); }
auto begin() const { return eggs.begin(); }
auto end() const { return eggs.end(); }
private:
std::vector<egg> eggs;
};
这里我重用了vector的迭代器。为了简洁起见,我使用auto;在c++11中,我必须更详细。
下面是一个快速且肮脏的可迭代范围视图:
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
std::size_t size() const
// C++20 only line: (off C++20 it generates a hard error)
requires std::random_access_iterator<It>
{
return end()-begin(); // do not use distance: O(n) size() is toxic
}
bool empty() const { return begin()==end(); }
range_t without_back() const {
if(emptty()) return *this;
return {begin(), std::prev(end())};
}
range_t without_back( std::size_t n ) const
// C++20 only line: (see below)
requires !std::random_access_iterator<It>
{
auto r=*this;
while(n-->0 && !r.empty())
r=r.without_back();
return r;
}
range_t without_front() const {
if(empty()) return *this;
return {std::next(begin()), end()};
}
range_t without_front( std::size_t n ) const
// C++20 only line: (see below)
requires !std::random_access_iterator<It>
{
auto r=*this;
while(n-->0 && !r.empty())
r=r.without_front();
return r;
}
// C++20 section:
range_t without_back( std::size_t n ) const
requires std::random_access_iterator<It>
{
n = (std::min)(n, size());
return {b, e-n};
}
range_t without_front( std::size_t n ) const
requires std::random_access_iterator<It>
{
n = (std::min)(n, size());
return {b+n, e};
}
// end C++20 section
decltype(auto) front() const { return *begin(); }
decltype(auto) back() const { return *(std::prev(end())); }
};
template<class It>
range_t(It,It)->range_t<It>;
template<class C>
auto make_range( C&& c ) {
using std::begin; using std::end;
return range_t{ begin(c), end(c) };
}
使用c++17模板类演绎。
std::vector<int> v{1,2,3,4,5};
for (auto x : make_range(v).without_front(2) ) {
std::cout << x << "\n";
}
指纹3 4 5,跳过前2。