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