Boost logo

Boost Users :

From: David Abrahams (dave_at_[hidden])
Date: 2004-12-13 22:49:03


Ronald Garcia wrote:
> 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.

The only case I can think of where that's true is when the reference
type is the same as the value type. If we allowed operator->

> 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]);

Why can't you just return a const proxy?

> 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.

  class multi_array
  {
      const mutable_proxy operator[](int);
      const immutable_proxy operator[](int);
      ...
  };

Now mutable_proxy only needs one version of the array operators.
Couldn't that work?

> 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.

Did someone think about returning const proxies?

> 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());

You may note that I'm an author of a major proposal to allow templated
references to bind to non-const rvalues.

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

Bingo.

> 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.

I'm not sure; have they used enough imagination?

-- 
Dave Abrahams
Boost Consulting
http://www.boost-consulting.com

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