像许多人一样,这些天我一直在尝试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() ?

简而言之,这样做的指导方针是什么?


当前回答

我应该专门化begin()和end()吗?

据我所知,这就足够了。您还必须确保对指针进行递增可以从开始到结束。

下一个示例(它缺少begin和end的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;
    }
}

下面是另一个使用begin/end作为函数的例子。它们必须和类在同一个命名空间中,因为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;
    }
}

其他回答

如果你想直接用std::vector或std::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;
}

我应该专门化begin()和end()吗?

据我所知,这就足够了。您还必须确保对指针进行递增可以从开始到结束。

下一个示例(它缺少begin和end的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;
    }
}

下面是另一个使用begin/end作为函数的例子。它们必须和类在同一个命名空间中,因为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;
    }
}

受到BitTickler关于如何使它适用于非“容器”类型的评论的启发,这里有一个适用于双精度对象的最小示例:

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

注意,在!=操作符中使用<保持了正确的不变量,但显然假设step是正的,并且不适用于更一般的范围。我使用整数索引来防止浮点错误的传播,但在其他方面力求简单。

这可以用于:

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

GCC和Clang在进行优化编译时都能生成非常合理的代码(例如,GCC的-Os或高于-O1, Clang的-O2)。

标准的相关部分为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; — 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). For the purposes of this name lookup, namespace std is an associated namespace.

所以,你可以做以下任何一件事:

定义开始和结束成员函数 定义ADL可以找到的开始和结束自由函数(简化版本:将它们放在与类相同的命名空间中) 专门化std::begin和std::end

Std::begin调用begin()成员函数,所以如果你只实现了上面的一个,那么无论你选择哪一个,结果都应该是一样的。这对于基于范围的for循环也是一样的结果,对于没有自己神奇的名称解析规则的普通代码也是一样的结果,所以只使用std::begin;接着是开始(a)的不合格调用。

如果你实现了成员函数和ADL函数,那么基于范围的for循环应该调用成员函数,而凡人将调用ADL函数。最好确保他们在这种情况下做同样的事情!

如果您正在编写的东西实现了容器接口,那么它将已经有begin()和end()成员函数,这应该足够了。如果它是一个不是容器的范围(如果它是不可变的,或者如果你不知道前面的大小,这将是一个好主意),你可以自由选择。

对于所布局的选项,请注意不能重载std::begin()。允许为用户定义的类型专门化标准模板,但除此之外,向名称空间std添加定义是未定义的行为。但无论如何,专门化标准函数是一个糟糕的选择,因为缺乏部分函数专门化意味着您只能为单个类而不是类模板这样做。

我想我没有什么要解释的,因为答案已经说明了这一点。但我可能不得不引用标准(N4885)中的这句话:

(支撑。[远程]/1:(强调我的)

The range-based for statement 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.2) range, begin, and end are variables defined for exposition only; and (3.1) begin-expr and end-expr are determined as follows: (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. If R is an array of unknown bound or an array of incomplete type, the program is ill-formed; (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.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]).


请注意,字符串、数组和所有STL容器都是可迭代的数据结构,因此已经可以使用基于范围的for循环对它们进行迭代。为了使数据结构可迭代,它必须类似于现有的STL迭代器:

1-必须有begin和end方法对该结构进行操作,可以作为成员,也可以作为独立函数,并且返回结构的开始和结束的迭代器。

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

这个程序的输出是:

第一个 第二个 第三 第四