Bug 6695 – typeof(this) does not take into account const/immutable attributes inside member functions

Status
RESOLVED
Resolution
FIXED
Severity
major
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2011-09-19T14:31:00Z
Last change time
2011-10-06T02:25:32Z
Keywords
patch
Assigned to
nobody
Creator
luka8088

Comments

Comment #0 by luka8088 — 2011-09-19T14:31:54Z
import std.stdio; immutable struct a { b b1; } struct b { void c () immutable { writeln(typeid(typeof(this))); // b instead of immutable(b) } } void main () { a a1; a1.b1.c(); }
Comment #1 by schveiguy — 2011-09-20T05:53:59Z
This is only a problem with the expression typeof(this), the real reference 'this' actually *is* immutable: simplified example: import std.stdio; struct b { void c () immutable { writeln(typeid(typeof(this))); // b auto x = this; writeln(typeid(typeof(x))); // immutable(b) } } void main() { immutable b b1; b1.c(); } However, if you tried to change a member of b, you will get a compiler error. So the type of the this reference inside c() is not simply b, it really is immutable(b). However, typeof(this) is a special expression that the compiler replaces with the actual type of the struct (i.e. struct b). This is so it can be used in static functions and at a declaration level. See the special cases here: http://www.d-programming-language.org/declaration.html#Typeof However, I think it is extremely unintuitive to make that happen inside a member function as well. I'm not sure if this is an enhancement, it's not exactly clear from the spec that typeof(this) should still be a special case *inside* a member function. It says it should be the same as if it were inside a member function, it doesn't say *what kind* of member function. At the very least, this is a doc bug.
Comment #2 by luka8088 — 2011-09-20T08:19:42Z
The actual problem is: import std.stdio; immutable struct a { b b1; } struct b { /* void c () { } case 1 => Error: function b.c () is not callable using argument types () immutable void c () immutable { } case 2 => Error: function b.c () immutable is not callable using argument types () */ void c () { } } void main () { a a1; b b1; a1.b1.c(); // case 1 b1.c(); // case 2 } Lets say there is a struct b that can be independent and is mutable. On the other hand we have struct a that has b immutable, by some reason immutable is not inherited by void c () { }, so by trying to call a1.b1.c(); compiler throws Error: function b.c () is not callable using argument types () immutable I also tried a work-around by making void c () immutable { }, but then independent usage of b throws Error: function b.c () immutable is not callable using argument types () The reason for trying typeof(this) is I was trying to make two functions: void c () if (is(typeof(this) == b)) { } void c () if (is(typeof(this) == immutable(b))) immutable { } but since typeof(this) does not indicate immutable I found no work-around. Until this is resolved, is there any work-around for this situation... or am I doing something wrong ?
Comment #3 by schveiguy — 2011-09-20T08:47:22Z
void c() const {} This should work, as mutable and const implicitly cast to immutable. Two other things (even though the above is the *right* solution to your problem): 1. You can overload c, you don't need to use a template 2. You *can* get the exact type called with by using a this template parameter: import std.stdio; struct b { void c(this T)() const { writeln(typeid(T)); } } void main() { immutable b b1; b b2; b1.c(); b2.c(); } output: immutable(testimmutable.b) testimmutable.b But please note, the struct a has nothing to do with these issues, it's just simply a way you have declared an immutable b (which you can easily do on the stack). Declaring an immutable struct is the same as applying immutable to all the members. Declaring a local immutable variable is the same thing. Also note that in your attempt to use if constraints, you must use templates (I'm sure you would have gotten error messages, so I think this is a simple copy-paste omission)
Comment #4 by schveiguy — 2011-09-20T08:48:52Z
(In reply to comment #3) > void c() const {} > > This should work, as mutable and const implicitly cast to immutable. Oh my, that was completely off. Should have read: This should work, as mutable and immutable implicitly cast to const. Sorry...
Comment #5 by luka8088 — 2011-09-20T09:13:20Z
(In reply to comment #3) > void c() const {} > > This should work, as mutable and const implicitly cast to immutable. > yes, it works, thx ! > Declaring an immutable struct is the same as applying immutable to all the > members. Declaring a local immutable variable is the same thing. > so this is a bug or not ... ? // Error: function b.c () is not callable using argument types () immutable import std.stdio; immutable struct a { b b1; } struct b { void c () { } } void main () { a a1; a1.b1.c(); }
Comment #6 by schveiguy — 2011-09-20T10:26:54Z
(In reply to comment #5) > so this is a bug or not ... ? > > // Error: function b.c () is not callable using argument types () immutable > > import std.stdio; > > immutable struct a { > b b1; > } > > struct b { > void c () { } > } > > void main () { > a a1; > a1.b1.c(); > } Not a bug. Inside the c function, 'this' is mutable, and you are trying to pass an immutable struct as the 'this' parameter. Immutable cannot implicitly cast to mutable, so it fails to compile. You should read up on the spec documentation about const and immutable, it will help to understand it.
Comment #7 by luka8088 — 2011-09-20T10:59:37Z
(In reply to comment #6) > (In reply to comment #5) > > so this is a bug or not ... ? > > > > // Error: function b.c () is not callable using argument types () immutable > > > > import std.stdio; > > > > immutable struct a { > > b b1; > > } > > > > struct b { > > void c () { } > > } > > > > void main () { > > a a1; > > a1.b1.c(); > > } > > Not a bug. Inside the c function, 'this' is mutable, and you are trying to > pass an immutable struct as the 'this' parameter. Immutable cannot implicitly > cast to mutable, so it fails to compile. > > You should read up on the spec documentation about const and immutable, it will > help to understand it. I agree that maybe this is not a bug, but I don't agree with the explanation ... Documentation says "Both immutable and const are transitive, which means that any data reachable through an immutable reference is also immutable, and likewise for const." So in this example: import std.stdio; immutable struct a { b b1; } struct b { immutable void c () { } void d () { } immutable int e ; int f ; } void main () { a a1; b b1; writeln(" b1.c ", typeid(typeof(b1.c))); // immutable(void()) writeln(" b1.d ", typeid(typeof(b1.d))); // void() writeln(" b1.e ", typeid(typeof(b1.e))); // immutable(int) writeln(" b1.f ", typeid(typeof(b1.f))); // int writeln("a1.b1.c ", typeid(typeof(a1.b1.c))); // immutable(void()) writeln("a1.b1.d ", typeid(typeof(a1.b1.d))); // void() writeln("a1.b1.e ", typeid(typeof(a1.b1.e))); // immutable(int) writeln("a1.b1.f ", typeid(typeof(a1.b1.f))); // immutable(int) } From the documentation, both typeid(typeof(a1.b1.d)) and typeid(typeof(a1.b1.f)) should be either mutable or immutable, right ?
Comment #8 by schveiguy — 2011-09-20T11:42:19Z
(In reply to comment #7) > I agree that maybe this is not a bug, but I don't agree with the explanation > ... > > Documentation says "Both immutable and const are transitive, which means that > any data reachable through an immutable reference is also immutable, and > likewise for const." Note the key element here is *data*, not *functions*. > So in this example: [snip] > From the documentation, both > typeid(typeof(a1.b1.d)) > and > typeid(typeof(a1.b1.f)) > should be either mutable or immutable, right ? typeid(typeof(a1.b1.d)) is a method, not a piece of data. In fact, when you say typeof(a1.b1.d), you might as well have written typeof(b.d). There is a confusing concept to grasp, and it's made harder because of the terminology. An immutable function is not actually immutable. An immutable function is a member function that *can be called* on an immutable instance of the struct/class. The immutable modifier only affects the 'this' pointer, even though it seems like it's applied to the entire function. Remember that all member functions have a hidden this pointer? If we were forced to write them out, then this: struct S { void foo () immutable {} void foo2() const {} void foo3() {} } becomes this: struct S { void foo (ref immutable(S) this) {} void foo2(ref const(S) this) {} void foo3(ref S this) {} } Now you can see why its clear you cannot call for instance foo with a non-immutable instance of S. The reason the modifiers are applied to the function is because there's no parameter for them to cling to -- the parameter is hidden. If you want to discuss this more, I think the d.learn newsgroup is probably best, or you can email me directly. I'd be happy to answer any questions on it.
Comment #9 by luka8088 — 2011-09-20T12:16:01Z
(In reply to comment #8) > (In reply to comment #7) > > I agree that maybe this is not a bug, but I don't agree with the explanation > > ... > > > > Documentation says "Both immutable and const are transitive, which means that > > any data reachable through an immutable reference is also immutable, and > > likewise for const." > > Note the key element here is *data*, not *functions*. > > > So in this example: > > [snip] > > > From the documentation, both > > typeid(typeof(a1.b1.d)) > > and > > typeid(typeof(a1.b1.f)) > > should be either mutable or immutable, right ? > > typeid(typeof(a1.b1.d)) is a method, not a piece of data. In fact, when you > say typeof(a1.b1.d), you might as well have written typeof(b.d). > > There is a confusing concept to grasp, and it's made harder because of the > terminology. An immutable function is not actually immutable. An immutable > function is a member function that *can be called* on an immutable instance of > the struct/class. The immutable modifier only affects the 'this' pointer, even > though it seems like it's applied to the entire function. > > Remember that all member functions have a hidden this pointer? If we were > forced to write them out, then this: > > struct S > { > void foo () immutable {} > void foo2() const {} > void foo3() {} > } > > becomes this: > > struct S > { > void foo (ref immutable(S) this) {} > void foo2(ref const(S) this) {} > void foo3(ref S this) {} > } > > Now you can see why its clear you cannot call for instance foo with a > non-immutable instance of S. The reason the modifiers are applied to the > function is because there's no parameter for them to cling to -- the parameter > is hidden. > > If you want to discuss this more, I think the d.learn newsgroup is probably > best, or you can email me directly. I'd be happy to answer any questions on > it. OK, I understand it now, you are right, thank you very much for detailed explanation !
Comment #10 by k.hara.pg — 2011-10-05T17:46:52Z
This issue is not limited only typeof(this). struct S { int x; void f() immutable { static assert(is(typeof(x) == immutable(int))); // fails! static assert(is(typeof(x) == int)); // pass, but it is fake } } Because typeof(x) inside immutable member function f is translated to typeof(this.x), and `this` is specially typed as S, then typeof returns int.
Comment #11 by k.hara.pg — 2011-10-06T00:04:33Z
Comment #12 by bugzilla — 2011-10-06T02:25:32Z