c++for-loopc++11range-based-loop

Custom class range based for-loop over a 2D map


I have the following class MyClass that contains a 2D map (std::map<std::string, std::map<std::string,double>>).

I would like to know if it is possible to implement the MyClass::begin() MyClass::end() functions for MyClass in order to have a range-based for loop (as in the code below) that would allow me to print all doubles contained in that 2D map.

To be clear I do not want to introduce double for loops, I would like a single for() loop
(The aim after is putting the map map as a private member of MyClass and only allow to loop over the class through that range-based for loop)

Many thanks in advance!


class MyClass {
public:
    MyClass(){};
    ~MyClass(){}; 
    std::map<std::string, std::map<std::string,double>> map = {};
};


int main()
{
    MyClass myClass; 
    myClass.map["a"]["a"] = 1;
    // ...
    myClass.map["e"]["c"] = 12;

    for (std::pair<const std::string, double> &obj : mycls){
        std::cout << "obj.second = " << obj.second << std::endl; 
  }
  return 0; 
}

Solution

  • Yes, It can be done. You must introduce iterator functionality.

    If you look in the CPP reference here, then you can see the requirements for the range based for loop in the explanation. Only 5 functions are needed.

    The implementation of a custom iterator is very simple.

    You write 5 using statements to fulfill formal requirements. Then, you need to define the internal representation of your iterator, for example a pointer or a other iterator.

    In the example below, we reuse the 2 iterators from the nested maps. An iterator to the outer map and an interator for the inner map. For easier implementation of functionality, we will also store a pointer to the surrounding custom class.

    The increment (and decrement operation) causes a little bit more effort, because we need to handle the wrap around of the inner map iterator.

    Example: If the inner iterator reaches the end, we must increment the outer iterator and then reset the inner iterator to the begin position.

    I adedd also a decrement function which faces similar challenges.

    By adding a difference function, I make the whole thing even sortable, by destroying the relation of the strings to the double. So, do not do that.

    But with that function, we can easily implement space ship like comparision of iterators.

    Please see one of many potential solutions below. I added some more functions for your convenience.

    #include<string> 
    #include<map> 
    #include<iostream>
    #include <iterator>
    #include <algorithm>
    #include <numeric>
    
    using Map = std::map<std::string, double>;
    using MapMap = std::map<std::string, Map>;
    
    struct MyClass {
        struct iterator; // Forward declaration
    
        MapMap mp{};
    
        bool empty() { return mp.empty(); }
        size_t size() { return std::accumulate(mp.begin(), mp.end(), 0u, [](const size_t& sum, const auto& m) { return sum + m.second.size(); }); }
    
        iterator begin() { return iterator(&mp, mp.begin()->second.begin(), mp.begin()); }
        iterator end() { return iterator(&mp, ((--mp.end())->second).end(), mp.end()); }
    
        // iterator stuff ---------------------------------------------------------------------------
        struct iterator {
            // Definitions ----------------
            using iterator_category = std::bidirectional_iterator_tag;
            using difference_type = std::ptrdiff_t;
            using value_type = double;
            using pointer = double*;
            using reference = double&;
    
            // Data
            MapMap* outerMapPtr{};               // Reference to surrounding nested map
            MapMap::iterator outerMapIterator{}; // Iterator to the outer part of the nesed map
            Map::iterator innerMapIterator{};    // Iterator to the outer part of the nesed map
    
            // Constructor for iterator. Take a pointer to the surrounding nested map and both iterators
            iterator(MapMap* const mmSD, const Map::iterator& msdIter, const MapMap::iterator& mmsdIter) :
                outerMapPtr(mmSD), innerMapIterator(msdIter), outerMapIterator(mmsdIter) {}
    
            // Dereferencing
            reference operator *() const { return innerMapIterator->second; }
            reference operator->() const { return **this; }
    
            // Comparison. Must be template, because the other iterator may be of different type
            template <typename Iter>
            bool operator != (const Iter& other) const { return(outerMapIterator != other.outerMapIterator) or (innerMapIterator != other.innerMapIterator); }
            template <typename Iter>
            bool operator == (const Iter& other) const { return(outerMapIterator == other.outerMapIterator) and (innerMapIterator == other.innerMapIterator); }
    
            bool operator < (const iterator& other) const { return other - *this < 0; };
            bool operator <= (const iterator& other) const { return other - *this <= 0; };
            bool operator > (const iterator& other) const { return other - *this > 0; };
            bool operator >= (const iterator& other) const { return other - *this >= 0; };
    
            // Arithmetic operations
            // A little bit complex
            iterator operator ++() {
                // If we are at the end with the outer iterator, we do nothing
                if (outerMapPtr->empty() or (outerMapIterator != outerMapPtr->end())) {
    
                    // We want to increment the inner iterator. Before we do that, we check, if this is already at the end
                    if (innerMapIterator != outerMapIterator->second.end())
                        ++innerMapIterator;
    
                    // So, now the innerMapIterator can be at the end, either from the beginning or after incrementing it
                    if (innerMapIterator == outerMapIterator->second.end()) {
    
                        // Increment outer iterator
                        ++outerMapIterator;
    
                        // And reset the inner interator back to begin, but only if the outer iterator is not at the end now
                        if (outerMapIterator != outerMapPtr->end())
                            innerMapIterator = outerMapIterator->second.begin();
                    }
                }
                return *this;
            }
            iterator operator --() {
                // No decrementation on empty container
                if (not outerMapPtr->empty()) {
    
                    // If we are at the end of the outer iterator then decrement it
                    if (outerMapIterator == outerMapPtr->end())
                        --outerMapIterator;
    
                    // If we are not at the begin the inner iterator
                    if (innerMapIterator != outerMapIterator->second.begin())
                        --innerMapIterator;
                    else {
                        // Inner iterator was at begin, therefore also decrement outer one
                        if (outerMapIterator != outerMapPtr->begin()) {
                            --outerMapIterator;
                            innerMapIterator = outerMapIterator->second.end();
                            if (innerMapIterator != outerMapIterator->second.begin())
                                --innerMapIterator;
                        }
                    }
                }
                return *this;
            }
    
            // Derived functions
            iterator operator++(int) { iterator tmp = *this; ++* this; return tmp; }
            iterator operator--(int) { iterator tmp = *this; --* this; return tmp; }
            iterator operator +(const difference_type& n) const {
                iterator temp{ *this };  difference_type k{ n }; if (k > 0) while (k--)++temp; else while (k++)--temp; return temp;
            }
            iterator operator +=(const difference_type& n) {
                difference_type k{ n }; if (k > 0) while (k--)++* this; else while (k++)--* this; return *this;
            };
            iterator operator -(const difference_type& n) const {
                iterator temp{ *this };  difference_type k{ n }; if (k > 0) while (k--)--temp; else while (k++)++temp; return temp;
            }
            iterator operator -=(const difference_type& n) {
                difference_type k{ n }; if (k > 0) while (k--)--* this; else while (k++)++* this; return *this;
            };
    
            // Difference. Very inefficient
            difference_type operator-(const iterator& other) const {
    
                difference_type result{};
                // No subtraction on empty container
                if (not outerMapPtr->empty()) {
    
                    int indexThis{ }, indexOther{ }, index{};
    
                    iterator current(outerMapPtr, outerMapPtr->begin()->second.begin(), outerMapPtr->begin());
                    iterator last(outerMapPtr, ((--outerMapPtr->end())->second).end(), outerMapPtr->end());
    
                    for (; current != last; ++current, ++index) {
                        if (current  == *this)
                            indexThis = index;
                        if (current == other)
                            indexOther = index;
                    }
                    if (current == *this)
                        indexThis = index;
                    if (current == other)
                        indexOther = index;
    
                    if (indexThis >= 0 and indexOther >= 0)
                        result = indexThis - indexOther;
                }
                return result;
            }
        };
    };
    
    int main()
    {
        MyClass mycls;
        mycls.mp["a"]["a"] = 1;
        mycls.mp["a"]["b"] = 2;
        mycls.mp["a"]["c"] = 3;
        mycls.mp["b"]["a"] = 4;
        mycls.mp["b"]["b"] = 5;
        mycls.mp["b"]["c"] = 6;
        mycls.mp["d"]["a"] = 7;
        mycls.mp["d"]["b"] = 8;
        mycls.mp["d"]["c"] = 9;
        mycls.mp["e"]["a"] = 10;
        mycls.mp["e"]["b"] = 11;
        mycls.mp["e"]["c"] = 12;
    
        std::cout << "\nSize: " << mycls.size() << "\n\n";
    
        for (double d : mycls)
            std::cout << d << ' ';
    
        return 0;
    }