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