0

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; 
}
AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
romain.bqt4
  • 159
  • 1
  • 6
  • 2
    The short answer is yes, it's possible. – sweenish Feb 07 '22 at 18:01
  • @sweenish thanks would you know how I can implement it ? If I am asking it is because I do not know how to implement it Thanks in advance – romain.bqt4 Feb 07 '22 at 18:05
  • 1
    You need to provide iterators for your class. They'll likely just be wrappers for the map iterators. – sweenish Feb 07 '22 at 18:07
  • 1
    This is a semi-advanced topic. We could either give you a ready implementation or a tutorial in an answer. Both aren't suitable to be StackOverflow answers. Do you know how to implement a custom iterator? If yes, then what *exactly* is your question? If no, then the first step would be learning how to do so by searching for a tutorial on that topic. – Fureeish Feb 07 '22 at 18:07
  • @Fureeish if I had a 1D map or 1D vector I know how to "implement" that it would be just mp.begin() and mp.end() But for the 2D (or more generally multidimensionnal case) I have no idea how it can be implemented And before asking the question I did not even had an idea if it was possible to be implemented or no. I may not understand things just because I am not aware of such feature exists So from your answer I first need to understand how to implement my own iterator and once I have the iterator implemted I can define the begin end functions ? Am I right ? Thanks – romain.bqt4 Feb 07 '22 at 18:16
  • Yes, you are exactly right :) – Fureeish Feb 07 '22 at 18:26
  • @Fureeish thanks again !! – romain.bqt4 Feb 07 '22 at 18:27

1 Answers1

2

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.

  • Your class must have a begin() function that returns a custom iterator

  • Your class must have a end() function that returns a custom iterator

  • Your class must have a custom iterator and a function for dereferencing it

  • Your class must have a custom iterator and a function for incrementing it

  • Your class must have a custom iterator and a function for comparison

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;
}

A M
  • 14,694
  • 5
  • 19
  • 44