How to allow range-for loop on my class? [duplicate]
Asked Answered
T

3

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)

Twitch answered 19/8, 2013 at 18:58 Comment(8)
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<int,4> would better suit your needs.Rubalcava
You need a begin and end member function.Futurism
@Futurism Is there any way to define how to iterate? begin and end in this case do not really apply...Twitch
@texasbruce Since you're opting for not using an array type it'll be difficult to.Futurism
@Futurism 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..Twitch
@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.Biddick
@MooingDuck if you see the example, I cast them/ type conversionTwitch
@MooingDuck In the comment inside the for loop?Twitch
D
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.
Dyanne answered 19/8, 2013 at 19:10 Comment(8)
So an iterator type with begin(), end(), ++, * and != ?Twitch
Thanks.. lemme try it. Will post the final answer when it worksTwitch
@texasbruce: begin() and end() go on your container class, not your iterator.Lightner
Global begin/end don't work because ordinary lookup is not being performed, only ADL with std as an associated namespaceColdhearted
@TemplateRex: It seems you're right. I'd assumed the lookup would include the global namespace, but I guess it means stictly ADL only.Dyanne
I wrote iterators coliru.stacked-crooked.com/…Biddick
BTW, there is core issue 1442 that changes the rules a little bit. Namespace std is no longer an associated namespace and it is explicitly mentioned that "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 ]"Coldhearted
Thanks it worked! begin and end return 2 iterators, and iterators have ++,!=,* operators (++ and * both require to return reference)Twitch
O
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.

Overbold answered 19/8, 2013 at 19:11 Comment(0)
C
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

Chalcopyrite answered 19/8, 2013 at 19:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.