Mladen Janković
Mladen Janković

Reputation: 8045

Common code to iterate through different types of collections

Is there an elegant solution to use common code to iterate through hash_map/unordered_map and list/vector collections?

An example:

template<typename collection>
class multicast
{
public:
    typedef collection collection_type;

private:
    collection_type& m_channels;

public:
    multicast(collection_type& channels) : m_channels(channels) { }

    void operator ()(const buffer::ptr& head, const buffer::ptr& cnt)
    {
        for each(collection_type::value_type& ch in m_channels)
            ch->send(head, cnt); /* this is where the magic should happen? */
    }
}

This code obviously fails to compile when collection_type is unordered_map since collection_type::value_type is a pair so the code that access the actual value should be different: ch.second->send(head, cnt) instead of ch->send(head, cnt). So what would be the most elegant way to get rid of key part when it is not needed?

Upvotes: 1

Views: 1468

Answers (2)

Emilio Garavaglia
Emilio Garavaglia

Reputation: 20759

The problem is that list/vector contains just a value, while map-s contains a pair of key-value. They are not the same thing, and to iterate the same way you shold at least define what part of the pair you are interested in.

Once defined, you essentially need a "derefence" operation that accepts an iterator, and -in case it has as a value_type a pair, return the second element, oterwise just dereference it.

// default case, returning itself
template<class T>
T& get_val(T& t) { return t; } 

// case for pair (derefence for a map iterator)
template<class K, class V>
V& get_val(std::pair<const K, V>& s) { return s.second; }

// iterator dereference
template<class Iter>
decltype(get_val(*Iter()) deref_iter(const Iter& i)
{ return get_val(*i); }

Of course, const_iter version are also required, if needed.

Now:

for(auto i=container.begin(); i!=container-end(); ++i)
    do_something_with(deref_iter(i));

will be the same whatever the container.

Upvotes: 3

Kerrek SB
Kerrek SB

Reputation: 477408

Yes:

for (auto & x : collection) { do_stuff_with(x); }

Alternatively:

for (auto it = std::begin(collection), end = std::end(collection); it != end; ++it)
{
    do_stuff_with(*it);
}

If neither range-based for nor auto are available, you could write a template which takes a container C and use C::value_type and C::iterator; or you could make a template which accepts a pair of iterators of type Iter and uses std::iterator_traits<Iter>::value_type for the element value type.

Thirdly, you can use for_each and a lambda:

std::for_each(colllection.begin(), collection.end(),
              [](collection::value_type & x) { do_stuff_with(x); });


To accommodate for both single-element and pair-element containers, you can build a little wrapper:

template <typename T> struct get_value_impl
{
    typedef T value_type;
    static value_type & get(T & t) { return t; }
};
template <typename K, typename V> struct get_value_impl<std::pair<K, V>>
{
    typedef V value_type;
    static value_type & get(std::pair<K,V> & p) { return p.second; }
};
template <typename T>
typename get_value_impl<T>::value_type & get_value(T & t)
{
    return get_value_impl<T>::get(t);
}

Now you can use get_value(x) or get_value(*it) to get the value only.

Upvotes: 4

Related Questions