Search code examples
c++iteratorlanguage-lawyerc++14

Does my class satisfy the requirements of an iterator?


Consider the following class:

template <class T>
struct X {
    T& operator*() & { return t; }
    T& operator*() && = delete; 
    X& operator++() { return *this; }
    T t;
};

Does this class satisfy requirements of Iterator concept by the C++ Standard? Object of this class is incrementable and dereferenceable. But dereference of rvalue object is forbidden.

int main() {
    X<int> x;
    *x; // ok
    *X<int>(); // fail. But is it really necessary for Iterator by Standard?
}

Solution

  • With a strict reading of the standard; [iterator.iterators]/2 (§24.2.2/2) does hint at the type X qualifying as an iterator;

    ...a and b denote values of type X or const X

    ...r denotes a value of X&...

    A type X satisfies the Iterator requirements if:

    • X satisfies the CopyConstructible, CopyAssignable, and Destructible requirements ([utility.arg.requirements]) and lvalues of type X are swappable ([swappable.requirements]), and

    • the expressions in Table (below) are valid and have the indicated semantics.

      • *r (r is dereferenceable)
      • ++r (returns X&)

    Given the code;

    template <class T>
    struct X {
        T& operator*() & { return t; }
        T& operator*() && = delete; 
        X& operator++() { return *this; }
        T t;
    };
    int main()
    {
        X<int> x;
        ++x;
        int i = *x;
        X<int> y(x);
        using std::swap;
        std::swap(x, y);
    }
    

    Certainly does seem to satisfy those requirements.

    However, the story continues, the Iterator concept, as defined above, is not listed as one of the Iterator Categories in the standard §24.2.1/2;

    This International Standard defines five categories of iterators, according to the operations defined on them: input iterators, output iterators, forward iterators, bidirectional iterators and random access iterators...

    They all define an operation *a and *r++ for which the type X fails to compile;

    int j = *x++; // fails to compile even with the inclusion of the post increment operator
    const X<int> xc {};
    int ic = *x1; // no const overloads
    

    For an iterator to be usable within one of the defined categories, it needs to include further members for de-referencing const values, lvalues and rvalues, post increment etc. In the spirit of the standard;

    Iterators are a generalization of pointers that allow a C++ program to work with different data structures (containers) in a uniform manner.

    The guidance here for the overloaded members and operators is that they can/should be added to enforce compliance and optimise (if possible) the implementation of the semantics of the generalised pointer - not to disallow the semantics; limiting the semantics could have unintended consequences.