How to allow range-for loop on my class? [duplicat

2020-02-06 06:48发布

I have a class like this:

class Foo {
private:
    int a,b,c,d;
    char bar;
    double m,n
public:
    //constructors here
};

I wanna allow range-for loop on my class, e.g.

Foo foo {/*...*/};
for(auto& f : foo) {
  //f will be a specific order such as c,b,d,(int)m,(int)bar,a,(int)n
}

How can I achieve this? I was looking at iterator but don't know what are the requirements for a range-for loop. (Please don't ask me to use array or STL type)

3条回答
Emotional °昔
2楼-- · 2020-02-06 07:01

Here is a basic framework I came up with:

#include <iterator>

struct Foo;

template<typename Type>
struct MemberPtrBase {
    virtual ~MemberPtrBase() { }

    virtual Type get() const = 0;
    virtual MemberPtrBase & set(Type const &) = 0;
};

template<typename Class, typename RealType, typename CommonType>
struct MemberPtr : MemberPtrBase<CommonType> {
public:
    MemberPtr(Class * object, RealType(Class::*member))
    : m_object(object), m_ptr(member)
    { }

    CommonType get() const {
        return m_object->*m_ptr;
    }

    MemberPtr & set(CommonType const & val) {
        m_object->*m_ptr = val;
        return *this;
    }

    MemberPtr & operator=(RealType const & val) {
        return set(val);
    }

    operator CommonType() const {
        return get();
    }
private:
    Class * m_object;
    RealType (Class::*m_ptr);
};

template<typename Class, typename... Types>
struct MemberIterator {
public:
    using CommonType = typename std::common_type<Types...>::type;
public:
    MemberIterator(Class & obj, std::size_t idx, Types(Class::*...member))
    : m_object(obj), m_index(idx), m_members { new MemberPtr<Class, Types, CommonType>(&obj, member)... }
    { }

    MemberPtrBase<CommonType> & operator*() const {
        return *m_members[m_index];
    }

    bool operator==(MemberIterator const & it) const {
        return (&m_object == &it.m_object) && (m_index == it.m_index);
    }

    bool operator!=(MemberIterator const & it) const {
        return (&m_object != &it.m_object) || (m_index != it.m_index);
    }

    MemberIterator & operator++() {
        ++m_index;
        return *this;
    }
private:
    Class & m_object;
    std::size_t m_index;
    MemberPtrBase<CommonType> * m_members[sizeof...(Types)];
};

struct Foo {
public:
    using iterator = MemberIterator<Foo, int, int, int, int>;
public:
    Foo(int a, int b, int c, int d)
    : m_a(a), m_b(b), m_c(c), m_d(d)
    { }

    iterator begin() {
        return iterator(*this, 0, &Foo::m_b, &Foo::m_d, &Foo::m_c, &Foo::m_a);
    }

    iterator end() {
        return iterator(*this, 4, &Foo::m_b, &Foo::m_d, &Foo::m_c, &Foo::m_a);
    }
private:
    int m_a, m_b, m_c, m_d;
};

If you have a basic understanding of variadic templates, I think the code is self-explanatory.

Usage is simple:

#include <iostream>
int main(int argc, char ** argv) {
    Foo foo { 1, 2, 3, 4 };

    for(auto & mem : foo) {
        std::cout << mem.get() << std::endl;
        mem.set(3);
    }

    for(auto & mem : foo) {
        std::cout << mem.get() << std::endl;
    }
}

A POC can be found on ideone

查看更多
够拽才男人
3楼-- · 2020-02-06 07:05

This seems fairly un-C++-like, and rather prone to breakage. What if the iteration order is changed (accidentally or not) during some update in the future? Clients relying on a specific order will break.

All that said if you wish to support this all you have to do is implement your own iterator and provide begin/end methods (or free functions with those names) to provide access. Then the iterator takes care of remembering which attribute it's currently looking at and provides it when dereferenced.

查看更多
别忘想泡老子
4楼-- · 2020-02-06 07:18

The loop is defined to be equivalent to:

for ( auto __begin = <begin-expr>,
           __end = <end-expr>;
      __begin != __end;
      ++__begin ) {
    auto& f = *__begin;
    // loop body
}

where <begin-expr> is foo.begin(), or begin(foo) if there isn't a suitable member function, and likewise for <end-expr>. (This is a simplification of the specification in C++11 6.5.4, for this particular case where the range is a lvalue of class type).

So you need to define an iterator type that supports pre-increment ++it, dereference *it and comparison i1 != i2; and either

  • give foo public member functions begin() and end(); or
  • define non-member functions begin(foo) and end(foo), in the same namespace as foo so that they can be found by argument-dependent lookup.
查看更多
登录 后发表回答