4

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?
}
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
αλεχολυτ
  • 4,792
  • 1
  • 35
  • 71
  • @6502 added as tag `c++14` – αλεχολυτ Feb 28 '16 at 09:59
  • I don't know what `&& = delete` is supposed to mean – 6502 Feb 28 '16 at 10:09
  • 3
    @6502 Those are [ref-qualified member functions](http://stackoverflow.com/a/19474400/3410396) and [deleted functions](http://stackoverflow.com/questions/5513881/meaning-of-delete-after-function-declaration) – Revolver_Ocelot Feb 28 '16 at 10:10
  • @Revolver_Ocelot: Thanks, but I didn't say I wanted to know... – 6502 Feb 28 '16 at 10:14
  • 2
    @6502 : Then why say anything in the first place? – ildjarn Feb 28 '16 at 11:30
  • @ildjarn: I originally thought it was a typo, but unfortunately now I've seen the specs and I'm not able to unsee them. The more I understand about intricacies of move semantics the more I think it has been a bad idea adding it to C++. – 6502 Feb 28 '16 at 13:22

2 Answers2

3

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.

Community
  • 1
  • 1
Niall
  • 30,036
  • 10
  • 99
  • 142
  • Probably, I am not very well formulated the original question. The main thing that I wanted to know if it is important to implement the Iterator concept (let it be `ForwardIterator`) to provide the dereference operator for rvalue. If it's important, I would like to see an example, which will not compile because of the fact that an dereference operator declared as `= deleted`. At the same time, according to the Standard class meets the requirements of this type of iterator (e.g. `ForwardIterator`). – αλεχολυτ Mar 02 '16 at 13:27
  • *to provide the dereference operator for rvalue* - yes it is important (given the listed categories) to provide an operator suitable for an rvalue, in general this is the usual operator applied to both lvalues and rvalues; i.e. the operators are not ref-qualified at all (one for const and one for non-const). *If it's important, I would like to see an example* - as above `int j = *x++;` fails to compile. Another simple answer would be for the idiomatic `int i = *cont.begin();` The iterator returned by `begin()` is used here as an rvalue. – Niall Mar 02 '16 at 13:32
  • Are you sure about `int j = *x++`? [Live test works fine](http://melpon.org/wandbox/permlink/oLI9MIyFdt4d2545). – αλεχολυτ Mar 02 '16 at 13:36
  • @alexolut. Yes, here both clang++ and g++ fail; http://coliru.stacked-crooked.com/a/65b9041e63209e73. The code also fails at http://webcompiler.cloudapp.net (MSVC). – Niall Mar 02 '16 at 13:40
  • I see. The main difference is my code returns `X&` for `operator++(int)`. You version is more correct. Thanx. – αλεχολυτ Mar 02 '16 at 13:47
  • @alexolut. I just noticed that as well. You're right, the usual form for the post increment is [`T T::operator++(int);`](http://en.cppreference.com/w/cpp/language/operator_incdec) and it returns the "old" value of `T`. – Niall Mar 02 '16 at 13:50
  • Yet another small misunderstanding, if I return `T&` instead of `T` from `operator++(int)` is it violate `ForwardIterator` concept? – αλεχολυτ Mar 02 '16 at 14:09
  • @alexolut. Yes, it would violate it. The semantics of `*r++` is that it is equivalent to `{ T tmp = *r; ++r; return tmp; }` The old value is returned, hence `T` needs to be returned. – Niall Mar 02 '16 at 14:11
  • So if we consider to use local `static` variable (or another object that can live longer than auto local) for `tmp` and return `T&`? – αλεχολυτ Mar 02 '16 at 14:19
  • @alexolut. Umm, maybe, depends. Problem is what happens in situation when there are several valid expressions in a single statement; e.g. `*r1++ == *r2++` - that won't work the way you think it should. Or if the result is bound a rvalue ref `int&& j = *r++;`. You are scratching some dark corner cases, it may work, but not always. – Niall Mar 02 '16 at 14:37
  • @alexolut. I suppose in the end, if you understand the caveats/gotchas of your code and work accordingly, then ok, but I wouldn't expect other code to understand them, in particular generic type code (e.g. the standard library). – Niall Mar 02 '16 at 14:44
1

Well, the standard doesn't say anything about rvalue referenced iterators. And did not even recognize iterators whose members are overloaded by their reference types. But your code seems acceptable to be an iterator*, because in defining the requirements for an iterator...

Quoting section 24.2.2 of the latest C++14 standard draft, (emphasis are mine)

24.2.2.2: A type X satisfies the Iterator requirements if:

— X satisfies the CopyConstructible, CopyAssignable, and Destructible requirements (17.6.3.1) and lvalues of type X are swappable (17.6.3.2), and

— the expressions in Table 106 are valid and have the indicated semantics.

  • *r : r is dereferenceable

  • ++r : r is incrementable

Other than this, there is no extra constraint on iterator.


* Of cause, assuming the resulting X<typename ...> satisfies the first listed condition

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • 1
    it tells about requirement for lvalues to be swappable, and might not apply to other sentences. If we apply _"existence of exception supports general rule existence"_ to it, then all other requirements should apply to rvalues as well. – Revolver_Ocelot Feb 28 '16 at 10:14
  • I thought so too, but, I guess the OP is adding extra constraint to his `iterator` which I think should be permissible for his use case, after all, we sometimes overload functions based on reference types. But in the strict sense of his questions, the standard doesn't recognize iterators whose members are reference overloaded. I have modified my answer. – WhiZTiM Feb 28 '16 at 10:23
  • 3
    @Revolver_Ocelot ["r denotes a value of `X&`"](http://eel.is/c++draft/iterator.requirements#iterator.requirements.general-12), so it's an lvalue. However, just satisfying Iterator is useless; you'd want either InputIterator or OutputIterator, at least. And both of them require `*r++` to work. – T.C. Feb 28 '16 at 18:32
  • @T.C. Whether the 'operator*' is required has been defined for rvalue if we need to satisfy ForwardIterator? – αλεχολυτ Feb 29 '16 at 05:58