forked from boostorg/iterator
major revision, narrowed the focus of the paper
[SVN r20826]
This commit is contained in:
429
doc/issues.rst
429
doc/issues.rst
@ -1,6 +1,6 @@
|
|||||||
+++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
Issues With N1550_ and N1530_
|
Problem with ``is_writable`` and ``is_swappable`` in N1550_
|
||||||
+++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
.. _N1550: http://www.boost-consulting.com/writing/n1550.html
|
.. _N1550: http://www.boost-consulting.com/writing/n1550.html
|
||||||
.. _N1530: http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1530.html
|
.. _N1530: http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1530.html
|
||||||
@ -22,285 +22,168 @@
|
|||||||
Introduction
|
Introduction
|
||||||
==============
|
==============
|
||||||
|
|
||||||
Several issues with N1550_ (New Iterator Concepts) were raised in
|
The ``is_writable`` and ``is_swappable`` traits classes in N1550_
|
||||||
the run-up before the fall 2003 C++ Committee meeting, in a thread
|
provide a mechanism for determining at compile time if a type is a
|
||||||
beginning with John Maddock's posting ``c++std-lib-12187``. In
|
model of the new Writable Iterator and Swappable Iterator concepts,
|
||||||
looking at those issues, several other problems came up. This
|
analogous to ``iterator_traits<X>::iterator_category`` for the old
|
||||||
document addresses those issues and discusses some potential
|
iterator concepts. For backward compatibility, ``is_writable`` and
|
||||||
solutions and their impact on N1530_ (Iterator Facade and Adaptor).
|
``is_swappable`` not only work with new iterators, but they also are
|
||||||
|
intended to work for old iterators (iterators that meet the
|
||||||
|
requirements for one of the iterator concepts in the current
|
||||||
|
standard). In the case of old iterators, the writability and
|
||||||
|
swapability is deduced based on the ``iterator_category`` and also the
|
||||||
|
``reference`` type. The specification for this deduction gives false
|
||||||
|
positives for forward iterators that have non-assignable value types.
|
||||||
|
|
||||||
============
|
To review, the part of the ``is_writable`` trait definition which
|
||||||
The Issues
|
applies to old iterators is::
|
||||||
============
|
|
||||||
|
|
||||||
Non-Uniformity of the "``lvalue_iterator`` Bit"
|
|
||||||
===============================================
|
|
||||||
|
|
||||||
The proposed ``iterator_tag`` class template accepts an "access
|
|
||||||
bits" parameter which includes a bit to indicate the iterator's
|
|
||||||
*lvalueness* (whether its dereference operator returns a reference
|
|
||||||
to its ``value_type``. The relevant part of N1550_ says:
|
|
||||||
|
|
||||||
The purpose of the ``lvalue_iterator`` part of the
|
|
||||||
``iterator_access`` enum is to communicate to ``iterator_tag``
|
|
||||||
whether the reference type is an lvalue so that the appropriate
|
|
||||||
old category can be chosen for the base class. The
|
|
||||||
``lvalue_iterator`` bit is not recorded in the
|
|
||||||
``iterator_tag::access`` data member.
|
|
||||||
|
|
||||||
The ``lvalue_iterator`` bit is not recorded because N1550_ aims to
|
|
||||||
improve orthogonality of the iterator concepts, and a new-style
|
|
||||||
iterator's lvalueness is detectable by examining its ``reference``
|
|
||||||
type. This inside/outside difference is awkward and confusing.
|
|
||||||
|
|
||||||
Redundancy of Some Explicit Access Category Flags
|
|
||||||
=================================================
|
|
||||||
|
|
||||||
Shortly after N1550_ was accepted, we discovered that an iterator's
|
|
||||||
lvalueness can be determined knowing only its ``value_type``. This
|
|
||||||
predicate can be calculated even for old-style iterators (on whose
|
|
||||||
``reference`` type the standard places few requirements). A trait
|
|
||||||
in the Boost iterator library does it by relying on the compiler's
|
|
||||||
unwillingness to bind an rvalue to a ``T&`` function template
|
|
||||||
parameter. Similarly, it is possible to detect an iterator's
|
|
||||||
readability knowing only its ``value_type``. Thus, any interface
|
|
||||||
which asks the *user* to explicitly describe an iterator's
|
|
||||||
lvalue-ness or readability seems to introduce needless complexity.
|
|
||||||
|
|
||||||
New Access Traits Templates Wrong For Some Iterators
|
|
||||||
====================================================
|
|
||||||
|
|
||||||
``is_writable_iterator``
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
The part of the ``is_writable_iterator`` trait definition which
|
|
||||||
applies to old-style iterators is::
|
|
||||||
|
|
||||||
if (cat is convertible to output_iterator_tag)
|
if (cat is convertible to output_iterator_tag)
|
||||||
return true;
|
return true;
|
||||||
else if (
|
else if (cat is convertible to forward_iterator_tag
|
||||||
cat is convertible to forward_iterator_tag
|
|
||||||
and iterator_traits<Iterator>::reference is a
|
and iterator_traits<Iterator>::reference is a
|
||||||
mutable reference)
|
mutable reference)
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
The current forward iterator requirements place no constraints on
|
Suppose the ``value_type`` of the iterator has a private assignment
|
||||||
the iterator's ``reference`` type, so the logic above will give
|
operator::
|
||||||
false negatives for some otherwise-writable forward iterators whose
|
|
||||||
``reference`` type is not a mutable reference. Also, it will
|
|
||||||
report false positives for any forward, bidirectional, or random
|
|
||||||
access iterator whose ``reference`` is a mutable reference but
|
|
||||||
whose ``value_type`` is not assignable (e.g. has a private
|
|
||||||
assignment operator).
|
|
||||||
|
|
||||||
``is_swappable_iterator``
|
class B {
|
||||||
-------------------------
|
public:
|
||||||
|
...
|
||||||
|
private:
|
||||||
|
B& operator=(const B&);
|
||||||
|
};
|
||||||
|
|
||||||
Similarly, the part of ``is_swappable_iterator`` which applies to
|
and suppose the ``reference`` type of the iterator is ``B&``. Then
|
||||||
old-style iterators is::
|
``is_writable`` will deduce true when in fact attempting to write into
|
||||||
|
``B`` will cause an error.
|
||||||
|
|
||||||
else if (cat is convertible to forward_iterator_tag) {
|
The same problem applies to ``is_swappable``.
|
||||||
if (iterator_traits<Iterator>::reference is a const reference)
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
} else
|
|
||||||
return false;
|
|
||||||
|
|
||||||
In this case false positives are possible for non-writable forward
|
|
||||||
iterators whose ``reference`` type is not a reference, or as above,
|
|
||||||
any forward, bidirectional, or random access iterator whose
|
|
||||||
``reference`` is not a constant reference but whose ``value_type``
|
|
||||||
is not assignable (e.g., because it has a private assignment
|
|
||||||
operator).
|
|
||||||
|
|
||||||
False negatives can be "reasoned away": since it is part of a
|
|
||||||
writable iterator's concept definition that
|
|
||||||
``is_writable<I>::value`` is ``true``, any iterator for which
|
|
||||||
it is ``false`` is by definition not writable. This seems like a
|
|
||||||
perverse use of logic, though.
|
|
||||||
|
|
||||||
It might be reasonable to conclude that it is a defect that the
|
|
||||||
standard allows forward iterators with a ``reference`` type other
|
|
||||||
than ``value_type`` *cv*\ ``&``, but that still leaves the problem
|
|
||||||
of old-style iterators whose ``value_type`` is not assignable. It
|
|
||||||
is not possible to correctly compute writability and swappability
|
|
||||||
for those old-style iterators without intervention
|
|
||||||
(specializations of ``is_writable_iterator`` and
|
|
||||||
``is_swappable_iterator``) from a user.
|
|
||||||
|
|
||||||
No Use Cases for Some Access Traits
|
|
||||||
===================================
|
|
||||||
|
|
||||||
``is_swappable_iterator``
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
``is_swappable_iterator<I>`` is supposed to yield true if
|
|
||||||
``iter_swap(x,y)`` is valid for instances ``x`` and ``y`` of type
|
|
||||||
``I``. The only argument we have heard for
|
|
||||||
``is_swappable_iterator`` goes something like this:
|
|
||||||
|
|
||||||
*"If* ``is_swappable_iterator`` *yields* ``false``\ *, you
|
|
||||||
could fall back to using copy construction and assignment on
|
|
||||||
the* ``value_type`` *instead."*
|
|
||||||
|
|
||||||
This line of reasoning, however, falls down when closely examined.
|
|
||||||
To achieve the same effect using copy construction and assignment
|
|
||||||
on the iterator's ``value_type``, the iterator must be readable and
|
|
||||||
writable, and its ``value_type`` must be copy-constructible. But
|
|
||||||
then, ``iter_swap`` must work in that case, because its default
|
|
||||||
implementation just calls ``swap`` on the dereferenced iterators.
|
|
||||||
The only purpose for the swappable iterator concept is to represent
|
|
||||||
iterators which do not fulfill the properties listed above, but
|
|
||||||
which are nonetheless swappable because the user has provided an
|
|
||||||
overload or specialization of ``iter_swap``. In other words,
|
|
||||||
generic code which wants to swap the referents of two iterators
|
|
||||||
should *always* call ``iter_swap`` instead of doing the
|
|
||||||
assignments.
|
|
||||||
|
|
||||||
``is_writable_iterator``
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
Try to imagine a case where ``is_writable_iterator`` can be used to
|
|
||||||
choose behavior. Since the only requirement on a writable iterator
|
|
||||||
is that we can assign into its referent, the only use for
|
|
||||||
``is_writable_iterator`` in selecting behavior is to modify a
|
|
||||||
sequence when the sequence is mutable, and to not modify it
|
|
||||||
otherwise.
|
|
||||||
|
|
||||||
There is no precedent for generic functions which modify their
|
|
||||||
arguments only if the arguments are non-const reference, and with
|
|
||||||
good reason: the simple fact that data is mutable does not mean
|
|
||||||
that a user *intends* it to be mutated. We provide ``const`` and
|
|
||||||
non-\ ``const`` overloads for functions like ``operator[]``, but
|
|
||||||
these do not modify data; they merely return a reference to data
|
|
||||||
which preserves the object's mutability properties. We can do the
|
|
||||||
same with iterators using their ``reference`` types; the
|
|
||||||
accessibility of an assignment operator on the ``value_type``,
|
|
||||||
which determines writability, does not change that.
|
|
||||||
|
|
||||||
The one plausible argument we can imagine for
|
|
||||||
``is_writable_iterator`` and ``is_swappable_iterator`` is that they
|
|
||||||
can be used to remove algorithms from an overload set using a
|
|
||||||
SFINAE technique like enable_if_, thus minimizing unintentional
|
|
||||||
matches due to Koenig Lookup. If it means requiring explicit
|
|
||||||
indications of writability and swappability from new-style iterator
|
|
||||||
implementors, however, it seems to be too small a gain to be worth
|
|
||||||
the cost. That's especially true since we can't get many existing
|
|
||||||
old-style iterators to meet the same requirements.
|
|
||||||
|
|
||||||
.. _enable_if: http://tinyurl.com/tsr7
|
|
||||||
|
|
||||||
Naming Issues
|
|
||||||
=============
|
|
||||||
|
|
||||||
Traversal Concepts and Tags
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
Howard Hinnant pointed out some inconsistencies with the naming of
|
|
||||||
these tag types::
|
|
||||||
|
|
||||||
incrementable_iterator_tag // ++r, r++
|
|
||||||
single_pass_iterator_tag // adds a == b, a != b
|
|
||||||
forward_traversal_iterator_tag // adds multi-pass capability
|
|
||||||
bidirectional_traversal_iterator_tag // adds --r, r--
|
|
||||||
random_access_traversal_iterator_tag // adds r+n,n+r,r-n,r[n],etc.
|
|
||||||
|
|
||||||
Howard thought that it might be better if all tag names contained
|
|
||||||
the word "traversal".
|
|
||||||
|
|
||||||
It's not clear that would result in the best possible names,
|
|
||||||
though. For example, incrementable iterators can only make a
|
|
||||||
single pass over their input. What really distinguishes single
|
|
||||||
pass iterators from incrementable iterators is not that they can
|
|
||||||
make a single pass, but that they are equality comparable. Forward
|
|
||||||
traversal iterators really distinguish themselves by introducing
|
|
||||||
multi-pass capability. Without entering a "Parkinson's Bicycle
|
|
||||||
Shed" type of discussion, it might be worth giving the names of
|
|
||||||
these tags (and the associated concepts) some extra attention.
|
|
||||||
|
|
||||||
Access Traits
|
|
||||||
-------------
|
|
||||||
|
|
||||||
The names ``is_readable``, ``is_writable``, and ``is_swappable``
|
|
||||||
are probably too general for their semantics. In particular, a
|
|
||||||
swappable iterator is only swappable in the same sense that a
|
|
||||||
mutable iterator is mutable: the trait refers to the iterator's
|
|
||||||
referent. It would probably be better to add the ``_iterator``
|
|
||||||
suffix to each of these names.
|
|
||||||
|
|
||||||
================================
|
|
||||||
Proposed Solution (in progress)
|
|
||||||
================================
|
|
||||||
|
|
||||||
We believe that ``is_readable_iterator`` is a fine name for the
|
|
||||||
proposed ``is_readable`` trait and will use that from here on. In
|
|
||||||
order to avoid confusion, however, and because we aren't terribly
|
|
||||||
convinced of any answer yet, we are going to phrase this solution
|
|
||||||
in terms of the existing traversal concept and tag names. We'll
|
|
||||||
propose a few possible traversal naming schemes at the end of this
|
|
||||||
section.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
========
|
|
||||||
|
|
||||||
Following the dictum that what we can't do well probably shouldn't
|
|
||||||
be done at all, we'd like to solve many of the problems above by
|
|
||||||
eliminating details and simplifying the library as proposed. In
|
|
||||||
particular, we'd eliminate ``is_writable`` and ``is_swappable``,
|
|
||||||
and remove the requirements which say that writable, and swappable
|
|
||||||
iterators must support these traits. ``is_readable_iterator`` has
|
|
||||||
proven to be useful and will be retained, but since it can be
|
|
||||||
implemented with no special hints from the iterator, it will not be
|
|
||||||
mentioned in the readable iterator requirements. Since we don't
|
|
||||||
want to require the user to explicitly specify access category
|
|
||||||
information, we'll change ``iterator_tag`` so that it computes the
|
|
||||||
old-style category in terms of the iterator's traversal category,
|
|
||||||
``reference``, and ``value_type``.
|
|
||||||
|
|
||||||
Future Enhancements
|
|
||||||
===================
|
|
||||||
|
|
||||||
For C++0x, we could consider a change to ``iterator_traits`` which
|
|
||||||
allows the user to avoid the use of iterator_tag (or similar
|
|
||||||
devices) altogether and write a new-style iterator by specifying
|
|
||||||
only a traversal tag. This change is not being proposed as it does
|
|
||||||
not constitute a "pure bolt-on"::
|
|
||||||
|
|
||||||
iterator_traits<I>::iterator_category
|
|
||||||
= if (I::iterator_category is a type) // use mpl::has_xxx (SFINAE)
|
|
||||||
return I::iterator_category
|
|
||||||
|
|
||||||
// Only old-style output iterators may have a void value_type
|
|
||||||
// or difference_type
|
|
||||||
if (iterator_value_type<I>::type is void
|
|
||||||
|| iterator_difference_type<I>::type is void
|
|
||||||
)
|
|
||||||
return std::output_iterator_tag
|
|
||||||
|
|
||||||
t = iterator_traversal<I>::type
|
|
||||||
|
|
||||||
if (I is an lvalue iterator)
|
|
||||||
{
|
|
||||||
if (t is convertible to random_access_traversal_tag)
|
|
||||||
return std::random_access_iterator_tag
|
|
||||||
if (t is convertible to bidirectional_traversal_tag)
|
|
||||||
return std::bidirectional_iterator_tag
|
|
||||||
else if (t is convertible to forward_traversal_tag)
|
|
||||||
return std::forward_iterator_tag
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t is convertible to single_pass_traversal_tag
|
|
||||||
&& I is a readable iterator
|
|
||||||
)
|
|
||||||
return input_output_iterator_tag // (**)
|
|
||||||
else
|
|
||||||
return std::output_iterator_tag
|
|
||||||
|
|
||||||
|
|
||||||
Impact on N1530_ (Iterator Facade and Adaptor)
|
====================
|
||||||
==============================================
|
Proposed Resolution
|
||||||
|
====================
|
||||||
|
|
||||||
|
1. Remove the ``is_writable`` and ``is_swappable`` traits, and remove the
|
||||||
|
requirements in the Writable Iterator and Swappable Iterator concepts
|
||||||
|
that require their models to support these traits.
|
||||||
|
|
||||||
|
2. Change the ``is_readable`` specification to be:
|
||||||
|
``is_readable<X>::type`` is ``true_type`` if the
|
||||||
|
result type of ``X::operator*`` is convertible to
|
||||||
|
``iterator_traits<X>::value_type`` and is ``false_type``
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
Remove the requirement for support of the ``is_readable`` trait from
|
||||||
|
the Readable Iterator concept.
|
||||||
|
|
||||||
|
3. Change ``iterator_tag`` to::
|
||||||
|
|
||||||
|
template <class Value, class Reference, class Traversal>
|
||||||
|
struct iterator_tag;
|
||||||
|
|
||||||
|
The argument for ``Value`` must be the value type of the
|
||||||
|
iterator, ``Reference`` must be the return type of
|
||||||
|
``operator*`` [*]_, and ``Traversal`` the traversal tag for the
|
||||||
|
iterator.
|
||||||
|
|
||||||
|
``iterator_tag`` is required to be convertible to both the
|
||||||
|
``Traversal`` tag and also to the appropriate old iterator category
|
||||||
|
tag, as specified by the following pseudo-code::
|
||||||
|
|
||||||
|
inherit-category(Value, Reference, Traversal) =
|
||||||
|
if (Reference is a reference
|
||||||
|
and Traversal is convertible to forward_traversal_tag) {
|
||||||
|
if (Traversal is convertible to random_access_traversal_tag)
|
||||||
|
return random_access_iterator_tag;
|
||||||
|
else if (Traversal is convertible to bidirectional_traversal_tag)
|
||||||
|
return bidirectional_iterator_tag;
|
||||||
|
else
|
||||||
|
return forward_iterator_tag;
|
||||||
|
} else if (Traversal is convertible to single_pass_traversal_tag
|
||||||
|
and Reference is convertible to Value) {
|
||||||
|
if (Value is const)
|
||||||
|
return input_iterator_tag;
|
||||||
|
else
|
||||||
|
return input_output_iterator_tag;
|
||||||
|
} else
|
||||||
|
return output_iterator_tag;
|
||||||
|
|
||||||
|
|
||||||
|
.. [*] Instead of saying "return type of operator*", we could have
|
||||||
|
said ``iterator_traits<X>::reference``. However, the standard
|
||||||
|
specifies nothing about ``iterator_traits<X>::reference``,
|
||||||
|
which we believe is a defect. Once the defect is fixed,
|
||||||
|
the above could be rephrased.
|
||||||
|
|
||||||
|
|
||||||
|
4. Change the specification of ``traversal_category`` to::
|
||||||
|
|
||||||
|
traversal-category(Iterator) =
|
||||||
|
let cat = iterator_traits<Iterator>::iterator_category
|
||||||
|
if (cat convertible to incrementable_iterator_tag)
|
||||||
|
return cat; // Iterator is a new iterator
|
||||||
|
else if (cat is convertible to random_access_iterator_tag)
|
||||||
|
return random_access_traversal_tag;
|
||||||
|
else if (cat is convertible to bidirectional_iterator_tag)
|
||||||
|
return bidirectional_traversal_tag;
|
||||||
|
else if (cat is convertible to forward_iterator_tag)
|
||||||
|
return forward_traversal_tag;
|
||||||
|
else if (cat is convertible to input_iterator_tag)
|
||||||
|
return single_pass_iterator_tag;
|
||||||
|
else if (cat is convertible to output_iterator_tag)
|
||||||
|
return incrementable_iterator_tag;
|
||||||
|
else
|
||||||
|
return null_category_tag;
|
||||||
|
|
||||||
|
|
||||||
|
==========
|
||||||
|
Rationale
|
||||||
|
==========
|
||||||
|
|
||||||
|
1. There are two reasons for removing ``is_writable``
|
||||||
|
and ``is_swappable``. The first is that we do not know of
|
||||||
|
a way to fix the specification so that it gives the correct
|
||||||
|
answer for all iterators. Second, there was only a weak
|
||||||
|
motivation for having ``is_writable`` and ``is_swappable``
|
||||||
|
there in the first place. The main motivation was simply
|
||||||
|
uniformity: we have tags for the old iterator categories
|
||||||
|
so we should have tags for the new iterator categories.
|
||||||
|
While having tags and the capability to dispatch based
|
||||||
|
on the traversal categories is often used, we see
|
||||||
|
less of a need for dispatching based on writability
|
||||||
|
and swappability, since typically algorithms
|
||||||
|
that need these capabilities have no alternative if
|
||||||
|
they are not provided.
|
||||||
|
|
||||||
|
2. We discovered that the ``is_readable`` trait can be
|
||||||
|
implemented without special hints from the iterator.
|
||||||
|
Therefore we remove the requirement for ``is_readable``
|
||||||
|
from the Readable Iterator concept, and change
|
||||||
|
the definition of ``is_readable`` so that it works
|
||||||
|
for any iterator type.
|
||||||
|
|
||||||
|
3. With ``is_writable`` and ``is_swappable`` gone, and
|
||||||
|
``is_readable`` no longer in need of special hints,
|
||||||
|
there is no reason for the ``iterator_tag`` class to provide
|
||||||
|
information about the access capabilities of an iterator.
|
||||||
|
This new version provides only information about the traversal
|
||||||
|
capabilities and the old iterator category tag. Instead of accessing
|
||||||
|
the traversal category as a nested typedef ``::traversal``,
|
||||||
|
the ``iterator_tag`` itself will be convertible to the traversal
|
||||||
|
tag. The ``access_bits`` parameter is no longer needed for
|
||||||
|
specifying the access member (which is now gone). However,
|
||||||
|
some access information is still needed so that we can
|
||||||
|
deduce the appropriate old iterator category. The
|
||||||
|
``Value`` and ``Reference`` parameters fill this need.
|
||||||
|
Note that this solution cleans up the issue that John
|
||||||
|
Maddock raised on the reflector about the non-uniformity
|
||||||
|
of the lvalue bit.
|
||||||
|
|
||||||
|
4. The changes to the specification of ``traversal_category`` are a
|
||||||
|
direct result of the changes to ``iterator_tag``.
|
||||||
|
|
||||||
XXX
|
|
Reference in New Issue
Block a user