Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom container range-based iteration

Tags:

c++

containers

I have a custom container which I want to use in a ranged-based for loop. The container is somewhat based on a vector, like this:

template<typename T>
class IDMap
{
private:
    struct Item {
        uint16_t mVersion;
        T mItem;

        template <typename... Arguments>
        Item(uint16_t version, Arguments&&... args) : mVersion(version), mItem(args...)
        {
        }
    };


public:
    typedef uint32_t ItemID;

    template <typename... Arguments>
    ItemID AddItem(Arguments&&... args);

    void MarkAsFree(const ItemID id);

    T& GetItem(const ItemID id);

    T* TryGetItem(const ItemID id);

    void Clear();


private:
    std::vector<Item> mItems;
    std::vector<uint16_t> mFreeIndices;
};

I want to iterate the mItems vector, but only return the mItem member rather than the entire Item struct. Is there any easy/elegant way to do this?

like image 427
KaiserJohaan Avatar asked Mar 03 '15 12:03

KaiserJohaan


1 Answers

You have to provide a begin and end function, both returning a corresponding iterator, which itself implements operators ++, != and *. The begin and end functions can be free-standing or as members.

Start with implementing an iterator which has the behavior you want. You can implement it as a wrapper around a std::vector::iterator to save most of the "core" work.

The following is untested code

Basically, inside class IDMap, add:

class ItemIterator {
    // based on vector iterator
    std::vector<Item>::iterator i;
public:
    ItemIterator(std::vector<Item>::iterator i) : i(i) {}

    // incrementing
    ItemIterator & operator ++() { ++i; return *this; }
    ItemIterator operator ++(int) { const_iterator old(*this); ++(*this); return old; }

    // comparison
    bool operator!=(const ItemIterator &o) const { return i != o.i; }

    // dereferencing
    const T & operator*() const { return i->mItem; }
};

using iterator = ItemIterator;
using value_type = T;

ItemIterator begin() const { return ItemIterator(mItems.begin()); }
ItemIterator end()   const { return ItemIterator(mItems.end()  ); }

If you ever want to support multiple kinds of "special iteration" over your IDMap, like for example also over the indices, or over the "whole" Items, you should wrap everything above in another adaptor. This adaptor can then be accessed with a member method, like .items().

Brief example:

class IDMap {
    // (your code)

public:
    struct ItemsAdaptor {
        // (insert above iterator definition + usings)

        ItemsAdaptor(std::vector<Item>::iterator b,
                     std::vector<Item>::iterator e)
            : b{b}, e{e}
        {}

        ItemIterator begin() const { return b; }
        ItemIterator end()   const { return e; }

    private:
        ItemIterator b, e;
    };

    ItemsAdaptor items() const {
        return ItemsAdaptor(mItems.begin(), mItems.end());
    }
};

Then, you can write:

IDMap<int> map = ...;

for (int i : map.items()) {
    ...
}
like image 94
leemes Avatar answered Nov 07 '22 01:11

leemes