digitalmars.D - The tailconst problem (and suggestions for solution)
- Janice Caron (61/61) Dec 07 2007 Walter, I have thought very hard about what you said, and I have come
- Walter Bright (25/97) Dec 07 2007 Regardless of the technical merits of tailconst and tailinvariant, I
- Janice Caron (16/22) Dec 07 2007 I think I got misunderstood. But no matter - it's unimportant. It just
- Don Clugston (9/32) Dec 07 2007 Don't you just need to wrap it in a struct ??
- Janice Caron (37/45) Dec 07 2007 I thought the intent was that TailConst!(T) would be accessed exactly
- Craig Black (8/11) Dec 07 2007 Thank you for that good explanation. I know it must be frustrating goin...
- Janice Caron (9/13) Dec 07 2007 Look at it another way. You'd want
- Craig Black (10/25) Dec 07 2007 I think you are misunderstanding my question.
Walter, I have thought very hard about what you said, and I have come to the conclusion that Andrei is absolutely correct. However, there might be some things you can do to mitigate things a little, to make things slightly easier for the programmer. Once I realised that my ampersand idea wouldn't work, because it would set up a contradiction between the requirements of generic programming, and the requirement of being able to wrap anything in a struct, my next thought was a new keyword: "tailconst". (Actually, two new keywords, because you'd also need "tailinvariant"). With this keyword, class C {} tailconst(C) c; would mean "c is a mutable ref to const data". Of course that does then beg the question, what does tailconst(T) mean for T in general, if T is not a class. The answer is: (1) from the perspective of a human being writing code, consider it undefined. Just don't use it. Except with classes, there is never a need to use it all, as everything you want can be expressed more clearly with const, and (2) from the perpective of the compiler, one can come up with some rules to make it all work properly under the hood. I did actually come up with most of what those rules would need to be (...and I can certainly tell you if you want...), but then it occurred to me that that must be exactly what Andrei did when he proposed his template idea. In other words, I suspect that my "tailconst(C)" idea is identical in practice to Andrei's "TailConst!(T)" idea, the only difference being that I made up new keywords and Andrei didn't. I still think that the tailconst keyword idea is /slightly/ preferable to the TailConst! template idea, if only because of the implementation of the specialization for classes. "tailconst" could give us compiler support for the notion of "mutable ref to const data", whereas TailConst! might involve a lot of fiddling about. The third possibility is of course, doing without it altogether. That's easy to do in principle: If you want a mutable reference, use a pointer! class C {} const(C)* c; ...or arrays class C {} const(C)*[] a; There are two problems with this, however. Neither of the following two lines will compile: a[3] = new C; auto x = a[3],member; The first one won't compile, because the compiler won't implicitly convert a reference-to-class-data into a pointer-to-reference-to-class-data (and with good reason!). The second one won't compile because the type of a[3] is pointer-to-reference-to-class-data, not reference-to-class-data, and the dot operator will only dereference once. The only way I can think of getting around this is to allow two new rules (one of which I've actually asked for before). The first is: (1) Make it possible to implicitly convert a reference-to-class-data into a pointer-to-reference-to-class-data. The implementation of this trick must first copy the reference onto the heap, so that the pointer points into the heap, not into the stack. (2) (and this is the one I've asked for before) allow dereferencing to be recursive, so that p.member will work even if p is a pointer-to-pointer-to-pointer-to-pointer-to-data. I think those two rules will let us work with const(C)*[], and hence do away with the need for tailconst altogether.
Dec 07 2007
Janice Caron wrote:Once I realised that my ampersand idea wouldn't work, because it would set up a contradiction between the requirements of generic programming, and the requirement of being able to wrap anything in a struct, my next thought was a new keyword: "tailconst". (Actually, two new keywords, because you'd also need "tailinvariant"). With this keyword, class C {} tailconst(C) c; would mean "c is a mutable ref to const data".Regardless of the technical merits of tailconst and tailinvariant, I suspect that going from 2 kinds of const to 4 kinds will produce a revolt and a general sense that D has miserably failed at a comprehensible const design. This is on top of the problem that I have never been able to find the words to explain in a straightforward manner what tailconst even means.Of course that does then beg the question, what does tailconst(T) mean for T in general, if T is not a class. The answer is: (1) from the perspective of a human being writing code, consider it undefined. Just don't use it. Except with classes, there is never a need to use it all, as everything you want can be expressed more clearly with const, andThat doesn't work because of the need to have a consistent system for use with generic code.(2) from the perpective of the compiler, one can come up with some rules to make it all work properly under the hood.I've never been smart enough to keep all the const rules in my head at the same time, which is why the last const design is coming up short. I forgot a couple :-( This is possibly why I also have trouble explaining it.I did actually come up with most of what those rules would need to be (...and I can certainly tell you if you want...), but then it occurred to me that that must be exactly what Andrei did when he proposed his template idea. In other words, I suspect that my "tailconst(C)" idea is identical in practice to Andrei's "TailConst!(T)" idea, the only difference being that I made up new keywords and Andrei didn't. I still think that the tailconst keyword idea is /slightly/ preferable to the TailConst! template idea, if only because of the implementation of the specialization for classes. "tailconst" could give us compiler support for the notion of "mutable ref to const data", whereas TailConst! might involve a lot of fiddling about.If it can be done with a template, then I'd prefer the template solution because: 1) templates need to be powerful enough to do such things and this would add credence to D templates and 2) the language is complex enough already and 3) there would have to be a big advantage to putting it in the core language and I don't see one.The third possibility is of course, doing without it altogether. That's easy to do in principle: If you want a mutable reference, use a pointer! class C {} const(C)* c; ...or arrays class C {} const(C)*[] a; There are two problems with this, however. Neither of the following two lines will compile: a[3] = new C; auto x = a[3],member; The first one won't compile, because the compiler won't implicitly convert a reference-to-class-data into a pointer-to-reference-to-class-data (and with good reason!). The second one won't compile because the type of a[3] is pointer-to-reference-to-class-data, not reference-to-class-data, and the dot operator will only dereference once. The only way I can think of getting around this is to allow two new rules (one of which I've actually asked for before). The first is: (1) Make it possible to implicitly convert a reference-to-class-data into a pointer-to-reference-to-class-data. The implementation of this trick must first copy the reference onto the heap, so that the pointer points into the heap, not into the stack.I'm uncomfortable with solutions that imply heap allocation in a non-obvious manner.(2) (and this is the one I've asked for before) allow dereferencing to be recursive, so that p.member will work even if p is a pointer-to-pointer-to-pointer-to-pointer-to-data.The problem with this is the C problem with confusing * and [] for pointers and arrays. Such ambiguities work well initially, but as the language grows more and more problems result from it.I think those two rules will let us work with const(C)*[], and hence do away with the need for tailconst altogether.I'd like to accumulate more experience with const to see if there is a pressing need to solve this problem, otherwise we risk throwing in complex solutions for non-issues.
Dec 07 2007
On Dec 7, 2007 11:00 AM, Walter Bright <newshound1 digitalmars.com> wrote:That doesn't work because of the need to have a consistent system for use with generic code.I think I got misunderstood. But no matter - it's unimportant. It just goes to show how hard it is to have a conversation about this stuff at all! :-)If it can be done with a template, then I'd prefer the template solutionI believe it can be done with a template, yes. However, that template needs to be written by someone who really understands what goes on under the hood, because (if it works like I think it will have to work) that template is going to need to do something which the manual says is undefined: it will have to cast away const (in at least one place). That's not a problem in a standard-library-provided template, of course. It's probably better done there than in user code. That said, Andrei is much smarter than I, so maybe he can do it without even needed that cast.I'd like to accumulate more experience with const to see if there is a pressing need to solve this problem, otherwise we risk throwing in complex solutions for non-issues.Makes total sense to me. I agree completely. Thanks
Dec 07 2007
Janice Caron wrote:On Dec 7, 2007 11:00 AM, Walter Bright <newshound1 digitalmars.com> wrote:Don't you just need to wrap it in a struct ?? struct A { const B b; } A a; Then you can't change a.b, but you can replace a. Or did I miss something?That doesn't work because of the need to have a consistent system for use with generic code.I think I got misunderstood. But no matter - it's unimportant. It just goes to show how hard it is to have a conversation about this stuff at all! :-)If it can be done with a template, then I'd prefer the template solutionI believe it can be done with a template, yes. However, that template needs to be written by someone who really understands what goes on under the hood, because (if it works like I think it will have to work) that template is going to need to do something which the manual says is undefined: it will have to cast away const (in at least one place). That's not a problem in a standard-library-provided template, of course. It's probably better done there than in user code. That said, Andrei is much smarter than I, so maybe he can do it without even needed that cast.
Dec 07 2007
On Dec 7, 2007 2:14 PM, Don Clugston <dac nospam.com.au> wrote:Don't you just need to wrap it in a struct ?? struct A { const B b; } A a; Then you can't change a.b, but you can replace a. Or did I miss something?I thought the intent was that TailConst!(T) would be accessed exactly like T or const(T). As in, you want to be able to write "a.member", but access "b.member". As in: class T { int x; this() { x = 42; } int get() const { return x; } void set(int n) { x = n; } } T mt; TailConst!(T) tt; const(T) ct; // new mt = new T; // OK tt = new TailConst!(T); // OK ct = new const(T); // ERROR // reading int n; n = mt.get; // OK n = tt.get; // OK n = ct.get; // OK // writing mt.set(1); // OK tt.set(1); // ERROR ct.set(1); // ERROR I think that wrapping it in a struct will work, providing we have alias this. (Right now, we don't, so that didn't occur to me). As in struct tailConst(T) { const(T) __this; alias __this this; } but without the alias this, you'd have to explicitly provide an extra level of dereferencing. So yeah! Give us alias this and that will work just fine. Problem solved!
Dec 07 2007
I'd like to accumulate more experience with const to see if there is a pressing need to solve this problem, otherwise we risk throwing in complex solutions for non-issues.Thank you for that good explanation. I know it must be frustrating going over this stuff again and again with us mortals. What I don't understand is why. const(C) c; Means that the reference is constant. It would be more intuitive (to me) if it meant that the instance was constant. Was it decided that it is more important to have constant references than constant instances? -Craig
Dec 07 2007
On Dec 7, 2007 3:12 PM, Craig Black <cblack ara.com> wrote:What I don't understand is why. const(C) c; Means that the reference is constant.Look at it another way. You'd want const T t; t = something; to fail, always - regardless of the type of T. It has to be true when T is a class, because it has to be true for everything. Remember, the two new (vastly simplified, but brilliant) rules are: (1) const type identifer; means the same thing as const(type) identifier; (2) Everything inside the brackets is const
Dec 07 2007
"Janice Caron" <caron800 googlemail.com> wrote in message news:mailman.250.1197041224.2338.digitalmars-d puremagic.com...On Dec 7, 2007 3:12 PM, Craig Black <cblack ara.com> wrote:I think you are misunderstanding my question. Everything inside the parenthesis should be constant. I am in total agreement here. But in this example, the only thing that appears inside the parenthesis is the class type, and it is interpreted as the class instance is mutable, but the reference is constant. I've been reading the other const thread and Sean Kelly mentioned the same thing regarding arrays. I think Walter mentioned fixing this in the next release. I'm not sure though.What I don't understand is why. const(C) c; Means that the reference is constant.Look at it another way. You'd want const T t; t = something; to fail, always - regardless of the type of T. It has to be true when T is a class, because it has to be true for everything. Remember, the two new (vastly simplified, but brilliant) rules are: (1) const type identifer; means the same thing as const(type) identifier; (2) Everything inside the brackets is const
Dec 07 2007