range-based for loop for private map values

  • A+
Category:Languages

I have the following code:

#include "stdafx.h" #include <map> #include <string> #include <iostream>  class MyObject { public:     MyObject()         : m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }     {}      RETURNTYPE GetStringIterator() const     {         IMPLEMENTATION     }  private:     std::map<int, std::string> m_Items; };   int main() {     MyObject o;     for (auto& s : o.GetStringIterator())     {         std::cout << s;     } } 

What should RETURNTYPE and IMPLEMENTATION be in order to allow any client of MyObject (in this case the main() function), to iterate over the values of the m_Items map, without copying any data? It seems that this should be possible with c++11 range based for loops and iterators. but I have not been able to figure out how.

 


I'm going to first answer this in .

Here is a minimal mapping iteratoroid:

template<class F, class It> struct iterator_mapped {   decltype(auto) operator*() const {     return f(*it);   }    iterator_mapped( F f_in, It it_in ):     f(std::move(f_in)),     it(std::move(it_in))   {}    iterator_mapped( iterator_mapped const& ) = default;   iterator_mapped( iterator_mapped && ) = default;   iterator_mapped& operator=( iterator_mapped const& ) = default;   iterator_mapped& operator=( iterator_mapped && ) = default;    iterator_mapped& operator++() {     ++it;     return *this;   }   iterator_mapped operator++(int) {     auto copy = *this;     ++*this;     return copy;   }   friend bool operator==( iterator_mapped const& lhs, iterator_mapped const& rhs ) {     return lhs.it == rhs.it;   }   friend bool operator!=( iterator_mapped const& lhs, iterator_mapped const& rhs ) {     return !(lhs==rhs);   } private:   F f;   It it; }; 

it is not technically an iterator, but it qualifies for for(:) loops.

template<class It> struct range_t {   It b, e;   It begin() const { return b; }   It end() const { return e; } }; template<class It> range_t<It> range( It b, It e ) {   return {std::move(b), std::move(e)}; } 

the above is an absolutely minimal iterator range type that can be for(:) iterated.

template<class F, class R> auto map_range( F&& f, R& r ) {   using std::begin; using std::end;   auto b = begin(r);   auto e = end(r);   using it = iterator_mapped<std::decay_t<F>, decltype(b)>;   return range( it( f, b ), it( f, e ) ); } 

note that R& not R&&; taking an rvalue for r here is dangerous.

auto GetStringIterator() const {   return map_range( [](auto&& pair)->decltype(auto){     return pair.second;   }, m_Items ); } 

and done.

Converting this to is a pain. You have to toss around std::functions in place of lambdas (or write function objects that do the task instead of a lambda), replace decltype(auto) with auto and trailing return types, give the exact type of auto&& arguments to lambdas, etc. You end up with about 25%-50% more code, most of it obscure type chasing.

This is basically what boost::adaptors::map_values does, but this is hand-rolled so you can understand how it works and don't have a boost dependency.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: