I have asked this question again (updated) at the suggestion of one of the StackOverflow members (see comment to this question: Traversing a bidirectional iterator back to the first element).
I have these aliases:
using SuggestedPublishersSet = std::set<CString>;
using SuggestedPublishersSetIter = SuggestedPublishersSet::iterator;
using SuggestedPublishersMap = std::map<long, SuggestedPublishersSet>;
using SuggestedPublishersMapIter = SuggestedPublishersMap::iterator;
And my class has these data members:
SuggestedPublishersMap m_mapSuggestedPublishers;
SuggestedPublishersMapIter m_mapSuggestedPublishersIter;
SuggestedPublishersSetIter m_setSuggestedPublishersIter;
My class is for a dialog and it has a button handler to let the user iterate forward sequentially through the container.
void CDemoPickerDlg::OnBnClickedButtonSuggest()
{
if (m_mapSuggestedPublishers.empty())
{
// Build the map first - snipped
// Begin iterators and select first suggested name
m_mapSuggestedPublishersIter = m_mapSuggestedPublishers.begin();
m_setSuggestedPublishersIter = m_mapSuggestedPublishersIter->second.begin();
}
else
{
// Find the next suggested name (if any)
while (m_mapSuggestedPublishersIter != m_mapSuggestedPublishers.end())
{
// Did we reach the last name in the current "set"?
if (m_setSuggestedPublishersIter == m_mapSuggestedPublishersIter->second.end())
{
// Yes, so we need to move to the next date and begin a new iterator
m_mapSuggestedPublishersIter++;
// Have we reached the end?
if (m_mapSuggestedPublishersIter == m_mapSuggestedPublishers.end())
{
// Reset iterators and select first suggested name
m_mapSuggestedPublishersIter = m_mapSuggestedPublishers.begin();
m_setSuggestedPublishersIter = m_mapSuggestedPublishersIter->second.begin();
}
else
{
// Start with first name of this next date
m_setSuggestedPublishersIter = m_mapSuggestedPublishersIter->second.begin();
}
break;
}
else
{
// Move to next name
m_setSuggestedPublishersIter++;
// Have we reached the last name?
if (m_setSuggestedPublishersIter == m_mapSuggestedPublishersIter->second.end())
{
// Yes, so we need to move to the next date and begin a new iterator
m_mapSuggestedPublishersIter++;
// Have we reached the end?
if (m_mapSuggestedPublishersIter == m_mapSuggestedPublishers.end())
{
// Reset iterators and select first suggested name
m_mapSuggestedPublishersIter = m_mapSuggestedPublishers.begin();
m_setSuggestedPublishersIter = m_mapSuggestedPublishersIter->second.begin();
}
else
{
// Start with first name of this next date
m_setSuggestedPublishersIter = m_mapSuggestedPublishersIter->second.begin();
}
}
break;
}
}
}
// Get the next publisher to use
const auto& strNextPublisher = m_setSuggestedPublishersIter->GetString();
// Iterate grid to find this publisher - snipped
}
This code supports forward iteration. Next I want to allow the user to click the button and iterate backwards through the container (do the same in reverse). Eg. we detect if CTRL is pressed when they click the button (I know how deleted the key press) then it should work backwards.
Perhaps the reason for your confusion is that iterator API is asymmetric: you have an end
iterator that doesn't point to any valid element, but begin
does point to a valid element. So forward and backward operations with boundary conditions need an asymmetry too. Reverse iterators are convenient when you need to flip this asymmetry -- to have an iterator pointing before the first element, but to have no iterator pointing after the last element.
You want to be able to cross container boundaries in both directions, so the simplest approach is to carefully consider what invariant you impose on your data members and not bother with reverse iterators. As you've already done with your forward iteration, the invariant is the following: set iterator always points to a valid string (between button clicks) that is displayed to the user; map iterator always points to a valid date-set pair; set iterator belongs to the set pointed to by the map iterator.
Next, special cases at boundaries should be considered. What should you do when user transitions to the first publisher for some date? Nothing special, it is displayed and your iterators refer to it. When user transitions backward from the first publisher for some date? You first find the previous date, update the map iterator, then find the last publisher for it and point the set iterator to it. Similarly special attention is needed when it is necessary to transition backward from the first date. It sounds verbose, but it's actually very short in code:
#include <iostream>
#include <map>
#include <set>
#include <cassert>
class SuggestedPublishers {
public:
using Set = std::set<std::string>;
using Map = std::map<long, Set>;
SuggestedPublishers(Map body)
: container{std::move(body)},
map_iter{},
set_iter{} {
assert(!container.empty());
for (auto& [key, value]: container) {
assert(!value.empty());
}
map_iter = container.begin();
set_iter = map_iter->second.begin();
}
void stepForward() {
++set_iter;
if (set_iter == map_iter->second.end()) {
// incremented set_iter doesn't point to a string
++map_iter;
if (map_iter == container.end()) {
// incremented map_iter doesn't point to a key-value pair
map_iter = container.begin();
// now map_iter does
}
set_iter = map_iter->second.begin();
// now set_iter does
}
}
void stepBackward() {
if (set_iter == map_iter->second.begin()) {
// set_iter can't be decremented
if (map_iter == container.begin()) {
// map_iter can't be decremented
map_iter = container.end();
// now map_iter can
}
--map_iter;
set_iter = map_iter->second.end();
// now set_iter can
}
--set_iter;
}
std::string getString() {
return *set_iter;
}
long getDate() {
return map_iter->first;
}
private:
Map container;
Map::iterator map_iter;
Set::iterator set_iter;
};
void interactionDemo() {
SuggestedPublishers suggestions{{
{1, {"a", "e", "i", "o"}},
{4, {"y"}},
{7, {"b", "d", "p", "q"}},
}};
std::cout << suggestions.getDate() << ' ' << suggestions.getString() << '\n';
std::string cmd;
while (std::cin >> cmd) {
assert(cmd[0] == "forward"[0] || cmd[0] == "backward"[0]);
if (cmd[0] == "forward"[0]) {
suggestions.stepForward();
} else {
suggestions.stepBackward();
}
std::cout << suggestions.getDate() << ' ' << suggestions.getString() << '\n';
}
}
int main() {
interactionDemo();
}