Iterate over enums set in a QFlags value



  • Hi all,

    I've seen a certain pattern emerge in some places of my code lately. It goes approximately like this:
    @
    enum myEnum {e1 = 0x1
    ,e2 = 0x2
    ,e3 = 0x4};
    Q_DECLARE_FLAGS(myEnumFlags, myEnum)

    void someFunction(myEnumFlags p)
    {
    QVector<myEnum> flagsInP;
    if (p.testFlag(e1)) flagsInP.append(e1);
    if (p.testFlag(e2)) flagsInP.append(e2);
    if (p.testFlag(e3)) flagsInP.append(e3);
    for (int i=0; i<flagsInP.size(); ++i)
    {
    // do something with flagsInP.at(i)
    }
    }
    @

    Now in my opinion this isn't very elegant, since a QFlags is already a container in some sense and there must be some elegant way to iterate its set flags, i.e. retrieving the set enum values iteration by iteration.

    So when someFunction(e1|e2) is called, I'd like to loop over {e1, e2}.

    Any Ideas?



  • That is one of my questionares in learning Qt and c++.
    I've done similar thing to meet your requirement using Qt's meta object and property system.
    Key points for me to know what kind of flag values are available and iterate over them.

    Take a look(modified to look like your enum decl) and hope this helps

    @
    #include <QtCore>
    #include <QDebug>

    class MyData : public QObject
    {
    Q_OBJECT
    public:
    enum myEnum {
    e1 = 0x1,
    e2 = 0x2,
    e3 = 0x4,
    e4 = 0x8,
    };
    Q_DECLARE_FLAGS(myEnumFlags, myEnum)
    Q_FLAGS(myEnum myEnumFlags)

    void test()
    {
    qDebug() << "testing enum inside of decl";
    myEnumFlags value = e1 | e4;
    QMetaEnum me = MyData::staticMetaObject.enumerator(0);
    for (int i=0; i<me.keyCount(); ++i) {
    if (value.testFlag((myEnum)me.value(i))) {
    qDebug() << "flag is on over " << me.key(i);
    }
    }
    }
    protected:
    };

    int main(int argc, char** argv)
    {
    qDebug() << "testing enum outside of class";
    {
    MyData::myEnumFlags value = MyData::e1 | MyData::e4;
    QMetaEnum me = MyData::staticMetaObject.enumerator(0);
    for (int i=0; i<me.keyCount(); ++i) {
    if (value.testFlag((MyData::myEnum)me.value(i))) {
    qDebug() << "flag is on over " << me.key(i);
    }
    }
    }
    MyData tester;
    tester.test();
    }

    #include "main.moc"
    @

    and the result (tested /w visual studio 2008) ...

    @
    testing enum outside of class
    flag is on over e1
    flag is on over e4
    testing enum inside of decl
    flag is on over e1
    flag is on over e4
    계속하려면 아무 키나 누르십시오 . . .
    @

    PS: I always am very shy to answer the question whose poster level is higher than me. :-o


  • Moderators

    While working fine, QMetaEnum is not very comfy because you have to embed your enum inside a QObject derived class and then instantiate that class to get to the meta object. Things complicate even more if you have more than one enum inside that class, since you can no longer do just MyData::staticMetaObject.enumerator(0), but have to find the correct index.

    A somewhat different, not without its drawback of course, solution is to use this "java style" flag iterator class:
    @
    template <typename T, typename U>
    class FlagIterator
    {
    public:
    FlagIterator(T& flags) : mFlags((unsigned)flags), mFlag(0) {}
    inline U value() { return static_cast<U>(mFlag); }
    inline bool hasNext() { return mFlags > mFlag; }
    void next() { if(mFlag == 0) mFlag = 1; else mFlag <<= 1;
    while((mFlags & mFlag) == 0) mFlag <<= 1; mFlags &= ~mFlag; }
    private:
    unsigned mFlags;
    unsigned mFlag;
    };
    @
    Having that and your enum declared with Q_DECLARE_FLAGS(SomeFlags, SomeFlag), you can do this:
    @
    FlagIterator<SomeFlags, SomeFlag> fi(f);
    while(fi.hasNext()) {
    fi.next();
    //do somethig with fi.value()
    }
    @



  • Looks nice and neat. Though i'm not OP, but it helps. thanks.



  • [quote author="Krzysztof Kawa" date="1358385940"]While working fine, QMetaEnum is not very comfy because you have to embed your enum inside a QObject derived class and then instantiate that class to get to the meta object. Things complicate even more if you have more than one enum inside that class, since you can no longer do just MyData::staticMetaObject.enumerator(0), but have to find the correct index.[/quote]On a sidenote: You actually don't have to.

    You can use Q_GADGET instead of Q_OBJECT for enumerations, which does not depend on QObject.
    @
    class SomeClass
    {
    Q_GADGET
    Q_ENUMS(SomeEnum)

    public:
    enum SomeEnum
    {
    Value1 = 0x1,
    Value2 = 0x2,
    Value3 = 0x4
    };
    };

    int someEnumIndex = SomeClass::staticMetaObject.indexOfEnumerator("SomeEnum");
    QMetaEnum someEnum = SomeClass::staticMetaObject.enumerator(someEnumIndex);
    @


  • Moderators

    Good point. Q_GADGET is a blind spot of mine. No matter how many times I've seen it I always forget about it :)
    As for the index - again, good point, but I personally avoid using "string references". I know it's not uncommon in Qt world, but the problem is - if one day someone changes the name of the enum you won't get a nice compiler error, just a -1 index which you might not catch right away, and it's still more monkey typing than iterator :P

    But again - there are pros and cons to both solutions.



  • Thanks for all your ideas.
    I'm aware of Qt's meta system and QMetaEnum, but somehow I find that unelegant too, especially the string indexing, as Krzysztof said. And the code looks hacky. I treat qt's meta-object system always as a last resort.

    The double-templated Iterator idea is pretty neat, didn't think of that. Produces very beautiful code (and is itself as beautiful as a double-template can be ;). I guess I'll use that sooner or later. They should include it in Qt, if you ask me, so I don't have to include it in my library, where it doesn't belong.

    [quote author="joonhwan" date="1358382382"]I always am very shy to answer the question whose poster level is higher than me. [/quote] Naah. Don't respect people for the post-count next to their name. There's no proportionality to the IQ (unfortunately).


  • Moderators

    [quote author="DerManu" date="1358458466"]as beautiful as a double-template can be ;)[/quote]
    Well, since enums are pretty much ints anyway (in most, the simplest, cases) you could probably go ahead with one template param:
    @
    template <typename T>
    class FlagIterator
    {
    public:
    FlagIterator(unsigned flags) : mFlags(flags), mFlag(0) {}
    inline T value() { return static_cast<T>(mFlag); }
    inline bool hasNext() { return mFlags > mFlag; }
    void next() { if(mFlag == 0) mFlag = 1; else mFlag <<= 1;
    while((mFlags & mFlag) == 0) mFlag <<= 1; mFlags &= ~mFlag; }
    private:
    unsigned mFlags;
    unsigned mFlag;
    };
    @
    or, if you don't really care about types and don't mind casting in user code, with no templates at all:
    @
    inline unsigned value() { return mFlag; }
    @



  • Here is my subclass of QFlags with iterator over flags. Improvements welcome.
    Sorry it's not quite readable here.

    @/**

    • @brief custom QFlags class

    • QFlags with iterator and methods to search next set flag
      /
      template <typename T>
      class qtvFlags : public QFlags<T>
      {
      public:
      /
      TODO:

        • check that *this != 0
        • register QMetaEnum here
          */
          qtvFlags(const QFlags<T> & other) : QFlags<T>(other) {}
          qtvFlags(T e) : QFlags<T>(e) {}
          qtvFlags(QFlag value) : QFlags<T>(value) {}

      /**

      • @brief Search nearest set flag

      • @param flag enum value to start search from

      • @return next set flag found

      • Iterate to the next set bit in the bit-set, nearest to the passed one while

      • searching forward

      • inline?
        */
        const T next(unsigned flag = 1) const {
        if (!flag) flag = 1;
        else flag <<= 1;
        if (flag > *this || !flag) return (T)0; // check !m in case max enum flag set or *this==0
        // consider: 'm > *this | !m' to eliminate branching

        while ( !( *this & flag ) ) flag <<= 1;

        return static_cast<T>(flag);
        }

      /**

      • @brief Circular search nearest set flag

      • @param flag enum value to start search from

      • @param forward search direction

      • @return next set flag found

      • Iterate to the next set bit in the bit-set, nearest to the passed one while

      • searching by circle in the specified direction

      • *this must not be 0

      • inline?
        */
        const T circularNext(unsigned flag = 1, const bool forward = true) const {
        if (!flag) flag = 1;
        const unsigned shift = forward ? sizeof(flag) * CHAR_BIT - 1 : 1;

        /* GCC usually recognizes certain circular shift expressions to introduce "rotate" instruction on CPUs that support it.

        • It was checked GCC does it here too at least for x86 and ARM.
        • ARM has only ROR, so use right shift expression here.
          */
          while ( !( *this & (flag = ( flag >> shift | flag << (sizeof(flag) * CHAR_BIT - shift) )) ) );

        return static_cast<T>(flag);
        }

    class const_iterator : public std::iterator<std::forward_iterator_tag, const T> {
        const qtvFlags<T>* /*const */i;     // const avoiding gives us copy assignable class by default
        unsigned int m;
    
    public:
        inline const_iterator(const qtvFlags<T>* const p) : i(p), m(0) {}
    
        inline const T& operator*() const { return static_cast<const T>(m); }
    
        inline bool operator==(const const_iterator &o) const { return m == o.m /*&& i == o.i*/; }
        inline bool operator!=(const const_iterator &o) const { return m != o.m /*|| i != o.i*/; }
        inline const_iterator& operator++() { m = i->next(m); return *this;}
        inline const_iterator operator++(int) { const const_iterator n(*this); m = i->next(m); return n; }
    };
    
    const_iterator begin() const { return ++const_iterator(this);}
    const_iterator end() const { return const_iterator(this); }
    

    };@


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.