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:
Courier
"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< A(extents[2][2]);
and a template function that takes some Random Access Sequence:
template <
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< >).
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 < 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@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/boost-users