Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to iterate a class that overload the operator[]? [duplicate]

Tags:

c++

I have a class like this:

class C {
private:
    std::unordered_map<char, int> m = {{'a', 1}, {'b', 2}, {'c', 3}};

public:
    int operator[](const char& key) const {
        return m.find(key)->second;
    }
};

Is it possible to iterate the elements of the map without modifying the class?

I want to have something like:

for (auto x: c) {
    // x -> a map element
}
like image 564
Keloo Avatar asked Oct 18 '18 09:10

Keloo


3 Answers

No, a ranged-for loop does not use operator[]

The definition of

for ( range_declaration : range_expression ) loop_statement

Is that it is treated as-if it were (without introducing __names)

{
    auto && __range = range_expression ; 
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) {   
        range_declaration = *__begin; 
        loop_statement    
    } 
} 

With the following rules for begin_expr and end_expr:

  • If range_expression is an expression of array type, then begin_expr is __range and end_expr is (__range + __bound), where __bound is the number of elements in the array (if the array has unknown size or is of an incomplete type, the program is ill-formed)
  • If range_expression is an expression of a class type C that has both a member named begin and a member named end (regardless of the type or accessibility of such member), then begin_expr is __range.begin() and end_expr is __range.end();
  • Otherwise, begin_expr is begin(__range) and end_expr is end(__range), which are found via argument-dependent lookup (non-ADL lookup is not performed).

A simple change to your class which would suffice would to be add begin and end, delegating to m.begin() and m.end()

class C {
private:
    std::unordered_map<char, int> m = {{'a', 1}, {'b', 2}, {'c', 3}};

public:
    int operator[](const char& key) const {
        return m.find(key)->second; 
        // return m.at(key); or return m[key]; are both safer than this
    }
    std::unordered_map<char, int>::iterator begin() {
        return m.begin();
    }
    // overloaded on the constness of "this"
    std::unordered_map<char, int>::const_iterator begin() const {
        return m.begin();
    }
    std::unordered_map<char, int>::iterator end() {
        return m.end();
    }   
    // overloaded on the constness of "this"
    std::unordered_map<char, int>::const_iterator end() const {
        return m.end();
    }   
};
like image 71
Caleth Avatar answered Oct 31 '22 22:10

Caleth


No.

In addition to the constraints imposed specifically by ranged-for, which others have covered, your class does not expose any way to know which elements are in the map, not even the first or last element keys. It does not expose any iterators. It simply does not expose sufficient information to perform iteration.

like image 32
Lightness Races in Orbit Avatar answered Oct 31 '22 22:10

Lightness Races in Orbit


A class can be iterated if it provides iterators i.e. if begin(c) and end(c) return iterators (or c.begin() and c.end()). Your class doesn't provide iterators, and it won't be possible to implement the iterators outside of the class, since the internal map is private.

So, the answer is no, the class does need to be modified.

like image 26
eerorika Avatar answered Oct 31 '22 20:10

eerorika