如何使我的自定义类型与“基于范围的 for 循环"一起使用? [英] How to make my custom type to work with "range-based for loops"?

查看:33
本文介绍了如何使我的自定义类型与“基于范围的 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() and X::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_exprend_expr 是调用 begin/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.

表达式范围已更新

{
  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 ;在 我必须更详细.

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) };
}

使用 模板类推导.

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 循环"一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆