
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