On Fri, 11 Feb 2011 17:08:26 -0500, Tomek Sowiński <j...@ask.me> wrote:
Steven Schveighoffer napisał:
> It would be much easier if he provided the specific case(s) which
broke
> his teeth. Then we'll all know where's the problem. If it's soluble,
> it'll open the door to tail type modifiers in general, not just in
> classes. It's a burning issue e.g. with ranges (mostly struct).
>
> http://d.puremagic.com/issues/show_bug.cgi?id=5377
>
> Look at the attachment to get a feel of what hoops we'll have to jump
> through to side-step lack of tail X.
I've worked through this very same problem (a few months back), thinking
that we need a general solution to tail-const. The large issue with
tail-const for structs in the general case is that you cannot control
the
type of 'this'. It's always ref. This might seem like a very
inconsequential detail, but I realized that a ref to X does not
implicitly
convert to a ref to a tail-const X. This violates a rule of two
indirections, in which case you are not able to implicitly convert the
indirect type, even if the indirect type would implicitly convert
outside
the reference.
A simple example, you cannot convert an int** to a const(int)**. Reason
being, then you could change the indirect pointer to point to something
that's immutable, and the original int ** now points to immutable data.
I tried to understand this on an example and now I'm even more confused.
:)
int* p;
int** pp = &p;
const(int)** cpp = pp; // compiles fine
immutable int i = 7;
*cpp = &i;
**pp = 5; // mutate the immutable
writeln(cpp, ' ', pp);
writeln(*cpp, ' ', *pp, ' ', &i);
writeln(**cpp, ' ', **pp, ' ', i);
The output is interesting:
12FE08 12FE08
12FE14 12FE14 12FE14
5 5 7
So even they all point to i at the end, it remains unchanged. What
gives? Register caching? It doesn't matter as the int** to a
const(int)** conversion should fail in the first place, but I'm
curious...
Most likely the compiler replaces usage of i as a value as a usage of the
literal 7. Maybe change the initialization of i to something decided at
runtime and it will be more as expected.
This is more understandable if you use string literals which inherently
must be references :)
and I guess I should have said "you *shouldn't be able to* convert an
int** to a const(int)**...", because obviously that isn't enforced. But
it should be (your example shows why). I think there's a bugzilla on
this, anyone?
The same is for tail-const structs, because you go through one ref via
'this' and the other ref via the referring member.
What does this all mean? It basically means that you have to define
*separate* functions for tail-const and const, and separate functions
for
tail-immutable and immutable. This is untenable.
I, from the very first discussions, assumed tail-const functions are
inevitable. You define empty() as const but popFront() as tail-const.
Feels natural.
It seems to work for ranges, but the problem is, const instances cannot
call tail-const functions. This means if there is a function that makes
sense as tail-const, you still need two implementations (the const one can
create a tail-const copy and call the tail-const version).
Immutable is even worse, you can't call immutable functions from
tail-immutable functions, and you can't call tail-immutable functions from
immutable functions.
This doesn't sit right when compared to arrays, where you can call tail-*
functions from their fully-* equivalents. This is because they can are
passed by value.
I don't know, maybe it's still viable, but I think we are unlikely to
convince Andrei and Walter that they are. If nothing else, having to have
new const types is a definite minus.
You might ask "why doesn't this problem occur with tail-const arrays?",
well because you *don't pass them by ref*. With structs we have no
choice.
I think what we need is a way to define two different structs as being
the
tail-const version of the other, with some compiler help, and then we do
not need to define a new flavor of const functions. We still need to
define these "tail-const" functions, but it comes in a more
understandable
form. But importantly, the implicit cast makes a *temporary* copy of
the
struct, allowing the cast to work.
I'd like to understand it better. How would you define with this scheme,
say, a range on a const collection, to which ranges on an (im)mutable
collection are implicitly convertible?
Probably with a templated range, where the template defined whether it was
iterating const data or not. The trick is, you need to convince the
compiler that they implicitly convert to one another. Either by
assumption (which I'm not sure can be done) or explicit declaration.
-Steve