Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible in C++ to do std::map<> "for element : container" iteration with named variables (eg, key and value) instead of .first and .second?

I wasn't sure what to search for. I found Renaming first and second of a map iterator but it's not quite what I want to do.

Here's what I'd like to do [see below for nonsense C++ code]. Is something close to this possible? Otherwise will just have to go with the option of "adapting" the iterator as the first line inside the loop I suppose.

// what I want to do: std::map<int, std::string> my_map; // ... populate my_map for(auto key, auto & value: my_map){     // do something with integer key and string value } 

C++11 is fine, but rather avoid boost if possible.

The closest I've gotten is

// TODO, can this be templated? struct KeyVal{     int & id;     std::string & info;      template <typename P>     KeyVal(P & p)         : id(p.first)         , info(p.second)     {     } };  //... for ( KeyVal kv : my_map ){     std::cout << kv.info; } 

But that means writing an adapter class for each map :(

// slightly joke answer/"what could possibly go wrong?" #define key first #define value second 
like image 202
JamEnergy Avatar asked Apr 07 '16 15:04

JamEnergy


People also ask

Can you iterate through a map C++?

By creating an iterator of std::map and initializing it to the starting of map and visiting upto the end of map we can successfully iterate over all the elements of map.

Does MAP return an iterator?

map() loops over the items of an input iterable (or iterables) and returns an iterator that results from applying a transformation function to every item in the original input iterable.


2 Answers

An approach inspired by Barry below would be to write a range adapter.

Doing this without boost or similar library support is a pain, but:

  1. Write a range template. It stores 2 class iterators and has begin() and end() methods (and whatever else you want).

  2. Write a transforming iterator adapter. It takes an iterator, and wraps it up so that its value type is transformed by some function object F.

  3. Write a to_kv transformer that takes a std::pair<K, V> cv& and returns a struct kv_t { K cv& key; V cv& value; }.

  4. Wire 3 into 2 into 1 and call it as_kv. It takes a range of pairs, and returns a range of key-values.

The syntax you end up with is:

std::map<int, std::string> m;  for (auto kv : as_kv(m)) {   std::cout << kv.key << "->" << kv.value << "\n"; } 

which is nice.

Here is a minimalist solution that doesn't actually create legal iterators, but does support for(:):

template<class Key, class Value> struct kv_t {   Key&& key;   Value&& value; };  // not a true iterator, but good enough for for(:) template<class Key, class Value, class It> struct kv_adapter {   It it;   void operator++(){ ++it; }   kv_t<Key const, Value> operator*() {     return {it->first, it->second};   }   friend bool operator!=(kv_adapter const& lhs, kv_adapter const& rhs) {     return lhs.it != rhs.it;   } }; template<class It, class Container> struct range_trick_t {   Container container;   range_trick_t(Container&&c):     container(std::forward<Container>(c))   {}   It begin() { return {container.begin()}; }   It end() { return {container.end()}; } }; template<class Map> auto as_kv( Map&& m ) {   using std::begin;   using iterator = decltype(begin(m)); // no extra (())s   using key_type = decltype((begin(m)->first)); // extra (())s on purpose   using mapped_type = decltype((begin(m)->second)); // extra (())s on purpose   using R=range_trick_t<     kv_adapter<key_type, mapped_type, iterator>,     Map   >;   return R{std::forward<Map>(m)}; } std::map<int, std::string> m() { return {{0, "Hello"}, {2, "World"}}; } 

which is very minimal, but works. I would not generally encourage this kind of half-assed pseudo iterators for for(:) loops; using real iterators is only a modest additional cost, and doesn't surprise people later on.

live example

(Now with temporary map support. Does not support flat C arrays ... yet)

The range-trick stores a container (possibly a reference) in order to copy temporary containers into the object stored for the duration of the for(:) loop. Non-temporary containers the Container type is a Foo& of some kind, so it doesn't make a redundant copy.

On the other hand, kv_t obviously only stores references. There could be a strange case of iterators returning temporaries that break this kv_t implementation, but I'm uncertain how to avoid it in general without sacrificing performance in more common cases.


If you don't like the kv. part of the above, we can do some solutions, but they aren't as clean.

template<class Map> struct for_map_t {   Map&& loop;   template<class F>   void operator->*(F&& f)&&{     for (auto&& x:loop) {       f( decltype(x)(x).first, decltype(x)(x).second );     }   } }; template<class Map> for_map_t<Map> map_for( Map&& map ) { return {std::forward<Map>(map)}; } 

then:

map_for(m)->*[&](auto key, auto& value) {   std::cout << key << (value += " ") << '\n'; }; 

close enough?

live example

There are some proposals around first-class tuples (and hence pairs) that may give you something like that, but I do not know the status of the proposals.

The syntax you may end up if this gets into C++ would look something like this:

for( auto&& [key, value] : container ) 

Comments on the ->* abomination above:

So the ->* is being used sort of as an operator bind from Haskell (together with implicit tuple unpacking), and we are feeding it a lambda that takes the data contained within the map and returns void. The (Haskell-esque) return type becomes a map over void (nothing), which I elide into void.

The technique has a problem: you lose break; and continue; which suck.

A less hackey Haskell-inspired variant would expect the lambda to return something like void | std::experimental::expected<break_t|continue_t, T>, and if T is void return nothing, if T is a tuple-type return a map, and if T is a map join the returned map-type. It would also either unpack or not unpack the contained tuple depending on what the lambda wants (SFINAE-style detection).

But that is a bit much for a SO answer; this digression points out that the above style of programming isn't a complete dead-end. It is unconventional in C++ however.

like image 145
Yakk - Adam Nevraumont Avatar answered Sep 21 '22 00:09

Yakk - Adam Nevraumont


You could write a class template:

template <class K, class T> struct MapElem {     K const& key;     T& value;      MapElem(std::pair<K const, T>& pair)         : key(pair.first)         , value(pair.second)     { } }; 

with the advantage of being able to write key and value but with the disadvantage of having to specify the types:

for ( MapElem<int, std::string> kv : my_map ){     std::cout << kv.key << " --> " << kv.value; } 

And that won't work if my_map were const either. You'd have to do something like:

template <class K, class T> struct MapElem {     K const& key;     T& value;      MapElem(std::pair<K const, T>& pair)         : key(pair.first)         , value(pair.second)     { }      MapElem(const std::pair<K const, std::remove_const_t<T>>& pair)         : key(pair.first)         , value(pair.second)     { } };  for ( MapElem<int, const std::string> kv : my_map ){     std::cout << kv.key << " --> " << kv.value; } 

It's a mess. Best thing for now is to just get used to writing .first and .second and hope that the structured bindings proposal passes, which would allow for what you really want:

for (auto&& [key, value] : my_map) {     std::cout << key << " --> " << value; } 
like image 44
Barry Avatar answered Sep 18 '22 00:09

Barry