On 2010-12-03 18:17:26 -0500, "Steven Schveighoffer"
<[email protected]> said:
Here is an example range from dcollections (well, at least the
pertinant part), for a linked list:
struct Range
{
LinkNode *node;
void popFront() { node = node.next; }
}
Now, let's say I have a LinkList instance (ignore the parameterized
type). I want to pass this list to a function and ensure nothing
changes in the list. So I create a function like this:
void foo(const(LinkList) list)
{
auto r = list[]; // get a range from the list
}
Now, LinkList has a function like this:
Range opSlice()
{
Range result;
result.node = head;
return result;
}
In order to satisfy constancy, I now have to do two things. I have to
define a *different* range, because const(Range) doesn't work (popFront
is not and cannot be const). Call it ConstRange. The second thing I
have to do is now define a completely separate function for opSlice:
ConstRange opSlice() const
{
ConstRange result;
result.node = head; // result.node is tail-const
}
Now, I could possibly make Range just parameterized on the
parameterized type, but I still have to create two separate types
(whether generated by template or not).
Finally, I have to repeat *all this* for immutable. And for all
functions that take a range, or return a range.
And this is the real kicker... it *still* doesn't work correctly. Observe:
void foo(LinkList.ConstRange r)
{
}
If I have a mutable LinkList called list, I can't do foo(list[]),
because Range does not implicitly convert to ConstRange. I have to
first convert list to a const(LinkList), and then use the slice
operator.
Now, if we have tail-const that I can apply to a struct, which just
makes all references contained in the type tail-const, then this
becomes very very easy (with proposed syntax from Tomek):
@tail inout(Range) opSlice() inout
{
...
}
One function, one Range defined, very simple, very elegant.
Note that I can do all this if my range is an array (as it is in
ArrayList) without modification to the compiler, because tail-const
arrays are possible.
All I want is to duplicate the implicit casting, and implicit typing,
that arrays have with tail const. If we can have a solution that
fixes the tail-const class problem *and* this problem, it will be two
birds, one stone.
A fine explanation. Thank you.
I disagree about your proposed solution, but I recognize the problem.
The basic problem with your solution is that it creates a new kind of
const, a new kind of immutable and a new kind of shared. You should
realize that for the compiler to know the constness of member variables
inside a function, it'll have to know whether the 'this' pointer is
'const' or 'tail const'. So I think it's the wrong path.
The right path would be, I think, to parametrize the constness in the
type. A way to do this within the current constrains of the language
would be to make Range implicitly convertible to ConstRange, something
you should be able to do with "alias X this", X being a function
returning a ConstRange. The disadvantages are the duplication of the
opSlice function, and the inability to cast implicitly a ref Range to
ref ConstRange or a Range[] to a ConstRange[].
I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member is
implicitly convertible to the same member of the other template.
--
Michel Fortin
[email protected]
http://michelf.com/