www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Calling members of null pointers-to-struct

reply Tor Myklebust <tmyklebu csclub.uwaterloo.ca> writes:
Why is this illegal in D?  As I understand it (from discussion with 
GregorR on IRC), methods of a D struct are not virtual --- the following 
program cannot use vtables as an excuse for its AssertError.


struct foo {
  int doit(int x) { return 42; }
}
void main() {
  foo *bar;  bar.doit(9);
}


Neither does the following slightly less trivial code (note that null 
pointers passed as the 'this' argument aren't dereferenced by struct 
list's methods):


struct list {
  list *next;
  int data;

  static list *make(list *foo, int bar) {
    list *x = new list();
    x.next = foo; x.data = bar;
    return x;
  }

  list *put(int k) {
    return make(this, k);
  }

  bool has(int k) {
    if (this == null) return false;
    return data == k || next.has(k);
  }
}

void main() {
  list *foo;
  foo = foo.put(42);
  foo = foo.put(17);
  foo = foo.put(31345);
  assert(foo.has(42));
  assert(foo.has(17));
  assert(foo.has(31345));
  assert(!foo.has(24));
}


(Except for one minor detail about foo not being initialised to null by 
default, this is perfectly good C++ code; calling a nonvirtual method of 
a null pointer-to-struct simply results in the 'this' parameter coming 
out to be null.  So, like, why doesn't D do the same?)


Tor Myklebust
May 06 2007
next sibling parent reply James Dennett <jdennett acm.org> writes:
Tor Myklebust wrote:
 Why is this illegal in D?  As I understand it (from discussion with 
 GregorR on IRC), methods of a D struct are not virtual --- the following 
 program cannot use vtables as an excuse for its AssertError.

[snip]
 (Except for one minor detail about foo not being initialised to null by 
 default, this is perfectly good C++ code; calling a nonvirtual method of 
 a null pointer-to-struct simply results in the 'this' parameter coming 
 out to be null.  So, like, why doesn't D do the same?)

It's technically invalid in C++ also unless the function is a static member function. The fact that most compilers allow it to run means nothing; in a portably valid C++ program, the value of the "this" pointer is never null. If D requires this to be diagnosed in at least some modes, that would be a minor improvement on C++ (which leaves this as undefined, largely for performance reasons). -- James
May 06 2007
parent reply Tor Myklebust <tmyklebu csclub.uwaterloo.ca> writes:
James Dennett <jdennett acm.org> wrote:
 Tor Myklebust wrote:
 Why is this illegal in D?  As I understand it (from discussion with 
 GregorR on IRC), methods of a D struct are not virtual --- the following 
 program cannot use vtables as an excuse for its AssertError.

[snip]
 (Except for one minor detail about foo not being initialised to null by 
 default, this is perfectly good C++ code; calling a nonvirtual method of 
 a null pointer-to-struct simply results in the 'this' parameter coming 
 out to be null.  So, like, why doesn't D do the same?)

It's technically invalid in C++ also unless the function is a static member function. The fact that most compilers allow it to run means nothing; in a portably valid C++ program, the value of the "this" pointer is never null. If D requires this to be diagnosed in at least some modes, that would be a minor improvement on C++ (which leaves this as undefined, largely for performance reasons).

OK, what you say about the C++ standard is true; 9.3.2.1 says the this pointer is a pointer to the object on which the function is called, and there can't very well be such a thing if this is null. (I still see no earthly reason why it *should* be invalid for nonvirtual functions --- by extension, I see no earthly reason why it should be invalid for methods in D structs.) Tor Myklebust
May 06 2007
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Tor Myklebust wrote:
 [...] (I still see no
 earthly reason why it *should* be invalid for nonvirtual functions --- 
 by extension, I see no earthly reason why it should be invalid for 
 methods in D structs.)
 
 Tor Myklebust

I suppose one could argue that if it is allowable to call the method with a non-existent instance for context, then there's no reason for making it a member function in the first place. Conversely, the vast majority of member functions are member functions specifically *because* they require the context to be there, where the assertion makes sense to have. Of course, since all D ever gives is "ZOMG! ASSERT ERRORZ!" and neither tells you *where* it happened[1], or on what, it's not very useful for more than telling you you screwed up somewhere. Thank $DEITY for ddbg, though. -- Daniel [1] Ok, it tells you the line of the function being called, but all things considered, that isn't very useful. Oh for the day we get back-traces in the standard library :) -- int getRandomNumber() { return 4; // chosen by fair dice roll. // guaranteed to be random. } http://xkcd.com/ v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 06 2007
parent Tor Myklebust <tmyklebu csclub.uwaterloo.ca> writes:
Daniel Keep <daniel.keep.lists gmail.com> wrote:
 
 
 Tor Myklebust wrote:
 [...] (I still see no
 earthly reason why it *should* be invalid for nonvirtual functions --- 
 by extension, I see no earthly reason why it should be invalid for 
 methods in D structs.)
 
 Tor Myklebust

I suppose one could argue that if it is allowable to call the method with a non-existent instance for context, then there's no reason for making it a member function in the first place. Conversely, the vast majority of member functions are member functions specifically *because* they require the context to be there, where the assertion makes sense to have.

It doesn't make sense, though, which was precisely the point of the original post. I even gave an example demonstrating why it doesn't make sense; it is reproduced below: struct list { list *next; int data; static list *make(list *foo, int bar) { list *x = new list(); x.next = foo; x.data = bar; return x; } list *put(int k) { return make(this, k); } bool has(int k) { if (this == null) return false; return data == k || next.has(k); } } void main() { list *foo; foo = foo.put(42); foo = foo.put(17); foo = foo.put(31345); assert(foo.has(42)); assert(foo.has(17)); assert(foo.has(31345)); assert(!foo.has(24)); } This is a toy implementation of a linked list representing a set of ints. The empty list is represented by a null pointer. A nonempty list is represented by a null-terminated, er, list of struct lists. It makes sense for put() to be a member function because put() needs to know what 'this' is. It doesn't need to know about any of the members of 'this', though, so it doesn't need to dereference 'this'. It also makes sense for 'has' to be a member function, since it needs to know about 'data' and 'has'. (Neither one is needed if 'this' is null, however, since null represents the empty list and the empty list contains nothing.) Thus, in neither method does it make sense for a not-null assertion to be made but in both methods the context provided by 'this' is required. Tor Myklebust
May 07 2007
prev sibling parent BCS <ao pathlink.com> writes:
Reply to Tor,

 Why is this illegal in D?  As I understand it (from discussion with
 GregorR on IRC), methods of a D struct are not virtual --- the
 following program cannot use vtables as an excuse for its AssertError.
 
 struct foo {
 int doit(int x) { return 42; }
 }
 void main() {
 foo *bar;  bar.doit(9);
 }

try this
 foo *bar= cast(foo*)(null+1);  bar.doit(9);

I was found this trick because I had a function that needed to be called as a delegate and I didn't want the overhead of doing the nested call trick. p.s. Wow, 2 (strange things I was working on that other people bring up) in one day! The other is label variables.
May 06 2007