像现在的许多人一样,我一直在尝试 C++11 带来的不同功能。我的最爱之一是“基于范围的 for 循环”。
我明白那个:
for(Type& v : a) { ... }
相当于:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
还有那个
begin()
只需返回 a.begin()
用于标准容器。但是,如果我想让我的自定义类型“基于范围的 for 循环”感知呢?
我应该专攻吗
begin()
和 end()
?如果我的自定义类型属于命名空间
xml
, 我应该定义 xml::begin()
或 std::begin()
?简而言之,这样做的指导方针是什么?
最佳答案
自从发布问题(和大多数答案)以来,标准已更改 in the resolution of this defect report .
制作for(:)
的方法在您的类型上循环工作 X
现在是两种方式之一:
X::begin()
和 X::end()
返回一些像迭代器一样的东西begin(X&)
和 end(X&)
返回类似于迭代器的东西,在与您的类型相同的命名空间中 X
.¹和
const
类似变化。这将适用于实现缺陷报告更改的编译器和不实现的编译器。返回的对象实际上不必是迭代器。
for(:)
循环,与 C++ 标准的大多数部分不同,是 specified to expand to something equivalent to :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
}
}
其中以 __
开头的变量仅供展示,begin_expr
和 end_expr
是魔法召唤begin
/end
.²对开始/结束返回值的要求很简单:您必须重载 pre-
++
, 确保初始化表达式有效,二进制 !=
可以在 bool 上下文中使用,一元 *
返回一些你可以分配初始化的东西 range_declaration
和公开一个公共(public)析构函数。以与迭代器不兼容的方式执行此操作可能是一个坏主意,因为如果您这样做,C++ 的 future 迭代可能会相对傲慢地破坏您的代码。
顺便说一句,该标准的 future 修订版很可能允许
end_expr
返回与 begin_expr
不同的类型.这很有用,因为它允许易于优化的“懒惰”评估(如检测空终止)与手写 C 循环一样有效,以及其他类似的优点。¹ 请注意
for(:)
循环将任何临时存储在 auto&&
中变量,并将其作为左值传递给您。您无法检测是否正在迭代临时(或其他右值); for(:)
不会调用这样的重载环形。参见 n4527 的 [stmt.ranged] 1.2-1.3。² 要么调用
begin
/end
方法或仅 ADL 查找自由函数 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
}
}
与 __begin
的类型和 __end
已经解耦了。这允许结束迭代器与开始的类型不同。您的最终迭代器类型可以是仅支持
!=
的“哨兵”使用开始迭代器类型。为什么这很有用的一个实际例子是,当
char*
时,您的最终迭代器可以读取“检查您的 '0'
以查看它是否指向 ==
”。与 char*
.这允许 C++ 范围表达式在迭代以空值结尾的 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;
}
};
live example这个的。最小的测试代码是:
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); }
}
这是一个示例,您可以将无法控制的类型扩充为可迭代的。在这里,我返回指针作为迭代器,隐藏了我在引擎盖下有一个 vector 的事实。
对于您拥有的类型,您可以添加方法:
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我必须更详细。这是一个快速而肮脏的可迭代范围 View :
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
std::size_t size() const { return end()-begin(); }
bool empty() const { return begin()==end(); }
range_t without_back( std::size_t n = 1 ) const {
n = (std::min)(n, size());
return {begin(), end()-n};
}
range_t without_front( std::size_t n = 1 ) const {
n = (std::min)(n, size());
return {begin()+n, end()};
}
decltype(auto) front() const { return *begin(); }
decltype(auto) back() const { return *(std::prev(end())); }
};
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。
关于c++ - 如何使我的自定义类型与 "range-based for loops"一起使用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8164567/