iterator_facade and InputIterators

The Standard's table of InputIterator requirements (table 72 in section 24.1) says that, amongst other things, an InputIterator must support the following two expressions: *a Convertible to T, the iterator's value_type a->m Equivalent to (*a).m The same language is present in the "New Iterator Concepts", N1550, documentation in the definition of ReadableIterator. In particular, InputIterators are allowed to, and often do, have operator* returning by value: template <typename T> class my_iterator { public: typedef T value_type; T operator*() const; // ... }; Because it is legal to call non-const member functions on temporary objects, it is legal to call non-const member functions on the value returned from operator*: struct foo { void non_const(); }; my_iterator<foo> i; (*i).non_const(); My reading of Table 72 documentation suggests that this means that for my_iterator<foo> to be a Readable Iterator, it must also be legal to write: i->non_const(); If so, this means that operator-> should return T* instead of T const* (possibly via a proxy class). In the current implementation of boost::iterator_facade, operator-> returns a proxy that acts as a T const*: template <class T> struct operator_arrow_proxy { operator_arrow_proxy(T const* px) : m_value(*px) {} const T* operator->() const { return &m_value; } operator const T*() const { return &m_value; } T m_value; }; Is there a reason not to change this to have operator-> returning a T*? That would seem closer to the defintion of an InputIterator. (I supposed that if operator* returns by const value, it would be necessary to resurrect the constness in the proxy, but assuming the iterator's reference typedef is also a const-qualified value, this should be easy enough.) I had a look at the current concept-based iterator proposal, N2039, to see whether that shed any light on this and it also seems to require this behaviour from an InputIterator. It formal concept only requires operator->'s return to be convertible to T const* (which T*, or a proxy to T*, is): concept InputIterator<typename X> { where /*...*/ Convertible<pointer, value_type const*>; pointer operator->(X); // } And in it's definition of operator->, it requires (*a).m to be well-formed and then defines a->m to be equivalent to it. Have I misunderstood some aspect of this, or does the Standard really all require that a generic input iterator adaptor, such as boost::iterator_adaptor (and by impliciation boost::iterator_facade) should support this? -- Richard Smith

Richard Smith <richard@ex-parrot.com> writes:
The Standard's table of InputIterator requirements (table 72 in section 24.1) says that, amongst other things, an InputIterator must support the following two expressions:
*a Convertible to T, the iterator's value_type
a->m Equivalent to (*a).m
The same language is present in the "New Iterator Concepts", N1550, documentation in the definition of ReadableIterator.
In particular, InputIterators are allowed to, and often do, have operator* returning by value:
template <typename T> class my_iterator { public: typedef T value_type; T operator*() const; // ... };
Yes.
Because it is legal to call non-const member functions on temporary objects,
Only if they're non-const.
it is legal to call non-const member functions on the value returned from operator*:
struct foo { void non_const(); };
my_iterator<foo> i; (*i).non_const();
No. The value returned from operator* might be a const proxy with a (const) conversion operator to value_type.
My reading of Table 72 documentation suggests that this means that for my_iterator<foo> to be a Readable Iterator,
Table 72 doesn't describe Readable Iterator.
it must also be legal to write:
i->non_const();
All Table 72 says about operator-> is a->m precondition: (*a).m is well-defined Equivalent to (*a).m
If so, this means that operator-> should return T* instead of T const* (possibly via a proxy class).
If operator* returns value_type, I think I agree that operator-> should return T* or an equivalent proxy pointer.
In the current implementation of boost::iterator_facade, operator-> returns a proxy that acts as a T const*:
template <class T> struct operator_arrow_proxy { operator_arrow_proxy(T const* px) : m_value(*px) {} const T* operator->() const { return &m_value; } operator const T*() const { return &m_value; } T m_value; };
Is there a reason not to change this to have operator-> returning a T*? That would seem closer to the defintion of an InputIterator.
I agree with you in principle. I think there's something missing in our concept checks that this wasn't picked up.
(I supposed that if operator* returns by const value, it would be necessary to resurrect the constness in the proxy, but assuming the iterator's reference typedef is also a const-qualified value, this should be easy enough.)
I had a look at the current concept-based iterator proposal, N2039, to see whether that shed any light on this and it also seems to require this behaviour from an InputIterator. It formal concept only requires operator->'s return to be convertible to T const* (which T*, or a proxy to T*, is):
concept InputIterator<typename X> { where /*...*/ Convertible<pointer, value_type const*>; pointer operator->(X); // }
And in it's definition of operator->, it requires (*a).m to be well-formed and then defines a->m to be equivalent to it.
Have I misunderstood some aspect of this, or does the Standard really all require that a generic input iterator adaptor, such as boost::iterator_adaptor (and by impliciation boost::iterator_facade) should support this?
I think you're right. Please enter a bug report at SourceForge so this doesn't get lost. Thanks for the report, Dave -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
Richard Smith <richard@ex-parrot.com> writes:
[...]
it is legal to call non-const member functions on the value returned from operator*:
struct foo { void non_const(); };
my_iterator<foo> i; (*i).non_const();
No. The value returned from operator* might be a const proxy with a (const) conversion operator to value_type.
That's an interesting point. If operator* returns a proxy type, as operator. cannot be overloaded, (*a).m will always call a member of the proxy type, so for a->m to be equivalent, the operator-> should return (probably by proxy) a pointer to the operator* proxy type. I.e. template <typename T> struct it { typedef T value_type; struct value_proxy { operator value_type() const; }; typedef const value_proxy reference; reference operator*() const; struct arrow_proxy { reference* operator->() const; }; arrow_proxy operator->() const; }; So assuming the reference typedef is precisely the return type of operator* (including top-level constness), if it is a real reference, we don't need an arrow_proxy, and if it's a value, arrow_proxy should always act as reference*, even when the reference typedef is itself a proxy. Does that sound right? (The only examples of proxy types returned from operator* that I can think of are things like vector<bool>::reference, where value_type has no members and so operator-> is irrelevant. I imagine in the absense of an overloadable operator., that's all you can do.)
My reading of Table 72 documentation suggests that this means that for my_iterator<foo> to be a Readable Iterator,
Table 72 doesn't describe Readable Iterator.
Sorry, I meant Input Iterator or the equivalent table in the "New Iterator Concepts" paper for Readable Iterator.
it must also be legal to write:
i->non_const();
All Table 72 says about operator-> is
a->m precondition: (*a).m is well-defined Equivalent to (*a).m
Yes. I was meaning where i was still the specific my_iterator<foo> defined earlier, in which case I think it does imply that. But I think you're agreeing with me below.
If so, this means that operator-> should return T* instead of T const* (possibly via a proxy class).
If operator* returns value_type, I think I agree that operator-> should return T* or an equivalent proxy pointer.
I think more generally (as discussed above), if operator* returns any cv-qualified non-reference type, T, that operator-> should return T* or an equivalent proxy pointer.
Please enter a bug report at SourceForge so this doesn't get lost.
Will do. Thanks, Richard

Richard Smith <richard@ex-parrot.com> writes:
David Abrahams wrote:
Richard Smith <richard@ex-parrot.com> writes:
[...]
it is legal to call non-const member functions on the value returned from operator*:
struct foo { void non_const(); };
my_iterator<foo> i; (*i).non_const();
No. The value returned from operator* might be a const proxy with a (const) conversion operator to value_type.
That's an interesting point. If operator* returns a proxy type, as operator. cannot be overloaded, (*a).m will always call a member of the proxy type, so for a->m to be equivalent, the operator-> should return (probably by proxy) a pointer to the operator* proxy type.
No, we only have to make the proxy's members inaccessible, and then the precondition for supplying operator-> goes away and we can make it do anything reasonable. -- Dave Abrahams Boost Consulting www.boost-consulting.com
participants (2)
-
David Abrahams
-
Richard Smith