Boost logo

Boost Users :

From: Ronald Garcia (garcia_at_[hidden])
Date: 2004-12-13 15:32:44


Howdy,

Dave sent me a forward to this conversation some time ago and I have
finally made the time to catch up on the conversation and contribute my
two cents. The issues that Tobias describes regarding operator-> are,
I believe, exactly the same issues I encountered while implementing
Boost.MultiArray.

To use an earlier version of iterator adaptors, I had to supply a
modified copy of the library as part of MultiArray's implementation.
Using the more recent version of iterator adaptors, I simply had to
implement my own operator->() member function and operator_arrow_proxy
object. Clearly I can implement the behavior I want using the library
as-is.

The question of interest then seems to be the following: Should
iterator adaptors somehow more explicitly take my and Tobias'
implementation needs into consideration? Perhaps the library's default
behavior when Reference is not a true reference type should be changed;
  Perhaps a FAQ entry in the documentation would suffice. Since I can
make my library work correctly today with little pain or code
duplication, I'm not particularly passionate about this case.

With respect to the greater language issue, let me respond to an
earlier statement by Dave:

"If iterator_facade handed you a pointer to non-const value_type, it
would allow you to modify this temporary, which would then disappear.
We don't do that for the same reasons the language won't bind a
non-const reference to a temporary."

Around here (the Open Systems Lab at Indiana University), I've been
arguing against this property of C++ for a long time, specifically that
the language does not allow you to bind temporary to a non-const
reference. I am under the impression that this behavior is meant to
"protect" the programmer from making certain mistakes, but the grave
downside to this is that it also prevents a library writer from using
proxy objects to mimic reference semantics.

Taking an example from MultiArray, if I have an 2x2 multidimensional
array:
boost::multi_array<int,2> A(extents[2][2]);

and a template function that takes some Random Access Sequence:

template <Sequence>
process_sequence(Sequence& Seq) { /* ... */ }

then I cannot pass this function the second "row" of my array using the
syntax:
process_sequence(A[1]);

because the return type of multi_array::operator[]() is an object of
type multi_array::subarray. The whole purpose of the subarray is to
behave as though it were a nested array within the multi_array. Bear
in mind that multi_arrays are not implemented using nested arrays: the
internal data is stored as a contiguous heap-allocated array. You
could argue that the entire point of the MultiArray library is to allow
one to avoid implementing multi-dimensional arrays using such a nested
form (i.e. std::vector<std::vector<int> >).

In order to make MultiArray work, it must return a proxy object that
implements const and nonconst versions of the array operators. The
subarray operators manipulate the data stored in the original
multi_array that created the original subarray.

In fact, the Matrix Template Library (MTL) ran into the same problem.
The library often creates a "view" of a matrix as an argument to a
subsequent function. But again, if the subsequent function takes its
argument by reference (which it should if the argument is used as an
"out parameter"), then the result is a compile-time error. What was
their solution? I hate to admit this: const_cast.

Let's take a step back for a moment. Why is it okay in the language for
me to create a temporary object and immediately call a non-const member
function on it, as in:
A().member_function();

But it's not okay for me to pass a temporary object to another function
that in turn calls that same member function, as in:

template <class T> call(T& x) { x.member_function(); }

call(A());

I've been convinced by others that if the move semantics proposal is
accepted into the language, it will take care of this issue.

Perhaps iterator_adaptors should mimic the behavior of C++ as it stands
for the purpose of consistency, but I'm not convinced that this
property of the language does much for safety. It is clear to me,
however, that it hinders library developers who hope to mimic reference
semantics with proxies. The correspondence that I quoted above
suggests to me that this lies at the heart of my questions regarding
iterator adaptors' default behavior.

ron

On Dec 6, 2004, at 2:25 PM, David Abrahams wrote:

> Tobias Schwinger wrote:
> > David Abrahams wrote:
> >
> >> But why not just change the interface in your dereference function
> and
> >> hand back a real reference at that point? If you are not storing a
> >> copy of the value_type within the proxy, I don't see what the proxy
> >> can be buying you.
> >>
> >
> > I'll put a short problem-domain-specific excursion at the end of this
> > post which explains what exactly the proxy is buying me.
>
> Thanks.
>
> >> So now we come to this one. This one is entirely in the hands of
> the
> >> library to prevent, since the user doesn't usually determine the
> >> return type of operator->. It seems like a bad idea to allow it
> >> silently.
> >> The library is supplying an operator-> that stores an object of the
> >> value_type. There are very few situations where modifications to
> that
> >> object can go anywhere useful, so it makes sense to prevent them.
> >
> >
> > Doesn't the fact that reference and value_type are the same and both
> are
> > mutable class types indicate that this should work as well ?
>
> Ah. Well now, that is a special case. Maybe we should allow it
> whenever reference is the same as or derived from value_type.
> Incidentally, I don't call that case a proxy -- as far as I'm
> concerned that's just an ordinary non-lvalue iterator. I suppose
> if your value_type is really a refcounted pimpl it is somewhat
> proxy-like, though. Incidentally #2, the library is focused
> mainly on building generic iterators so we usually didn't
> consider the fact that class value_type objects returned by value
> from an iterator's operator* might be assigned into.
>
> > After all, it should be just another notation for
> >
> > (*i).mutator(value)
> >
> > from a user's perspective.
> >
> >
> >> The library is able to make sensible default choices without fear
> >> because the user can always reimplement any operator within his
> >> derived iterator.
> >>
> >
> > My workaround to allow it feels somewhat ugly, because I have
> > to redundantly do the same thing iterator_facade does already
> > (use yet another proxy to aggregate my temporary reference
> > object and make its operator-> return a pointer to it - just
> > like operator_arrow_proxy (given that reference and value_type
> > are the same), except that there is a non-const operator->,
> > too).
>
> Understandable.
>
> > !!! However, it works for me. I don't want to try to make you change
> > things for what could be a special case !!!
> >
> > I'm not too sure calling mutators on a proxy class really is that
> > special, though.
>
> It's not so special, but building a proxy reference that fully
> emulates the interface of the referenced value type is quite
> unusual. It's takes a lot of work, is very fragile and can't be
> done generically.
>
> >>> ...and I can't imagine there is a user who would seriously
> complain that
> >>>
> >>> pointer ptr ( i.operator->().operator->() );
> >>>
> >>> gives an invalid pointer ;-).
> >>>
> >>>
> >>>
> >>> Am I missing something ?
> >>
> >>
> >>
> >> There was a guy who complained just last week on this very mailing
> >> list that putting an iterator_adaptor over a T const* suddenly
> >> produced writability in the iterator through operator-> !!
> >> http://lists.boost.org/MailArchives/boost-users/msg08656.php
> >
> > Yeah, I (partially) read it to ensure I am not asking redundant
> > questions before starting this thread. I still believe adapting
> > pointers is a different story.
>
> It's not such a different story. If I take a non-writable
> version your iterator, operating correctly, and wrap
> iterator_adaptor around it, it would be very strange if
> operator-> allowed you to mutate members.
>
> > Excursion: what's the proxy buying me ?
> >
> > I use a generic, resizable container (similar to std::vector
> > but with memory layout guarantees) to feed vector data to my
> > graphics board through some rendering API. The proxy allows me
> > to see a vector object instead of a reference to an array of
> > floating type.
> >
> > Operating on plain arrays in order to perform vector
> > arithmetics is quite unhandy. Using a converting constructor
> > from a reference to array type is a radical and error prone
> > approach and would restrict me to use only one class for vector
> > arithmetics (there is no way to tell if float[3] should be a
> > homogenous 2D-vector or a non-homogenous 3D-vector, for
> > example).
> >
> > That's why I use a vector-reference proxy class that allows me
> > to work on vectors whose data lives somewhere else.
>
> I understand, thanks.
>
>
> --
> Dave Abrahams
> Boost Consulting
> http://www.boost-consulting.com
>
> _______________________________________________
> Boost-users mailing list
> Boost-users_at_[hidden]
> http://lists.boost.org/mailman/listinfo.cgi/boost-users



Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net