13

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)

SwiftMango
  • 15,092
  • 13
  • 71
  • 136
  • 2
    You need an iterator type with `begin()` and `end()` exposure from your object class to enumerate the values in your (admittedly unusual) container. Perhaps an `std::array` would better suit your needs. – WhozCraig Aug 19 '13 at 19:01
  • 3
    You need a `begin` and `end` member function. – Rapptz Aug 19 '13 at 19:01
  • @Rapptz Is there any way to define how to iterate? begin and end in this case do not really apply... – SwiftMango Aug 19 '13 at 19:04
  • @texasbruce Since you're opting for not using an array type it'll be difficult to. – Rapptz Aug 19 '13 at 19:08
  • @Rapptz I can use an array, but the data types are not the same in the class... Plus I need a specific order to iterate, not just from beginning to end. If I use an array, I might need to re-arrange the array and construct a temporary array and output, which will reduce the performance.. – SwiftMango Aug 19 '13 at 19:10
  • @texasbruce: To iterate over elements at runtime with a for loop of any sort, the elements must be all of the same type. The closest you could come is a custom "for loop iteration" function with a functionoid callback. – Mooing Duck Aug 19 '13 at 19:15
  • @MooingDuck if you see the example, I cast them/ type conversion – SwiftMango Aug 19 '13 at 19:16
  • @MooingDuck In the comment inside the for loop? – SwiftMango Aug 19 '13 at 19:17

3 Answers3

15

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.
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 3
    So an iterator type with `begin()`, `end()`, `++`, `*` and `!=` ? – SwiftMango Aug 19 '13 at 19:13
  • Thanks.. lemme try it. Will post the final answer when it works – SwiftMango Aug 19 '13 at 19:18
  • @texasbruce: `begin()` and `end()` go on your container class, not your iterator. – cHao Aug 19 '13 at 19:24
  • [Global begin/end don't work](http://coliru.stacked-crooked.com/view?id=4b0aac7ff768cc17e80ed00a3b0f0b81-c96156d6cc95286981b0e9deef2eefae) because ordinary lookup is not being performed, only ADL with `std` as an associated namespace – TemplateRex Aug 19 '13 at 19:25
  • 2
    @TemplateRex: It seems you're right. I'd assumed the lookup would include the global namespace, but I guess it means stictly ADL only. – Mike Seymour Aug 19 '13 at 19:29
  • 1
    I wrote iterators http://coliru.stacked-crooked.com/view?id=48cd98af87c5135107c0841fde925c48-798d134e7828076b2f58250fd9f622f2 – Mooing Duck Aug 19 '13 at 19:29
  • BTW, there is [*core issue* 1442](http://wg21.cmeerw.net/cwg/issue1442) that changes the rules a little bit. Namespace `std` is no longer an associated namespace and [*it is explicitly mentioned that*](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3691.pdf) "begin and end are looked up in the associated namespaces (3.4.2). [ Note: Ordinary unqualified lookup (3.4.1) is not performed. — end note ]" – TemplateRex Aug 19 '13 at 19:51
  • Thanks it worked! begin and end return 2 iterators, and iterators have ++,!=,* operators (++ and * both require to return reference) – SwiftMango Aug 20 '13 at 02:59
1

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.

Mark B
  • 95,107
  • 10
  • 109
  • 188
1

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

Tom Knapen
  • 2,277
  • 16
  • 31