How to make my custom type to work with quot;range-based for loopsquot;?(如何使我的自定义类型与“基于范围的 for 循环一起使用?)
问题描述
像如今的许多人一样,我一直在尝试 C++11 带来的不同功能.我最喜欢的方法之一是基于范围的 for 循环".
Like many people these days I have been trying the different features that C++11 brings. One of my favorites is the "range-based for loops".
我明白:
for(Type& v : a) { ... }
相当于:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
对于标准容器,begin() 只返回 a.begin().
And that begin() simply returns a.begin() for standard containers.
但是如果我想让我的自定义类型基于范围的 for 循环"感知怎么办?
我应该专门研究 begin() 和 end() 吗?
Should I just specialize begin() and end()?
如果我的自定义类型属于命名空间 xml,我应该定义 xml::begin() 还是 std::begin()?
If my custom type belongs to the namespace xml, should I define xml::begin() or std::begin() ?
简而言之,这样做的指导方针是什么?
In short, what are the guidelines to do that?
推荐答案
自从问题(和大多数答案)发布后,标准已经改变 本缺陷报告的解决方案.
The standard has been changed since the question (and most answers) were posted in the resolution of this defect report.
使 for(:) 循环在您的类型 X 上工作的方法现在是以下两种方法之一:
The way to make a for(:) loop work on your type X is now one of two ways:
创建成员
X::begin()和X::end()返回一些类似于迭代器的东西
Create member
X::begin()andX::end()that return something that acts like an iterator
创建一个自由函数 begin(X&) 和 end(X&) 返回一些类似于迭代器的东西,在与你的相同的命名空间中输入 X.¹
Create a free function begin(X&) and end(X&) that return something that acts like an iterator, in the same namespace as your type X.¹
const 变体也类似.这将适用于实施缺陷报告更改的编译器和未实施的编译器.
And similar for const variations. This will work both on compilers that implement the defect report changes, and compilers that do not.
返回的对象实际上不必是迭代器.for(:) 循环与 C++ 标准的大多数部分不同,它是 指定扩展为等价于:
The objects returned do not have to actually be iterators. The for(:) loop, unlike most parts of the C++ standard, is 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的魔法code>/end.²
where the variables beginning with __ are for exposition only, and begin_expr and end_expr is the magic that calls begin/end.²
begin/end 返回值的要求很简单:必须重载 pre-++,确保初始化表达式有效,二进制 != 可以在布尔上下文中使用,一元 * 返回一些你可以分配初始化 range_declaration 的东西,并公开一个公共析构函数.
The requirements on the begin/end return value are simple: You must overload pre-++, ensure the initialization expressions are valid, binary != that can be used in a boolean context, unary * that returns something you can assign-initialize range_declaration with, and expose a public destructor.
以与迭代器不兼容的方式执行此操作可能是个坏主意,因为如果您这样做,C++ 的未来迭代可能会相对不屑一顾地破坏您的代码.
Doing so in a way that isn't compatible with an iterator is probably a bad idea, as future iterations of C++ might be relatively cavalier about breaking your code if you do.
顺便说一句,标准的未来修订版很可能允许 end_expr 返回与 begin_expr 不同的类型.这是有用的,因为它允许延迟结束".评估(如检测空终止),易于优化,与手写 C 循环一样高效,以及其他类似优点.
As an aside, it is reasonably likely that a future revision of the standard will permit end_expr to return a different type than begin_expr. This is useful in that it permits "lazy-end" evaluation (like detecting null-termination) that is easy to optimize to be as efficient as a hand-written C loop, and other similar advantages.
¹ 请注意,for(:) 循环将任何临时变量存储在 auto&& 变量中,并将其作为左值传递给您.您无法检测是否正在迭代临时(或其他右值);for(:) 循环不会调用这样的重载.参见 n4527 的 [stmt.ranged] 1.2-1.3.
¹ Note that for(:) loops store any temporary in an auto&& variable, and pass it to you as an lvalue. You cannot detect if you are iterating over a temporary (or other rvalue); such an overload will not be called by a for(:) loop. See [stmt.ranged] 1.2-1.3 from n4527.
² 要么调用 begin/end 方法,要么只用 ADL 查找自由函数 begin/end>、 或 C 风格数组支持的魔法.注意 std::begin 不会被调用,除非 range_expression 返回 namespace std 中类型的对象或依赖于相同的对象.
² Either call the begin/end method, or ADL-only lookup of free function begin/end, or magic for C-style array support. Note that std::begin is not called unless range_expression returns an object of type in namespace std or dependent on same.
在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 的类型已经解耦了.
with the types of __begin and __end have been decoupled.
这允许结束迭代器与开始的类型不同.您的最终迭代器类型可以是哨兵"类型.它只支持 != 和 begin 迭代器类型.
This permits the end iterator to not be the same type as begin. Your end iterator type can be a "sentinel" which only supports != with the begin iterator type.
为什么这很有用的一个实际例子是,您的最终迭代器可以读取检查您的 char* 以查看它是否指向 '0'";当 == 带有 char* 时.这允许 C++ range-for 表达式在迭代以空字符结尾的 char* 缓冲区时生成最佳代码.
A practical example of why this is useful is that your end iterator can read "check your char* to see if it points to '0'" when == with a char*. This allows a C++ range-for expression to generate optimal code when iterating over a null-terminated 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 << "
";
这是一个简单的例子.
Here is a simple example.
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); }
}
这是一个示例,您可以如何将无法控制的类型扩充为可迭代的.
this is an example how you can augment a type you don't control to be iterable.
这里我返回指针作为迭代器,隐藏了我在引擎盖下有一个向量的事实.
Here I return pointers-as-iterators, hiding the fact I have a vector under the hood.
对于您拥有的类型,您可以添加方法:
For a type you do own, you can add methods:
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 我必须更详细.
here I reuse the vector's iterators. I use auto for brevity; in c++11 I'd have to be more verbose.
这是一个快速而肮脏的可迭代范围视图:
Here is a quick and dirty iterable range-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 << "
";
}
打印 3 4 5,跳过第一个 2.
prints 3 4 5, skipping first 2.
这篇关于如何使我的自定义类型与“基于范围的 for 循环"一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:如何使我的自定义类型与“基于范围的 for 循环
- 将 hdc 内容复制到位图 2022-09-04
- 哪个更快:if (bool) 或 if(int)? 2022-01-01
- 从父 CMakeLists.txt 覆盖 CMake 中的默认选项(...)值 2021-01-01
- 使用 __stdcall & 调用 DLLVS2013 中的 GetProcAddress() 2021-01-01
- DoEvents 等效于 C++? 2021-01-01
- GDB 不显示函数名 2022-01-01
- XML Schema 到 C++ 类 2022-01-01
- 将函数的返回值分配给引用 C++? 2022-01-01
- OpenGL 对象的 RAII 包装器 2021-01-01
- 如何提取 __VA_ARGS__? 2022-01-01
