简体   繁体   English

如何使我的自定义类型与“基于范围的 for 循环”一起使用?

[英]How to make my custom type to work with "range-based for loops"?

Like many people these days I have been trying the different features that C++11 brings.像现在的许多人一样,我一直在尝试 C++11 带来的不同功能。 One of my favorites is the "range-based for loops".我的最爱之一是“基于范围的 for 循环”。

I understand that:我明白那个:

for(Type& v : a) { ... }

Is equivalent to:相当于:

for(auto iv = begin(a); iv != end(a); ++iv)
{
  Type& v = *iv;
  ...
}

And that begin() simply returns a.begin() for standard containers.begin()只是为标准容器返回a.begin()

But what if I want to make my custom type "range-based for loop"-aware ?但是,如果我想让我的自定义类型“基于范围的 for 循环”感知呢?

Should I just specialize begin() and end() ?我应该只专注于begin()end()吗?

If my custom type belongs to the namespace xml , should I define xml::begin() or std::begin() ?如果我的自定义类型属于命名空间xml ,我应该定义xml::begin()还是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 .自从问题(和大多数答案)发布在此缺陷报告的解决方案中以来,标准已更改。

The way to make a for(:) loop work on your type X is now one of two ways:使for(:)循环在您的类型X上工作的方法现在是以下两种方法之一:

  • Create member X::begin() and X::end() that return something that acts like an iterator创建返回类似于迭代器的东西的成员X::begin()X::end()

  • 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创建一个自由函数begin(X&)end(X&) ,它返回类似于迭代器的东西,在与您的类型X相同的命名空间中。¹

And similar for const variations.const变化类似。 This will work both on compilers that implement the defect report changes, and compilers that do not.这将适用于实现缺陷报告更改的编译器和不实现缺陷报告更改的编译器。

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 :与 C++ 标准的大多数部分不同, for(:)循环被指定为扩展为等价于

for( range_declaration : range_expression )

becomes:变成:

{
  auto && __range = range_expression ;
  for (auto __begin = begin_expr,
            __end = end_expr;
            __begin != __end; ++__begin) {
    range_declaration = *__begin;
    loop_statement
  }
}

where the variables beginning with __ are for exposition only, and begin_expr and end_expr is the magic that calls begin / end其中以__开头的变量仅用于说明,而begin_exprend_expr是调用begin / end的魔法。²

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.对开始/结束返回值的要求很简单:您必须重载 pre- ++ ,确保初始化表达式有效,二进制!=可以在布尔上下文中使用,一元*返回可以分配的内容-初始化range_declaration并公开一个公共析构函数。

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.以与迭代器不兼容的方式这样做可能是一个坏主意,因为如果你这样做,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 .顺便说一句,标准的未来修订版很有可能允许end_expr返回与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.这很有用,因为它允许“延迟”评估(如检测空终止),易于优化,与手写 C 循环一样高效,以及其他类似优点。


¹ Note that for(:) loops store any temporary in an auto&& variable, and pass it to you as an lvalue. ¹ 请注意, for(:)循环将任何临时存储在auto&&变量中,并将其作为左值传递给您。 You cannot detect if you are iterating over a temporary (or other rvalue);您无法检测是否正在迭代临时(或其他右值); such an overload will not be called by a for(:) loop.这样的重载不会被for(:)循环调用。 See [stmt.ranged] 1.2-1.3 from n4527.参见 n4527 中的 [stmt.ranged] 1.2-1.3。

² Either call the begin / end method, or ADL-only lookup of free function begin / end , or magic for C-style array support. ² 调用begin / end方法,或自由函数begin / end的 ADL-only 查找,C 风格数组支持的魔法。 Note that std::begin is not called unless range_expression returns an object of type in namespace std or dependent on same.注意std::begin不会被调用,除非range_expression返回namespace std中类型的对象或依赖于相同的对象。


In the range-for expression has been updated中,range-for 表达式已更新

{
  auto && __range = range_expression ;
  auto __begin = begin_expr;
  auto __end = end_expr;
  for (;__begin != __end; ++__begin) {
    range_declaration = *__begin;
    loop_statement
  }
}

with the types of __begin and __end have been decoupled. __begin__end的类型已经解耦。

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.您的结束迭代器类型可以是一个“哨兵”,它只支持!=与开始迭代器类型。

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* .为什么这很有用的一个实际示例是,当==带有char*时,您的最终迭代器可以读取“检查您的char*以查看它是否指向'0' ”。 This allows a C++ range-for expression to generate optimal code when iterating over a null-terminated char* buffer.这允许 C++ range-for 表达式在迭代以 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;
  }
};

live example of this.活生生的例子

Minimal test code is:最小的测试代码是:

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";

Here is a simple example.这是一个简单的例子。

namespace library_ns {
  struct some_struct_you_do_not_control {
    std::vector<int> data;
  };
}

Your code:你的代码:

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

here I reuse the vector 's iterators.在这里,我重用了vector的迭代器。 I use auto for brevity;为简洁起见,我使用auto in 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(); } // 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 {
    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 {
    auto r=*this;
    while(n-->0 && !r.empty())
      r=r.without_front();
    return r;
  }
  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 template class deduction.使用模板类推导。

std::vector<int> v{1,2,3,4,5};
for (auto x : make_range(v).without_front(2) ) {
  std::cout << x << "\n";
}

prints 3 4 5, skipping first 2.打印 3 4 5,跳过前 2。

I write my answer because some people might be more happy with simple real life example without STL includes.我写下我的答案是因为有些人可能对没有 STL 包含的简单现实生活示例更满意。

I have my own plain only data array implementation for some reason, and I wanted to use the range based for loop.出于某种原因,我有自己的纯数据数组实现,我想使用基于范围的 for 循环。 Here is my solution:这是我的解决方案:

 template <typename DataType>
 class PodArray {
 public:
   class iterator {
   public:
     iterator(DataType * ptr): ptr(ptr){}
     iterator operator++() { ++ptr; return *this; }
     bool operator!=(const iterator & other) const { return ptr != other.ptr; }
     const DataType& operator*() const { return *ptr; }
   private:
     DataType* ptr;
   };
 private:
   unsigned len;
   DataType *val;
 public:
   iterator begin() const { return iterator(val); }
   iterator end() const { return iterator(val + len); }

   // rest of the container definition not related to the question ...
 };

Then the usage example:然后是用法示例:

PodArray<char> array;
// fill up array in some way
for(auto& c : array)
  printf("char: %c\n", c);

The relevant part of the standard is 6.5.4/1:标准的相关部分是6.5.4/1:

if _RangeT is a class type, the unqualified-ids begin and end are looked up in the scope of class _RangeT as if by class member access lookup (3.4.5), and if either (or both) finds at least one declaration, begin- expr and end-expr are __range.begin() and __range.end() , respectively;如果 _RangeT 是类类型,则在类 _RangeT 的范围内查找未限定的 ID 开始和结束,就像通过类成员访问查找 (3.4.5) 一样,如果其中一个(或两者)找到至少一个声明,开始- expr 和 end-expr 分别是__range.begin()__range.end()

— otherwise, begin-expr and end-expr are begin(__range) and end(__range) , respectively, where begin and end are looked up with argument-dependent lookup (3.4.2). — 否则, begin-expr 和 end-expr 分别是begin(__range)end(__range) ,其中 begin 和 end 使用参数相关查找 (3.4.2) 进行查找。 For the purposes of this name lookup, namespace std is an associated namespace.出于此名称查找的目的,命名空间 std 是一个关联的命名空间。

So, you can do any of the following:因此,您可以执行以下任何操作:

  • define begin and end member functions定义beginend成员函数
  • define begin and end free functions that will be found by ADL (simplified version: put them in the same namespace as the class)定义将由 ADL 找到的beginend自由函数(简化版:将它们放在与类相同的命名空间中)
  • specialize std::begin and std::end专门std::beginstd::end

std::begin calls the begin() member function anyway, so if you only implement one of the above, then the results should be the same no matter which one you choose. std::begin无论如何都会调用begin()成员函数,因此如果您只实现上述其中一项,那么无论您选择哪一项,结果都应该是相同的。 That's the same results for ranged-based for loops, and also the same result for mere mortal code that doesn't have its own magical name resolution rules so just does using std::begin;这与基于范围的 for 循环的结果相同,对于没有自己神奇的名称解析规则的普通代码也是相同的结果,因此只需using std::begin; followed by an unqualified call to begin(a) .随后是对begin(a)的无限制调用。

If you implement the member functions and the ADL functions, though, then range-based for loops should call the member functions, whereas mere mortals will call the ADL functions.但是,如果您实现成员函数ADL 函数,则基于范围的 for 循环应该调用成员函数,而普通人将调用 ADL 函数。 Best make sure they do the same thing in that case!最好确保他们在这种情况下做同样的事情!

If the thing you're writing implements the container interface, then it will have begin() and end() member functions already, which should be sufficient.如果您正在编写的东西实现了容器接口,那么它将已经具有begin()end()成员函数,这应该足够了。 If it's a range that isn't a container (which would be a good idea if it's immutable or if you don't know the size up front), you're free to choose.如果它是一个不是容器的范围(如果它是不可变的或者如果您不知道前面的大小,这将是一个好主意),您可以自由选择。

Of the options you lay out, note that you must not overload std::begin() .在您布置的选项中,请注意您不能重载std::begin() You are permitted to specialize standard templates for a user-defined type, but aside from that, adding definitions to namespace std is undefined behavior.您可以为用户定义的类型专门化标准模板,但除此之外,将定义添加到命名空间 std 是未定义的行为。 But anyway, specializing standard functions is a poor choice if only because the lack of partial function specialization means you can only do it for a single class, not for a class template.但是无论如何,专门化标准函数是一个糟糕的选择,因为缺少部分函数专门化意味着您只能为单个类而不是类模板这样做。

Should I just specialize begin() and end() ?我应该只专注于 begin() 和 end() 吗?

As far as I know, that is enough.据我所知,这就够了。 You also have to make sure that incrementing the pointer would get from the begin to the end.您还必须确保从头到尾递增指针。

Next example (it is missing const version of begin and end) compiles and works fine.下一个示例(缺少开始和结束的 const 版本)编译并且工作正常。

#include <iostream>
#include <algorithm>

int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }
    int * begin()
    {
        return &v[0];
    }
    int * end()
    {
        return &v[10];
    }

    int v[10];
};

int main()
{
    A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}

Here is another example with begin/end as functions.这是另一个使用 begin/end 作为函数的示例。 They have to be in the same namespace as the class, because of ADL :由于 ADL,它们必须与类在同一个命名空间中:

#include <iostream>
#include <algorithm>


namespace foo{
int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }

    int v[10];
};

int *begin( A &v )
{
    return &v.v[0];
}
int *end( A &v )
{
    return &v.v[10];
}
} // namespace foo

int main()
{
    foo::A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}

In case you want to back a class's iteration directly with its std::vector or std::map member, here is the code for that:如果你想直接用它的std::vectorstd::map成员支持一个类的迭代,这里是代码:

#include <iostream>
using std::cout;
using std::endl;
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <map>
using std::map;


/////////////////////////////////////////////////////
/// classes
/////////////////////////////////////////////////////

class VectorValues {
private:
    vector<int> v = vector<int>(10);

public:
    vector<int>::iterator begin(){
        return v.begin();
    }
    vector<int>::iterator end(){
        return v.end();
    }
    vector<int>::const_iterator begin() const {
        return v.begin();
    }
    vector<int>::const_iterator end() const {
        return v.end();
    }
};

class MapValues {
private:
    map<string,int> v;

public:
    map<string,int>::iterator begin(){
        return v.begin();
    }
    map<string,int>::iterator end(){
        return v.end();
    }
    map<string,int>::const_iterator begin() const {
        return v.begin();
    }
    map<string,int>::const_iterator end() const {
        return v.end();
    }

    const int& operator[](string key) const {
        return v.at(key);
    }
    int& operator[](string key) {
        return v[key];
    } 
};


/////////////////////////////////////////////////////
/// main
/////////////////////////////////////////////////////

int main() {
    // VectorValues
    VectorValues items;
    int i = 0;
    for(int& item : items) {
        item = i;
        i++;
    }
    for(int& item : items)
        cout << item << " ";
    cout << endl << endl;

    // MapValues
    MapValues m;
    m["a"] = 1;
    m["b"] = 2;
    m["c"] = 3;
    for(auto pair: m)
        cout << pair.first << " " << pair.second << endl;
}

Inspired by BitTickler's comment about how to make it work for non-"container" types, here's a minimal example of something that works for double s:受 BitTickler 关于如何使其适用于非“容器”类型的评论的启发,这里有一个适用于double的最小示例:

class dranged {
    double start, stop, step, cur;
    int index;

public:
    dranged(double start, double stop, double step) :
        start(start), stop(stop), step(step),
        cur(start), index(0) {}

    auto begin() { return *this; }
    auto end() { return *this; }

    double operator*() const { return cur; }

    auto& operator++() {
        index += 1;
        cur = start + step * index;
        return *this;
    }

    bool operator!=(const dranged &rhs) const {
        return cur < rhs.stop;
    }
};

Note that the use of < in the != operator maintains the correct invariant, but obviously assumes step is positive and wouldn't be appropriate everywhere a more general range would be.请注意,在!=运算符中使用<可以保持正确的不变量,但显然假定step是正数,并且不适用于更一般范围的任何地方。 I've used an integer index to prevent propagation of floating point error, but have aimed for simplicity otherwise.我使用了一个整数index来防止浮点错误的传播,但是为了简单起见。

This can be used as:这可以用作:

double sum() {
    double accum = 0;
    for (auto val : dranged(0, 6.28, 0.1)) {
        accum += val;
    }
    return accum;
}

GCC and Clang both produce very reasonable code when compiled with optimisations (ie either -Os or above -O1 for GCC or -O2 for Clang). GCC 和 Clang 在使用优化编译时都会产生非常合理的代码(即-Os或以上-O1用于 GCC 或-O2用于 Clang)。

Here, I am sharing the simplest example of creating custom type, that will work with " range-based for loop ":在这里,我将分享创建自定义类型的最简单示例,该示例将与“基于范围的 for 循环”一起使用:

#include<iostream>
using namespace std;

template<typename T, int sizeOfArray>
class MyCustomType
{
private:
    T *data;
    int indx;
public:
    MyCustomType(){
        data = new T[sizeOfArray];
        indx = -1;
    }
    ~MyCustomType(){
        delete []data;
    }
    void addData(T newVal){
        data[++indx] = newVal;
    }

    //write definition for begin() and end()
    //these two method will be used for "ranged based loop idiom"
    T* begin(){
        return &data[0];
    }
    T* end(){
        return  &data[sizeOfArray];
    }
};
int main()
{
    MyCustomType<double, 2> numberList;
    numberList.addData(20.25);
    numberList.addData(50.12);
    for(auto val: numberList){
        cout<<val<<endl;
    }
    return 0;
}

Hope, it will be helpful for some novice developer like me :p :)希望对像我这样的新手开发人员有所帮助:p :)
Thank You.谢谢你。

Chris Redford's answer also works for Qt containers (of course). Chris Redford 的回答也适用于 Qt 容器(当然)。 Here is an adaption (notice I return a constBegin() , respectively constEnd() from the const_iterator methods):这是一个改编(注意我从 const_iterator 方法分别返回一个constBegin()constEnd() ):

class MyCustomClass{
    QList<MyCustomDatatype> data_;
public:    
    // ctors,dtor, methods here...

    QList<MyCustomDatatype>::iterator begin() { return data_.begin(); }
    QList<MyCustomDatatype>::iterator end() { return data_.end(); }
    QList<MyCustomDatatype>::const_iterator begin() const{ return data_.constBegin(); }
    QList<MyCustomDatatype>::const_iterator end() const{ return data_.constEnd(); }
};

I would like to elaborate some parts of @Steve Jessop's answer, for which at first I didn't understand.我想详细说明@Steve Jessop 答案的某些部分,起初我不明白。 Hope it helps.希望能帮助到你。

std::begin calls the begin() member function anyway, so if you only implement one of the above, then the results should be the same no matter which one you choose. std::begin无论如何都会调用begin()成员函数,因此如果您只实现上述其中一项,那么无论您选择哪一项,结果都应该是相同的。 That's the same results for ranged-based for loops, and also the same result for mere mortal code that doesn't have its own magical name resolution rules so just does using std::begin;这与基于范围的 for 循环的结果相同,对于没有自己神奇的名称解析规则的普通代码也是相同的结果,因此只需using std::begin; followed by an unqualified call to begin(a) .随后是对begin(a)的无限制调用。

If you implement the member functions and the ADL functions , though, then range-based for loops should call the member functions, whereas mere mortals will call the ADL functions.但是,如果您实现了成员函数ADL 函数,那么基于范围的 for 循环应该调用成员函数,而普通人将调用 ADL 函数。 Best make sure they do the same thing in that case!最好确保他们在这种情况下做同样的事情!


https://en.cppreference.com/w/cpp/language/range-for : https://en.cppreference.com/w/cpp/language/range-for

  • If ...如果 ...
  • If range_expression is an expression of a class type C that has both a member named begin and a member named end (regardless of the type or accessibility of such member), then begin_expr is __range.begin( ) and end_expr is __range.end() ;如果range_expression是一个类类型C的表达式,它同时具有一个名为begin的成员和一个名为end的成员(无论该成员的类型或可访问性如何),那么begin_expr__range.begin( ) 而end_expr__range.end() ;
  • Otherwise, begin_expr is begin(__range) and end_expr is end(__range) , which are found via argument-dependent lookup (non-ADL lookup is not performed).否则, begin_exprbegin(__range)end_exprend(__range) ,它们是通过参数相关查找找到的(不执行非 ADL 查找)。

For range-based for loop, member functions are selected first.对于基于范围的 for 循环,首先选择成员函数。

But for但对于

using std::begin;
begin(instance);

ADL functions are selected first.首先选择 ADL 功能。


Example:例子:

#include <iostream>
#include <string>
using std::cout;
using std::endl;

namespace Foo{
    struct A{
        //member function version
        int* begin(){
            cout << "111";
            int* p = new int(3);  //leak I know, for simplicity
            return p;
        }
        int *end(){
            cout << "111";
            int* p = new int(4);
            return p;
        }
    };

    //ADL version

    int* begin(A a){
        cout << "222";
        int* p = new int(5);
        return p;
    }

    int* end(A a){
        cout << "222";
        int* p = new int(6);
        return p;
    }

}

int main(int argc, char *args[]){
//    Uncomment only one of two code sections below for each trial

//    Foo::A a;
//    using std::begin;
//    begin(a);  //ADL version are selected. If comment out ADL version, then member functions are called.


//      Foo::A a;
//      for(auto s: a){  //member functions are selected. If comment out member functions, then ADL are called.
//      }
}

I think I don't have anything to explain since the answers already do that.我想我没有什么要解释的,因为答案已经做到了。 But I maybe have to cite this quote from the standard (N4885):但我可能不得不引用标准(N4885)中的这句话:

[stmt.ranged]/1: (emphasise mine) [stmt.ranged]/1:(强调我的)

The range-based for statement基于范围的 for 语句

for ( init-statement(opt) for-range-declaration : for-range-initializer ) statement(possibly curly-braced)

is equivalent to:相当于:

 { // starts namespace scope of for-range-initializer   init-statement; (opt)   auto &&range = for-range-initializer ;   auto begin = begin-expr ;   auto end = end-expr ;  for ( ; begin != end; ++begin ) {      for-range-declaration = * begin ;     statement ;  } } // ends namespace scope of for-range-initializer

where在哪里

(1.1) if the for-range-initializer is an expression, it is regarded as if it were surrounded by parentheses (so that a comma operator cannot be reinterpreted as delimiting two init-declarators); (1.1) 如果 for-range-initializer 是一个表达式,则认为它好像被括号包围(这样逗号运算符就不能重新解释为分隔两个 init-declarators);

(1.2) range, begin, and end are variables defined for exposition only; (1.2) range、begin 和 end 是仅为说明而定义的变量; and

(3.1) begin-expr and end-expr are determined as follows: (3.1) begin-expr 和 end-expr 确定如下:

(1.3.1) if the for-range-initializer is an expression of array type R, begin-expr and end-expr are range and range+N, respectively, where N is the array bound. (1.3.1) 如果 for-range-initializer 是数组类型 R 的表达式,则 begin-expr 和 end-expr 分别是 range 和 range+N,其中 N 是数组边界。 If R is an array of unknown bound or an array of incomplete type, the program is ill-formed;如果 R 是一个未知边界的数组或一个不完整类型的数组,则程序是非良构的;

(1.3.2) if the for-range-initializer is an expression of class type C, and [class.member.lookup] in the scope of C for the names begin and end each find at least one declaration, begin-expr and end-expr are range.begin() and range.end(), respectively; (1.3.2) 如果 for-range-initializer 是类类型 C 的表达式,并且 [class.member.lookup] 在 C 的范围内为名称 begin 和 end 每个找到至少一个声明,begin-expr 和end-expr 分别是 range.begin() 和 range.end();

(1.3.3) otherwise, begin-expr and end-expr are begin(range) and end(range), respectively, where begin and end undergo argument-dependent lookup ([basic.lookup.argdep]). (1.3.3) 否则,begin-expr 和 end-expr 分别是 begin(range) 和 end(range),其中 begin 和 end 进行参数相关查找 ([basic.lookup.argdep])。


Note that strings, arrays, and all STL containers are iterable data structures, so they can be iterated over with the range-based for-loop already.请注意,字符串、数组和所有 STL 容器都是可迭代的数据结构,因此它们已经可以使用基于范围的 for 循环进行迭代。 In order to make a data structure iterable, it must work similarly to the existing STL iterators:为了使数据结构可迭代,它必须类似于现有的 STL 迭代器:

1- There must be begin and end methods that operate on that structure, either as members or as stand-alone functions, and that return iterators to the beginning and end of the structure. 1- 必须有beginend方法对该结构进行操作,无论是作为成员还是作为独立函数,并将迭代器返回到结构的开头和结尾。

2- The iterator itself must support an operator*() method, an operator !=() method, and an operator++(void) method, either as members or as stand-alone functions. 2- 迭代器本身必须支持operator*()方法、 operator !=()方法和operator++(void)方法,无论是作为成员还是作为独立函数。


#include <iostream>
#include <vector>
#define print(me) std::cout << me << std::endl

template <class T>
struct iterator
{
    iterator(T* ptr) : m_ptr(ptr) {};
    bool operator!=(const iterator& end) const { return (m_ptr != end.m_ptr); }
    T operator*() const { return *m_ptr; }
    const iterator& operator++()
    {
        ++m_ptr;
        return *this;
    }

private:
    T* m_ptr;
};

template <class T, size_t N>
struct array
{
    typedef iterator<T> iterator;

    array(std::initializer_list<T> lst)
    {

        m_ptr = new T[N]{};
        std::copy(lst.begin(), lst.end(), m_ptr);
    };

    iterator begin() const { return iterator(m_ptr); }
    iterator end() const { return iterator(m_ptr + N); }

    ~array() { delete[] m_ptr; }

private:
    T* m_ptr;
};

int main()
{
    array<std::vector<std::string>, 2> str_vec{ {"First", "Second"}, {"Third", "Fourth"} };
    for(auto&& ref : str_vec)
        for (size_t i{}; i != ref.size(); i++) 
            print(ref.at(i));

      //auto &&range = str_vec;
      //auto begin = range.begin();
      //auto end = range.end();
      //for (; begin != end; ++begin)
      //{
         // auto&& ref = *begin;
         // for (size_t i{}; i != ref.size(); i++) 
         //     print(ref.at(i));
      //}
}

The output of this program is:这个程序的输出是:

First Second Third Fourth第一第二第三第四

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM