自问题(和大多数答案)发布以来,标准已更改在此缺陷报告的解决方案中.
制作方法for(:)
循环处理你的类型X
现在是两种方式之一:
和类似的const
变化。这对于实现缺陷报告更改的编译器和不实现缺陷报告更改的编译器都有效。
返回的对象实际上不必是迭代器。这for(:)
loop,
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
and end_expr
是召唤的魔法begin
/end
.²
对 begin/end 返回值的要求很简单:必须重载 pre-++
,确保初始化表达式有效,二进制!=
可以在布尔上下文中使用,一元*
返回一些你可以分配初始化的东西range_declaration
并公开一个公共析构函数。
以与迭代器不兼容的方式执行此操作可能是一个坏主意,因为如果这样做,C++ 的未来迭代可能会相对漫不经心地破坏您的代码。
顺便说一句,该标准的未来修订很可能会允许end_expr
返回与以下类型不同的类型begin_expr
。这很有用,因为它允许“惰性结束”评估(如检测空终止),很容易优化为与手写 C 循环一样高效,以及其他类似的优点。
1 请注意for(:)
循环将任何临时存储在auto&&
变量,并将其作为左值传递给您。您无法检测是否正在迭代临时(或其他右值);这样的重载不会被调用for(:)
环形。请参阅 n4527 中的 [stmt.ranged] 1.2-1.3。
² 拨打begin
/end
方法,或仅 ADL 查找自由函数begin
/end
, orC 风格数组支持的魔力。注意std::begin
不会被调用,除非range_expression
返回类型为 in 的对象namespace std
或依赖于相同的。
In c++17range-for 表达式已更新
{
auto && __range = range_expression ;
auto __begin = begin_expr;
auto __end = end_expr;
for (;__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
与类型__begin
and __end
已解耦。
这允许结束迭代器与开始迭代器的类型不同。您的最终迭代器类型可以是仅支持的“哨兵”!=
与开始迭代器类型。
为什么这很有用的一个实际例子是,你的最终迭代器可以读取“检查你的char*
看看它是否指向'0'
" when ==
with a char*
。这允许 C++ range-for 表达式在迭代 null 终止时生成最佳代码char*
buffer.
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) };
}
using 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。