Reputation: 8045
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
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
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