2

Many posts about const iterators (example), but none in the context of loops like:

for (const auto it: container) { ... }

When I started implementing, I was encouraged by the compiler's complaints about missing begin and end, hoping such complaints can guide me to fill in the missing pieces. I was wrong. The following code compiles fine though it surely lacks the equivalent of both operator++ and operator!=:

#include <iostream>
template<class T> class List { public:
    const T *head;
    const List<T> *tail;
    List(const T *h, const List<T> *t):head(h),tail(t){}
    const T *operator*() const { return head; } };

template<class T> const List<T> *begin(const List<T> *l) { return l; }
template<class T> const List<T> *end  (const List<T> *l) { return nullptr; }

class Person { public: int age; Person(int a):age(a){} };
typedef List<Person> Persons;

int main(int argc, char **argv) {
    Person *p1 = new Person(16);
    Person *p2 = new Person(27);
    Person *p3 = new Person(38);

    Persons *persons = new Persons(p1,
        new Persons(p2,
        new Persons(p3, nullptr)));

    for (const auto p: persons) { std::cout << (*p)->age << "\n"; }
    return 0; }

How come this code compiles?

OrenIshShalom
  • 5,974
  • 9
  • 37
  • 87
  • there is no iterators here. and range for loop is just a shorthand syntax for a normal for-loop – Swift - Friday Pie Dec 30 '21 at 06:46
  • `++` and `!=` are defined for all pointer types – Mat Dec 30 '21 at 06:49
  • It's not obvious to me why `persons`, a `Persons*` pointer, is being treated as a valid range expression. The only [case](https://en.cppreference.com/w/cpp/language/range-for#Explanation) that looks like it might be able to apply is treating it as an array type whose size isn't known. – Nathan Pierson Dec 30 '21 at 06:50
  • 1
    It’s because begin() and end() functions were defined that accept List* as their argument. – jtbandes Dec 30 '21 at 07:03

1 Answers1

4

You have not defined ++ and !=, but they are perfectly well-defined for your iterator type, const List<T>*, because is a pointer type. However, the default behavior of ++ does not do what you want, which would be to follow the tail pointer.

To imbue your iterator with special knowledge of how ++ should be implemented, you would want to use a separate, custom iterator class, rather than using a pointer.

I also made some other changes to your code:

  • The List type is made iterable, not List*. That means the begin and end functions are not defined on pointers, but on the List object itself, and we iterate over *persons rather than persons.
  • In for (const auto p : *persons), p is a Person, not a pointer.

godbolt.org link

#include <iostream>
template<class T> class List { public:
    const T *head;
    const List<T> *tail;
    List(const T *h, const List<T> *t):head(h),tail(t){}
    const T *operator*() const { return head; }

    class const_iterator {
        const List<T>* cur = nullptr;
    public:
        explicit const_iterator(const List<T>* list) : cur(list) {}
        const_iterator& operator++() {
            cur = cur->tail;
            return *this;
        }
        bool operator!=(const const_iterator& other) const {
            return cur != other.cur;
        }
        const T& operator*() const {
            return *cur->head;
        }
    };

    const_iterator begin() const {
        return const_iterator(this);
    }

    const_iterator end() const {
        return const_iterator(nullptr);
    }
};

class Person { public: int age; Person(int a):age(a){} };
typedef List<Person> Persons;

int main(int argc, char **argv) {
    Person *p1 = new Person(16);
    Person *p2 = new Person(27);
    Person *p3 = new Person(38);

    Persons *persons = new Persons(p1,
        new Persons(p2,
        new Persons(p3, nullptr)));

    for (const auto p: *persons) {
        std::cout << p.age << "\n";
    }
    return 0;
} 
jtbandes
  • 115,675
  • 35
  • 233
  • 266