www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Hiding class pointers -- was it a good idea?

reply Bill Baxter <dnewsgroup billbaxter.com> writes:
I'm starting to seriously wonder if it was a good idea to hide the 
pointers to classes.  It seemed kinda neat when I was first using D that 
I could avoid typing *s all the time.  But as time wears on it's 
starting to seem more like a liability.

Bad points:
- Harder to tell whether you're dealing with a pointer or not
   (c.f. the common uninitialized 'MyObject obj;' bug)
- To build on the stack, have to use 'scope'

Good points:
+ No need to type '*' everywhere when you use class objects
+ ??? anything else ???


I guess I feel like that if D were Java, I'd never see the pointers, 
"Foo foo;" would always mean a heap-allocated thing, and everything 
would be cool. But with D sometimes "Foo foo" means a stack allocated 
thing, and sometimes it doesn't.  I'm starting to think the extra 
cognitive load isn't really worth it.

In large part it all stems from the decision to make classes and structs 
different.  That seems nice in theory, but as time goes on the two are 
becoming more and more alike.  Classes can now be stack-allocated with 
scope.  Structs are supposedly going to get constructors, and maybe even 
destructors.  It kind of makes me think that maybe separating structs 
and classes was a bad idea.  I have yet to ever once say to myself 
"thank goodness structs and classes are different in D!", though plenty 
of times I've muttered "dangit why can't I just get a default copy 
constructor for this class" or "shoot, these static opCalls are 
annoying" or "dang, I wish I could get a weak reference to a struct", etc.

--bb
Aug 15 2007
next sibling parent Jascha Wetzel <"[firstname]" mainia.de> writes:
Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the 
 pointers to classes.  It seemed kinda neat when I was first using D that 
 I could avoid typing *s all the time.  But as time wears on it's 
 starting to seem more like a liability.
 
 Bad points:
 - Harder to tell whether you're dealing with a pointer or not
   (c.f. the common uninitialized 'MyObject obj;' bug)
 - To build on the stack, have to use 'scope'
 
 Good points:
 + No need to type '*' everywhere when you use class objects
 + ??? anything else ???
 
 
 I guess I feel like that if D were Java, I'd never see the pointers, 
 "Foo foo;" would always mean a heap-allocated thing, and everything 
 would be cool. But with D sometimes "Foo foo" means a stack allocated 
 thing, and sometimes it doesn't.  I'm starting to think the extra 
 cognitive load isn't really worth it.
 
 In large part it all stems from the decision to make classes and structs 
 different.  That seems nice in theory, but as time goes on the two are 
 becoming more and more alike.  Classes can now be stack-allocated with 
 scope.  Structs are supposedly going to get constructors, and maybe even 
 destructors.  It kind of makes me think that maybe separating structs 
 and classes was a bad idea.  I have yet to ever once say to myself 
 "thank goodness structs and classes are different in D!", though plenty 
 of times I've muttered "dangit why can't I just get a default copy 
 constructor for this class" or "shoot, these static opCalls are 
 annoying" or "dang, I wish I could get a weak reference to a struct", etc.
 
 --bb

i agree. D's rules for this are more complex than C++'s and their advantage is questionable. besides, not having to type *'s (everywhere except in uninitialized declarations) is a separate feature. we could have C++-like behaviour and still use the implicit * deduction.
Aug 15 2007
prev sibling next sibling parent reply Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
Bill Baxter wrote:
 In large part it all stems from the decision to make classes and structs
 different.  That seems nice in theory, but as time goes on the two are
 becoming more and more alike.  Classes can now be stack-allocated with
 scope.  Structs are supposedly going to get constructors, and maybe even
 destructors.  It kind of makes me think that maybe separating structs
 and classes was a bad idea.  I have yet to ever once say to myself
 "thank goodness structs and classes are different in D!", though plenty
 of times I've muttered "dangit why can't I just get a default copy
 constructor for this class" or "shoot, these static opCalls are
 annoying" or "dang, I wish I could get a weak reference to a struct", etc.
 

I think the separation was a good idea. I like structs not having hidden fields, being laid out in memory exactly as I write the code, and having no performance overhead due to vtables, etc. In C++, the only difference is that structs default to public while classes default to private. Where's the use in that? Just use a class for everything - and most people do. I like my POD datatype, thank you very much. With that said, I agree that hiding class pointers may not have been a good idea. Still, I can't think of many points which would make it a particularly bad idea. Defaulting to stack allocation somehow would be better: it's easy to write "auto" instead of "scope" and forget about it. But then, the performance in such cases rarely matters, and when it does, you do notice such things because you're looking for them. "Myobject obj;" being uninitialized is supposed to be a feature, not a bug, just like "T t;" should be explicitly initialized for every T. The idea is that if you forget to initialize something you get an easy-to-find error: all chars are initialized to 0xff, so if you're wondering why you're getting running into lots of 0xff in some output, you look for uninitialized chars. Ditto for floating point and NaNs. Integers don't have an "uninitialized" value so they go to zero. IMHO this should perhaps have been made T.min or T.max instead of zero, as zero is so common an initializer that people forget the reason why variables are initialized by default. Walter said that even he's guilty of sometimes leaving integers uninitialized when they should be initialized to zero, and I do it too, but that doesn't mean that's how it should be done. Initializers are present to catch errors by making code fail hard (as in the "Object o;" case) instead of silently (as in if you type "int x;" in C/C++ and proceed to increment or decrement x). -- Remove ".doesnotlike.spam" from the mail address.
Aug 15 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Deewiant wrote:
 Bill Baxter wrote:
 In large part it all stems from the decision to make classes and structs
 different.  That seems nice in theory, but as time goes on the two are
 becoming more and more alike.  Classes can now be stack-allocated with
 scope.  Structs are supposedly going to get constructors, and maybe even
 destructors.  It kind of makes me think that maybe separating structs
 and classes was a bad idea.  I have yet to ever once say to myself
 "thank goodness structs and classes are different in D!", though plenty
 of times I've muttered "dangit why can't I just get a default copy
 constructor for this class" or "shoot, these static opCalls are
 annoying" or "dang, I wish I could get a weak reference to a struct", etc.

I think the separation was a good idea. I like structs not having hidden fields, being laid out in memory exactly as I write the code, and having no performance overhead due to vtables, etc.

Well, in C++ you only get those things if you declare one or more functions as virtual. D automates flagging functions with 'virtual', so you couldn't do the same thing in D. But you could imagine a D with a 'nonvirtual' keyword that if stuck into a class would make it POD.
 In C++, the only difference is that structs
 default to public while classes default to private. Where's the use in that?

Very little point in it, I agree. C++ could have just stuck with 'struct' and been just fine. Part of my point was that maybe we didn't really need two different things in D either.
 Just use a class for everything - and most people do. I like my POD datatype,
 thank you very much.

I like my POD datatypes too. And I like the fact that in C++ I can change from POD to not-POD by adding one little word to the class/struct ("virtual"). Almost all code that relies on that POD type will still work if it gains a vtable and one or more virtual methods. In D it's much more difficult. static opCalls need to be changed to this() constructors, and every bit of code that uses the thing will have to be changed -- mostly Foo*'s will need to be changed to Foo's. But some places may be better off changing to scope Foo. So you need to look closely at the code you're changing.
 With that said, I agree that hiding class pointers may not have been a good
 idea. Still, I can't think of many points which would make it a particularly
bad
 idea.

I guess the main down side is just the inconsistency of value/ptr usages -- Foo/Foo* on the one hand and scope Foo/Foo on the other. It's easy to get used to lack of pointers in Java because it's consistent. But in D you have to go back and forth between C-like and Java-like line-by-line in your code. Put it this way, if I download someone's container library, I don't want to have to think about whether their CoolSet is a class or a struct in order to create one by value. Note that built-in containers are basically struct-like, so in some ways it makes sense for user defined containers to be structs, too. And C++ STL containers are almost always used by-value (i.e. you almost never see "new std::vector()"). But on the other hand a container may want to use interfaces or inheritance in the implementation, and writing static opCalls is just a pain, so it also also makes sense to use a class. The fact that it could reasonably be either class or struct means there's one more bit of information I have to keep in mind for every user-defined type in my code.
 Defaulting to stack allocation somehow would be better: it's easy to write
 "auto" instead of "scope" and forget about it. But then, the performance in
such
 cases rarely matters, and when it does, you do notice such things because
you're
 looking for them.

I don't think the performance is as much of an issue as consistency. --bb
Aug 15 2007
parent reply Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
Bill Baxter wrote:
 I like my POD datatypes too.  And I like the fact that in C++ I can change
 from POD to not-POD by adding one little word to the class/struct 
 ("virtual").  Almost all code that relies on that POD type will still work if
 it gains a vtable and one or more virtual methods.  In D it's much more
 difficult.  static opCalls need to be changed to this() constructors, and
 every bit of code that uses the thing will have to be changed -- mostly
 Foo*'s will need to be changed to Foo's.  But some places may be better off
 changing to scope Foo.  So you need to look closely at the code you're
 changing.
 

You're right, explicit virtual helps. Classes could default to virtual, and structs to non-virtual. Now _that_ would be handy. Newbies or people who don't care about this low-level stuff could just use classes for everything as it is now, but struct use would be simpler.
 Put it this way, if I download someone's container library, I don't want to
 have to think about whether their CoolSet is a class or a struct in order to
 create one by value.

This I agree with. Even type inference doesn't solve this problem. What I like about C++ is that "new" means "allocate on the heap". In D, you have to add "unless storing as 'scope'".
 I don't think the performance is as much of an issue as consistency.

I don't see the consistency problem between stack and heap allocation. In fact, upon further reflection, what heap allocation has going for it is that it's more consistent: you can do "Obj o = new Obj" followed by a "return o", and it works, just as you can do "int x = 5" followed by "return x". If stack allocation were the default, "return o" would be a problem, because it's a pointer to the now-invalid stack. -- Remove ".doesnotlike.spam" from the mail address.
Aug 15 2007
next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Deewiant wrote:
 I don't see the consistency problem between stack and heap allocation.
 
 In fact, upon further reflection, what heap allocation has going for it is that
 it's more consistent: you can do "Obj o = new Obj" followed by a "return o",
and
 it works, just as you can do "int x = 5" followed by "return x". If stack
 allocation were the default, "return o" would be a problem, because it's a
 pointer to the now-invalid stack.

...unless storing as 'scope'. But anyway, I'm not sure what you mean: if by-value were the default, then 'return o' would return a copy of 'o' not a pointer to it. And even if it did return a pointer to it, it's no different than trying to return a pointer to a scope class, or to any other function-local data. So maybe you mean "more safe" rather than "more consistent"? --bb
Aug 15 2007
parent Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
Bill Baxter wrote:
 Deewiant wrote:
 In fact, upon further reflection, what heap allocation has going for it is 
 that it's more consistent: you can do "Obj o = new Obj" followed by a 
 "return o", and it works, just as you can do "int x = 5" followed by 
 "return x". If stack allocation were the default, "return o" would be a 
 problem, because it's a pointer to the now-invalid stack.

...unless storing as 'scope'. But anyway, I'm not sure what you mean: if by-value were the default, then 'return o' would return a copy of 'o' not a pointer to it.

D'oh! Good point.
 And even if it did return a pointer to it, it's no different than trying to
 return a pointer to a scope class, or to any other function-local data.  So
 maybe you mean "more safe" rather than "more consistent"?
 

In accordance with my "d'oh" above, yes, that's what I meant. ;-) -- Remove ".doesnotlike.spam" from the mail address.
Aug 15 2007
prev sibling parent Sean Kelly <sean f4.ca> writes:
Deewiant wrote:
 
 What I like about C++ is that "new" means "allocate on the heap". In D, you
have
 to add "unless storing as 'scope'".

I'm not sure I agree. Stack allocation from the presence of "scope" is a QOI issue, it's not guaranteed in the spec. All "scope" really says is "destroy what this reference refers to when the reference goes out of scope." Sean
Aug 15 2007
prev sibling next sibling parent Tristam MacDonald <swiftcoder gmail.com> writes:
Possibly because they are not 'pointers to classes' in a strict sense. They
don't necessarily have to be implemented with a pointer (though this will
usually be the case), and they don't support many *unsafe* pointer operations,
such as initialisation to anything (only to objects or null), or arithmetic.

Bill Baxter Wrote:
 I'm starting to seriously wonder if it was a good idea to hide the 
 pointers to classes.  It seemed kinda neat when I was first using D that 
 I could avoid typing *s all the time.  But as time wears on it's 
 starting to seem more like a liability.
 
 Bad points:
 - Harder to tell whether you're dealing with a pointer or not
    (c.f. the common uninitialized 'MyObject obj;' bug)
 - To build on the stack, have to use 'scope'
 
 Good points:
 + No need to type '*' everywhere when you use class objects
 + ??? anything else ???
 
 
 I guess I feel like that if D were Java, I'd never see the pointers, 
 "Foo foo;" would always mean a heap-allocated thing, and everything 
 would be cool. But with D sometimes "Foo foo" means a stack allocated 
 thing, and sometimes it doesn't.  I'm starting to think the extra 
 cognitive load isn't really worth it.
 
 In large part it all stems from the decision to make classes and structs 
 different.  That seems nice in theory, but as time goes on the two are 
 becoming more and more alike.  Classes can now be stack-allocated with 
 scope.  Structs are supposedly going to get constructors, and maybe even 
 destructors.  It kind of makes me think that maybe separating structs 
 and classes was a bad idea.  I have yet to ever once say to myself 
 "thank goodness structs and classes are different in D!", though plenty 
 of times I've muttered "dangit why can't I just get a default copy 
 constructor for this class" or "shoot, these static opCalls are 
 annoying" or "dang, I wish I could get a weak reference to a struct", etc.
 
 --bb

Aug 15 2007
prev sibling next sibling parent reply Gregor Richards <Richards codu.org> writes:
#1 advantage of making them always by-value: Ridiculously inconsistent 
use of by-value passing, ref passing and pointer passing.

void foo(Thing a);
void foo(ref Thing a);
void foo(Thing *a);

Wait, that's not an advantage at all, that's C++.

  - Gregor Richards
Aug 15 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Gregor Richards wrote:
 #1 advantage of making them always by-value: Ridiculously inconsistent 
 use of by-value passing, ref passing and pointer passing.
 
 void foo(Thing a);
 void foo(ref Thing a);
 void foo(Thing *a);
 
 Wait, that's not an advantage at all, that's C++.

I don't get your point. Each of those things has different uses in C++. // if Thing is small/PoD and you don't want changes to affect caller void foo(Thing a); // if Thing is bigger, you want to allow caller-visible changes, and you require calling with non-null void foo(ref Thing a); // same as above, but you want to allow null too void foo(Thing *a); The ref-can't-be-null thing may not hold for D, but it's true of C++. --bb
Aug 15 2007
next sibling parent reply Gregor Richards <Richards codu.org> writes:
Bill Baxter wrote:
 Gregor Richards wrote:
 #1 advantage of making them always by-value: Ridiculously inconsistent 
 use of by-value passing, ref passing and pointer passing.

 void foo(Thing a);
 void foo(ref Thing a);
 void foo(Thing *a);

 Wait, that's not an advantage at all, that's C++.

I don't get your point. Each of those things has different uses in C++. // if Thing is small/PoD and you don't want changes to affect caller void foo(Thing a); // if Thing is bigger, you want to allow caller-visible changes, and you require calling with non-null void foo(ref Thing a); // same as above, but you want to allow null too void foo(Thing *a); The ref-can't-be-null thing may not hold for D, but it's true of C++. --bb

This just makes writing code more complicated. It's difficult to remember whether some function wants a reference or a pointer, and you have to be careful about how you treat them because they're fundamentally different (see the operator overloading post). The advantage is virtually nil, and it would create this huge complication. That's why it's terrible in C++, and that's why it would be terrible in D. - Gregor Richards
Aug 15 2007
next sibling parent Regan Heath <regan netmail.co.nz> writes:
Gregor Richards wrote:
 Bill Baxter wrote:
 Gregor Richards wrote:
 #1 advantage of making them always by-value: Ridiculously 
 inconsistent use of by-value passing, ref passing and pointer passing.

 void foo(Thing a);
 void foo(ref Thing a);
 void foo(Thing *a);

 Wait, that's not an advantage at all, that's C++.

I don't get your point. Each of those things has different uses in C++. // if Thing is small/PoD and you don't want changes to affect caller void foo(Thing a); // if Thing is bigger, you want to allow caller-visible changes, and you require calling with non-null void foo(ref Thing a); // same as above, but you want to allow null too void foo(Thing *a); The ref-can't-be-null thing may not hold for D, but it's true of C++. --bb

This just makes writing code more complicated. It's difficult to remember whether some function wants a reference or a pointer, and you have to be careful about how you treat them because they're fundamentally different (see the operator overloading post). The advantage is virtually nil, and it would create this huge complication. That's why it's terrible in C++, and that's why it would be terrible in D.

We have the same problem in D with value types like 'int'. Pass by reference has always bothered me. It hides what's going on at the call site, eg. int a; foo(a); bar(a); which one modifies a? Then there is the fact that you cannot pass null, meaning in some cases creating and passing a dummy parameter for an 'out' which you don't actually want. I actually quite like pointers myself but I like D's arrays more! They're passed by value or by reference and you can always pass null. If only classes could work in the same way! Regan
Aug 15 2007
prev sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Gregor Richards wrote:
 Bill Baxter wrote:
 Gregor Richards wrote:
 #1 advantage of making them always by-value: Ridiculously 
 inconsistent use of by-value passing, ref passing and pointer passing.

 void foo(Thing a);
 void foo(ref Thing a);
 void foo(Thing *a);

 Wait, that's not an advantage at all, that's C++.

I don't get your point. Each of those things has different uses in C++. // if Thing is small/PoD and you don't want changes to affect caller void foo(Thing a); // if Thing is bigger, you want to allow caller-visible changes, and you require calling with non-null void foo(ref Thing a); // same as above, but you want to allow null too void foo(Thing *a); The ref-can't-be-null thing may not hold for D, but it's true of C++. --bb

This just makes writing code more complicated. It's difficult to remember whether some function wants a reference or a pointer, and you have to be careful about how you treat them because they're fundamentally different (see the operator overloading post). The advantage is virtually nil, and it would create this huge complication. That's why it's terrible in C++, and that's why it would be terrible in D.

D functions can also take pointers, values or references, so I don't see that D is really that different in this respect. I guess you mean that, *if* you're talking about a class type, then you can be pretty certain you won't need to call a function using a derefernce like foo(&theThing). Yeh, I guess that is a positive point of hiding pointers. Of course it comes at the expense of making it impossible to pass a class by value. You could also just outlaw pass-by-value for classes, and automatically dereference any value classes passed to functions expecting a class pointer. So I don't think it's strictly necessary to hide the pointer in order to get this benefit you speak of. --bb
Aug 15 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Bill Baxter wrote:
 Of course it comes at the expense of making it impossible to 
 pass a class by value.

That is a deliberate positive feature, not an expense.
Aug 15 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Walter Bright wrote:
 Bill Baxter wrote:
 Of course it comes at the expense of making it impossible to pass a 
 class by value.

That is a deliberate positive feature, not an expense.

Ok. Thanks for all the responses. About slicing: it may be totally evil and impossible to debug when it occurs, but I have to say I can't recall ever being bitten by it in 10 years of C++ programming. But I tend not to go crazy with the polymorphism in the first place, and if I do I guess I've been trained to avoid passing by-value classes around. Just thinking about it triggers my "danger" reflex. Always makes me queasy when I see things like std::string being passed around by value even though I know that most implementations are optimized to avoid copying the payload. And I certainly would never try to assign different by-value classes to one another. So yeh, you've eliminated slicing, but I'm not really convinced it was such a huge problem that it warranted a syntax upheaval in the first place. But I do understand the logic that if you want polymorphic behavior you _have_ to call via a reference or pointer. And if you *always* have to call via reference/pointer, then you might as well make that the default. I think when it comes down to it the only things I'm really going to have left to complain about are missing features in structs: 1) the lack of constructors for structs (going to be fixed), 2) lack of (non-polymorphic) inheritance for structs (not so major), 3) lack of const reference for convenient passing of structs around (from what I understand the current situation in D 2.0 is that the closest you can come is a const pointer to a struct, meaning that every time I call the function I have to remember to dereference the args: rotate(&quat,&vec). Bleh.) Initially I unrealistically thought D's class/struct thing was going to be some kind of super magic bullet, recently it dawned on me that it wasn't panning out that way (hence these posts), and finally I guess I'm coming to the conclusion (thanks for all the input!) that D's class/struct thing is not worse than C++, but isn't necessarily better than C++'s way, either. They both have issues, just the issues are shifted around. And maybe I'll eventually come to view D's way as slightly better -- esp. if the 3 issues above are sorted out ;-). But I've been permanently disabused of my illusions that separating class/struct is some sort of magic cure-all. :-) --bb
Aug 15 2007
next sibling parent Leandro Lucarella <llucax gmail.com> writes:
Bill Baxter, el 16 de agosto a las 10:07 me escribiste:
 So yeh, you've eliminated slicing, but I'm not really convinced it was such a
huge problem that it 
 warranted a syntax upheaval in the first place.

I don't have slicing problem either, but I've seen a lot of code with that problem from novice (and not so novice) programmers, and it was really hard to debug. A place where is fairly common is on exception handling. -- Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/ .------------------------------------------------------------------------, \ GPG: 5F5A8D05 // F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05 / '--------------------------------------------------------------------' El amor es como una reina ortop├ędica. -- Poroto
Aug 15 2007
prev sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Bill Baxter wrote:
 About slicing: it may be totally evil and impossible to debug when it 
 occurs, but I have to say I can't recall ever being bitten by it in 10 
 years of C++ programming.  But I tend not to go crazy with the 
 polymorphism in the first place, and if I do I guess I've been trained 
 to avoid passing by-value classes around.  Just thinking about it 
 triggers my "danger" reflex.  Always makes me queasy when I see things 
 like std::string being passed around by value even though I know that 
 most implementations are optimized to avoid copying the payload.
 And I 
 certainly would never try to assign different by-value classes to one 
 another.

The slicing problem comes about indirectly, not by directly trying to do such assignments.
 So yeh, you've eliminated slicing, but I'm not really convinced it was 
 such a huge problem that it warranted a syntax upheaval in the first place.

It's a well-known problem with C++, inspiring many conventions and idioms to avoid. I haven't myself been bitten by it either - but one of D's aims is to be a robust, reliable and auditable language. Trying to avoid such gaping holes in the type system is very important. Relying on convention simply is not good enough.
 But I do understand the logic that if you want polymorphic behavior you 
 _have_ to call via a reference or pointer.  And if you *always* have to 
 call via reference/pointer, then you might as well make that the default.
 
 I think when it comes down to it the only things I'm really going to 
 have left to complain about are missing features in structs:
 1) the lack of constructors for structs (going to be fixed),
 2) lack of (non-polymorphic) inheritance for structs (not so major),
 3) lack of const reference for convenient passing of structs around 
 (from what I understand the current situation in D 2.0 is that the 
 closest you can come is a const pointer to a struct, meaning that every 
 time I call the function I have to remember to dereference the args: 
 rotate(&quat,&vec).  Bleh.)
 
 
 Initially I unrealistically thought D's class/struct thing was going to 
 be some kind of super magic bullet, recently it dawned on me that it 
 wasn't panning out that way (hence these posts), and finally I guess I'm 
 coming to the conclusion (thanks for all the input!) that D's 
 class/struct thing is not worse than C++, but isn't necessarily better 
 than C++'s way, either.  They both have issues, just the issues are 
 shifted around.   And maybe I'll eventually come to view D's way as 
 slightly better -- esp. if the 3 issues above are sorted out ;-).  But 
 I've been permanently disabused of my illusions that separating 
 class/struct is some sort of magic cure-all.  :-)

There are plans to deal with (1) and (3), and you can do (2) using aggregation instead of inheritance.
Aug 16 2007
prev sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Bill Baxter wrote:
 Each of those things has different uses in C++.

These different uses should be decided by the designer of the class, not the user of the class.
Aug 15 2007
prev sibling next sibling parent reply Gregor Richards <Richards codu.org> writes:
OK, OK, I guess I should respond with an argument from computer science 
as well :)

In the normal definition of Object Orientation, an object is a means of 
storing a context in which operations can be performed. The abstraction 
behind this (yay we're modeling the universe in code but realistically 
we aren't yay) is irrelevant, as fundamentally OO is just a means of 
storing and passing contexts. Because this is a context, it makes no 
sense whatsoever to pass it around with duplication - duplicating 
contexts is nonsense.

structs are sort of a hack for compatibility and/or optimization. They 
are not contexts, they are means of creating more complicated values. 
While a "Point" could be a struct, really being a more complicated 
value, an "NPC" would always be a class, since it is a context.

The fact that this is inconsistent with C++ is irrelevant: D is more to 
the spirit of good OO.

  - Gregor Richards
Aug 15 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Gregor Richards wrote:
 OK, OK, I guess I should respond with an argument from computer science 
 as well :)
 
 In the normal definition of Object Orientation, an object is a means of 
 storing a context in which operations can be performed. The abstraction 
 behind this (yay we're modeling the universe in code but realistically 
 we aren't yay) is irrelevant, as fundamentally OO is just a means of 
 storing and passing contexts. Because this is a context, it makes no 
 sense whatsoever to pass it around with duplication - duplicating 
 contexts is nonsense.
 
 structs are sort of a hack for compatibility and/or optimization. They 
 are not contexts, they are means of creating more complicated values. 
 While a "Point" could be a struct, really being a more complicated 
 value, an "NPC" would always be a class, since it is a context.
 
 The fact that this is inconsistent with C++ is irrelevant: D is more to 
 the spirit of good OO.

You put your finger on the very good reason why polymorphic, inheritable types in D are restricted to being reference types, not value types. OOP requires this characteristic. In C++, an OOP class can be used/misused by the user as a value type or a reference type, all out of the purview of the class designer. The class designer must control this, not the class user.
Aug 15 2007
parent reply James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 Gregor Richards wrote:
 OK, OK, I guess I should respond with an argument from computer
 science as well :)

 In the normal definition of Object Orientation, an object is a means
 of storing a context in which operations can be performed. The
 abstraction behind this (yay we're modeling the universe in code but
 realistically we aren't yay) is irrelevant, as fundamentally OO is
 just a means of storing and passing contexts. Because this is a
 context, it makes no sense whatsoever to pass it around with
 duplication - duplicating contexts is nonsense.

 structs are sort of a hack for compatibility and/or optimization. They
 are not contexts, they are means of creating more complicated values.
 While a "Point" could be a struct, really being a more complicated
 value, an "NPC" would always be a class, since it is a context.

 The fact that this is inconsistent with C++ is irrelevant: D is more
 to the spirit of good OO.

You put your finger on the very good reason why polymorphic, inheritable types in D are restricted to being reference types, not value types. OOP requires this characteristic.

For one, rather restricted, notion of OOP. There are many, many views of what constitutes OOP in the PL community.
 In C++, an OOP class can be used/misused by the user as a value type or
 a reference type, all out of the purview of the class designer. The
 class designer must control this, not the class user.

It's normal in C++ to make "entity" classes (those that you're calling reference types) noncopyable. It's also normal to make base classes abstract. Thus idioms easily prevent the basic misuses. I can't think of any reason why a value type would object to the identity of its objects being used (though functional languages often hide object identities). -- James
Aug 15 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
James Dennett wrote:
 For one, rather restricted, notion of OOP.  There are many,
 many views of what constitutes OOP in the PL community.

The definition I use is a common one, OOP design consists of three characteristics: 1) inheritance 2) polymorphism 3) encapsulation
 In C++, an OOP class can be used/misused by the user as a value type or
 a reference type, all out of the purview of the class designer. The
 class designer must control this, not the class user.

It's normal in C++ to make "entity" classes (those that you're calling reference types) noncopyable. It's also normal to make base classes abstract. Thus idioms easily prevent the basic misuses.

C++ is loaded with idioms and conventions to try and head off major problems. I'd rather snip off such problems at the source - for one reason, it will dramatically reduce the learning curve of the language. For another, the more guarantees the language can offer, the lesser a burden it is on the code auditor, and the more likely the code is to be correct.
Aug 16 2007
next sibling parent reply James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 James Dennett wrote:
 For one, rather restricted, notion of OOP.  There are many,
 many views of what constitutes OOP in the PL community.

The definition I use is a common one, OOP design consists of three characteristics: 1) inheritance 2) polymorphism 3) encapsulation

Then objects are in no way disallowed from having value semantics.
 In C++, an OOP class can be used/misused by the user as a value type or
 a reference type, all out of the purview of the class designer. The
 class designer must control this, not the class user.

It's normal in C++ to make "entity" classes (those that you're calling reference types) noncopyable. It's also normal to make base classes abstract. Thus idioms easily prevent the basic misuses.

C++ is loaded with idioms and conventions to try and head off major problems. I'd rather snip off such problems at the source - for one reason, it will dramatically reduce the learning curve of the language. For another, the more guarantees the language can offer, the lesser a burden it is on the code auditor, and the more likely the code is to be correct.

Valid points, though removing expressive power tends to move the complexity into code, and it's hard to remove the power to write bad code without also removing the power to write great code. -- James
Aug 16 2007
next sibling parent Don Clugston <dac nospam.com.au> writes:
James Dennett wrote:
 Walter Bright wrote:
 James Dennett wrote:
 For one, rather restricted, notion of OOP.  There are many,
 many views of what constitutes OOP in the PL community.

characteristics: 1) inheritance 2) polymorphism 3) encapsulation

Then objects are in no way disallowed from having value semantics.

And there are situations where they can even be true values, without encountering slicing problems. It always irked me that in C++ there's no natural way of having polymorphic value types. (And D hasn't changed this). The case where you have class Base { int a_; int b_; public: virtual int func() { ... } // use a_ and b_ }; class Derived : public Base { virtual int func() { ... } }; And the interesting thing about Derived is that it only adds an interface, not data. It's kind of like a abstract base class, in reverse. An instance of Derived is just a Base, but with a different value in the hidden vtable parameter. If a derived class does not add any data members (or multiple inheritance), then there's never a slicing problem. You can have a container of polymorphic classes, stored as values, with perfect safety. I this is a pretty common scenario, but there's no language support for it. Would be great to be able to say 'any class derived from this class is not permitted to add data' -- abstract derivations only.
Aug 17 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
James Dennett wrote:
 Walter Bright wrote:
 C++ is loaded with idioms and conventions to try and head off major
 problems. I'd rather snip off such problems at the source - for one
 reason, it will dramatically reduce the learning curve of the language.
 For another, the more guarantees the language can offer, the lesser a
 burden it is on the code auditor, and the more likely the code is to be
 correct.

Valid points, though removing expressive power tends to move the complexity into code, and it's hard to remove the power to write bad code without also removing the power to write great code.

The counterexample I'd give is that D classes tend to be implementable with much less complex code than C++ classes.
Aug 17 2007
parent James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 James Dennett wrote:
 Walter Bright wrote:
 C++ is loaded with idioms and conventions to try and head off major
 problems. I'd rather snip off such problems at the source - for one
 reason, it will dramatically reduce the learning curve of the language.
 For another, the more guarantees the language can offer, the lesser a
 burden it is on the code auditor, and the more likely the code is to be
 correct.

Valid points, though removing expressive power tends to move the complexity into code, and it's hard to remove the power to write bad code without also removing the power to write great code.

The counterexample I'd give is that D classes tend to be implementable with much less complex code than C++ classes.

One counterpoint to which is that most complexity in large systems arises at larger scales than individual classes, and then a language that allows you to express more of the properties of a class tends to fare better "in the large". But, as is sadly usual for our field, it's hard to provide robust objective data to support either viewpoint, and we're down to debating based on our (widely varying) experiences. -- James
Aug 18 2007
prev sibling parent reply Alex Burton <alexibu mac.com> writes:
Walter Bright Wrote:

 1) inheritance
 2) polymorphism
 3) encapsulation

Another one in that list should be "reference", which IMO would be assumed by most people, but has been lost with transitive const in D. I would define it as the ability to not encapsulate something a class has a reference to. By making all objects that an object has references to necessarily part of that object, the transitive const feature means that the only state relationship a class can have to another class is encapsulation. Correct me if i'm wrong :)
Aug 17 2007
parent reply Tristam MacDonald <swiftcoder gmail.com> writes:
Agreed, but I can't think of another language that supports this 
definition of OOP (apart from 'mutable' under C++ - which is widely 
regarded as a hack).

Alex Burton wrote:
 Walter Bright Wrote:
 
 1) inheritance
 2) polymorphism
 3) encapsulation

Another one in that list should be "reference", which IMO would be assumed by most people, but has been lost with transitive const in D. I would define it as the ability to not encapsulate something a class has a reference to. By making all objects that an object has references to necessarily part of that object, the transitive const feature means that the only state relationship a class can have to another class is encapsulation. Correct me if i'm wrong :)

Aug 17 2007
next sibling parent Alex Burton <alexibu mac.com> writes:
Tristam MacDonald Wrote:

 Agreed, but I can't think of another language that supports this 
 definition of OOP (apart from 'mutable' under C++ - which is widely 
 regarded as a hack).
 
 Alex Burton wrote:
 By making all objects that an object has references to necessarily part of
that object, the transitive const feature means that the only state
relationship a class can have to another class is encapsulation.
 


And I totally agree that mutable is a hack. But when a class has a *pointer* to another object in C++, I can choose whether to tell the compiler that the other object is part of the class or not. In D all classes are referred to by pointers. And all pointers are assumed to mean part of (because of transitive const). It is perfectly logical to disallow changing objects that are part of a const class. I am having trouble figuring out how I am going to write code when I can't store a pointer to something in a class without it being interpreted as a part of relationship. Clearly in the below code transitive const does not make sense. And does not allow me to describe reality. Which is what I need to do to write sensible code. class Table { Database mDatabase; const const(Data) getData() { mDatabase.lock(); //error cannot change the state of mDatabase even though it can be in no way thought of as part of Table ... mDatabase.unlock(); } };
Aug 17 2007
prev sibling parent James Dennett <jdennett acm.org> writes:
Tristam MacDonald wrote:
 Alex Burton wrote:
 Walter Bright Wrote:

 1) inheritance
 2) polymorphism
 3) encapsulation

Another one in that list should be "reference", which IMO would be assumed by most people, but has been lost with transitive const in D. I would define it as the ability to not encapsulate something a class has a reference to. By making all objects that an object has references to necessarily part of that object, the transitive const feature means that the only state relationship a class can have to another class is encapsulation. Correct me if i'm wrong :)

Agreed, but I can't think of another language that supports this definition of OOP (apart from 'mutable' under C++ - which is widely regarded as a hack).

About every OO-aware language but D supports it; only D (2.0) assumes in its type system that reference to another object always implies a whole-part relationship between them. "mutable" isn't much related to this (and note that widely held opinions are often not shared with informed experts). -- James
Aug 18 2007
prev sibling next sibling parent reply Reiner Pope <some address.com> writes:
Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the 
 pointers to classes.  It seemed kinda neat when I was first using D that 
 I could avoid typing *s all the time.  But as time wears on it's 
 starting to seem more like a liability.
 
 Bad points:
 - Harder to tell whether you're dealing with a pointer or not
   (c.f. the common uninitialized 'MyObject obj;' bug)
 - To build on the stack, have to use 'scope'
 
 Good points:
 + No need to type '*' everywhere when you use class objects
 + ??? anything else ???
 
 
 I guess I feel like that if D were Java, I'd never see the pointers, 
 "Foo foo;" would always mean a heap-allocated thing, and everything 
 would be cool. But with D sometimes "Foo foo" means a stack allocated 
 thing, and sometimes it doesn't.  I'm starting to think the extra 
 cognitive load isn't really worth it.
 
 In large part it all stems from the decision to make classes and structs 
 different.  That seems nice in theory, but as time goes on the two are 
 becoming more and more alike.  Classes can now be stack-allocated with 
 scope.  Structs are supposedly going to get constructors, and maybe even 
 destructors.  It kind of makes me think that maybe separating structs 
 and classes was a bad idea.  I have yet to ever once say to myself 
 "thank goodness structs and classes are different in D!", though plenty 
 of times I've muttered "dangit why can't I just get a default copy 
 constructor for this class" or "shoot, these static opCalls are 
 annoying" or "dang, I wish I could get a weak reference to a struct", etc.
 
 --bb

How about allowing operator overloads on classes which must be treated as reference types? ByValue* a; // can't overload opAdd usefully: assert(a.opAdd(5) != a + 5); ByRef b; // but we can if the pointer is hidden assert(b.opAdd(5) == a + 5); -- Reiner
Aug 15 2007
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Reiner Pope wrote:
 Bill Baxter wrote:
 
 How about allowing operator overloads on classes which must be treated 
 as reference types?
 
 ByValue* a;
 // can't overload opAdd usefully:
 assert(a.opAdd(5) != a + 5);
 
 ByRef b;
 // but we can if the pointer is hidden
 assert(b.opAdd(5) == a + 5);

That's a good point. But it does kind of fall into the category of "no need to type '*' everywhere". You could just say *a + 5. Or in C++ you could use a reference: ByValue&. But yeh, you pretty much need real reference types like in C++ if you don't hide the pointer. Another alternative might be to make a distinction between traditional raw memory pointers and pointers to objects. Then a+5 could automatically dereference 'a' if it's an object pointer. And if you want to do pointer arithmetic you would need some special syntax. But now that I think about it, that 'object pointer' is really just a reference type by another name. It's a pointer that acts like a value by automatically dereferencing when needed. --bb
Aug 15 2007
prev sibling next sibling parent reply Russell Lewis <webmaster villagersonline.com> writes:
I agree, IMHO this was a mistake.  With automatic type deduction, the 
lack of '*' doesn't really save any typing anymore, at least for 
declarations, which (I suspect) are by far the most common use:
	auto temp = new MyClass;

It saves (a tiny bit of) typing on function declarations, and avoids the 
need for a copy constructor, but we could have eliminated copy 
constructors simply by making pass-by-value class arguments to functions 
illegal.

But the worst of it all, IMHO, is what you (and others) have pointed 
out: the lack of clarity about whether a declaration is a 
struct-by-value or class-by-reference.  That means that if a container 
was originally implemented as a struct, you can't ever change it to a 
class, since that would require rewriting all of the code that uses it. 
  Doesn't that violate one of the basic principles of Object Orientation 
- the hiding of implementation?

Russ

Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the 
 pointers to classes.  It seemed kinda neat when I was first using D that 
 I could avoid typing *s all the time.  But as time wears on it's 
 starting to seem more like a liability.
 
 Bad points:
 - Harder to tell whether you're dealing with a pointer or not
   (c.f. the common uninitialized 'MyObject obj;' bug)
 - To build on the stack, have to use 'scope'
 
 Good points:
 + No need to type '*' everywhere when you use class objects
 + ??? anything else ???
 
 
 I guess I feel like that if D were Java, I'd never see the pointers, 
 "Foo foo;" would always mean a heap-allocated thing, and everything 
 would be cool. But with D sometimes "Foo foo" means a stack allocated 
 thing, and sometimes it doesn't.  I'm starting to think the extra 
 cognitive load isn't really worth it.
 
 In large part it all stems from the decision to make classes and structs 
 different.  That seems nice in theory, but as time goes on the two are 
 becoming more and more alike.  Classes can now be stack-allocated with 
 scope.  Structs are supposedly going to get constructors, and maybe even 
 destructors.  It kind of makes me think that maybe separating structs 
 and classes was a bad idea.  I have yet to ever once say to myself 
 "thank goodness structs and classes are different in D!", though plenty 
 of times I've muttered "dangit why can't I just get a default copy 
 constructor for this class" or "shoot, these static opCalls are 
 annoying" or "dang, I wish I could get a weak reference to a struct", etc.
 
 --bb

Aug 15 2007
parent reply Tristam MacDonald <swiftcoder gmail.com> writes:
Russell Lewis Wrote:
 But the worst of it all, IMHO, is what you (and others) have pointed 
 out: the lack of clarity about whether a declaration is a 
 struct-by-value or class-by-reference.  That means that if a container 
 was originally implemented as a struct, you can't ever change it to a 
 class, since that would require rewriting all of the code that uses it. 
   Doesn't that violate one of the basic principles of Object Orientation 
 - the hiding of implementation?

I would tend to disagree. Classes are reference types, structs are value types. When I refactor something from a class to a struct, I *expect* the behavior of function arguments to switch from by-refrence to by-value. Furthermore, since classes *cannot* be passed by value, it makes no sense to have additional syntax, which will lead to begginers writing 'Object o;' instead of 'Object* o;' - which will not compile under your suggestion.
Aug 15 2007
parent reply Russell Lewis <webmaster villagersonline.com> writes:
Tristam MacDonald wrote:
 Russell Lewis Wrote:
 But the worst of it all, IMHO, is what you (and others) have pointed 
 out: the lack of clarity about whether a declaration is a 
 struct-by-value or class-by-reference.  That means that if a container 
 was originally implemented as a struct, you can't ever change it to a 
 class, since that would require rewriting all of the code that uses it. 
   Doesn't that violate one of the basic principles of Object Orientation 
 - the hiding of implementation?


Tristam, your response felt a little "hotter" than I what I prefer. If I sounded a little flamish in my post, I apologize. If I'm misreading you, I also apologize!
 I would tend to disagree. Classes are reference types, structs are value
types. When I refactor something from a class to a struct, I *expect* the
behavior of function arguments to switch from by-refrence to by-value.

I think that you made my point for me...if refactoring requires lots of rewrites, then the use-side of the code has too much insight into the implementation. I don't see any fundamental reason why classes need to be reference types, other than history. (Yes, they are currently all on the heap, even scope variables, but even that is a decision that I could change, if there was a reason for it.) That's MHO, anyhow.
 Furthermore, since classes *cannot* be passed by value, it makes no sense to
have additional syntax, which will lead to begginers writing 'Object o;'
instead of 'Object* o;' - which will not compile under your suggestion.

What's wrong with Object o; ? If I had a magic wand, I would declare that syntax to be equivalent to the current syntax: scope Object o = new Object; or, better, to make it a stack variable. So the current implementation requires "extra syntax" as well (for scope declarations). It's a question of which extra syntax is better. IMHO, making classes consistent with structs is more desirable than eliminating a few stray asteriskes. The beginners who stumble across the "can't pass a class as a by-value function parameter" rule can be easily educated with a well-written error message emitted by the compiler. I don't have a magic wand, and I don't want everybody to have to rewrite their old code. But I continue to hope that this might change in 2.0, or maybe 3.0. :)
Aug 15 2007
parent reply Johan Granberg <lijat.meREM OVEgmail.com> writes:
Russell Lewis wrote:
 I don't see any fundamental reason why classes need to
 be reference types, other than history.

What about this situation. //begin C++ class A{ int val; }; class B:public A{ int foo; }; int main(int argc,char**argvs){ A a; B b; a=b;//HERE what happens to b's member foo? } //end C++ it's my impression that D's classes are reference types to avoid that specific problem.
Aug 15 2007
next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Johan Granberg wrote:
 Russell Lewis wrote:
 I don't see any fundamental reason why classes need to
 be reference types, other than history.

What about this situation. //begin C++ class A{ int val; }; class B:public A{ int foo; }; int main(int argc,char**argvs){ A a; B b; a=b;//HERE what happens to b's member foo? } //end C++ it's my impression that D's classes are reference types to avoid that specific problem.

That's known as the 'slicing' problem. It's pernicious in that it can be extremely hard to expose via testing or code reviews, yet will expose the program to unpredictable behavior. While it is fairly obvious in your example that it is wrong, it can occur in cases that are *impossible* for the compiler to detect: void foo(A* a) { A ax = *a; } void main() { B b; foo(&b); } This kind of error is impossible in D.
Aug 15 2007
next sibling parent reply James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 Johan Granberg wrote:
 Russell Lewis wrote:
 I don't see any fundamental reason why classes need to
 be reference types, other than history.

What about this situation. //begin C++ class A{ int val; }; class B:public A{ int foo; }; int main(int argc,char**argvs){ A a; B b; a=b;//HERE what happens to b's member foo? } //end C++ it's my impression that D's classes are reference types to avoid that specific problem.

That's known as the 'slicing' problem. It's pernicious in that it can be extremely hard to expose via testing or code reviews, yet will expose the program to unpredictable behavior.

It's trivially detected by various automated tools, which can flag any non-abstract base class. (Such classes almost invariably indicate bad design in any case.) Clearly it would be simple for a compiler to detect when a concrete class was used as a base class. There's no need to remove value semantics in order to solve this problem; it's something of a sledgehammer solution. -- James
Aug 15 2007
next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
James Dennett Wrote:

 It's trivially detected by various automated tools, which
 can flag any non-abstract base class.  (Such classes almost
 invariably indicate bad design in any case.)  

Now that's just patently untrue. I've never done any serious work in C++ outside of school, so perhaps that's a belief some people hold in the C++ world, but I doubt it's a very common one. I'd say it's about an 80-20 split for code I write and 60-40 in standard libraries. That is, 80% of the time I'm extending something I am indeed extending an abstract base, but 20% of the time I'm extending a concrete class is helpful, and in the case of libraries you can't change, necessary. Here's a real-world example. At work recently (I don't think this violates my NDA...) I was asked to write a fake POP & IMAP server (it gives a certain number of new messages per hour to every account for load testing) in Java. I have a class UserAcct which is used to track persistent accounts so that UIDs remain contiguous. When I added support for IMAP idle, some additional information was needed to handle idling subscriptions/send events/etc. My options were to either create an "imap idle subscription" class to wrap a subscription with a pointer to the UserAcct class (composition) or to extend the UserAcct with a SubscribedUserAcct (only a small fraction of the users would have IMAP idle enabled), or just to add some extra fields to UserAcct. I selected the second option (extension) here. Arguably, composition may have been the better choice from a design standpoint, and I certainly respect this opinion. However: - Composition removes polymorphism: The obvious. If I needed to override or change a function of the base class, this would not be an option with composition. In addition, were I using a wrapper class, I copuld no longer keep a map of "UserAcct" objects, I would need two different data structures, one of the wrapper and one of the accounts. - Composition costs additional memory: the extra 8 bytes (in Java and D, not sure about C++) overhead of the additional object, plus another 4 for the reference (actually, that averages to 6 because the Sun JVM likes to align classes on 8-byte word boundaries). Since I had recently run into some OutOfMemory errors when it was loaded with 50,000+ accounts, I was loath to incur much additional heap usage than I had to. Anyways, this is getting a bit off-topic. However a sweeping statement like "all base classes should be abstract" just seems very wrong to me. I personally think D's behavior is perfect, though I think non-polymorphic struct inheritance (i.e. as syntactic sugar for mixins) and constructors would be nice. The distinction, IMO, shows D to be a more mature language with different use-cases for structs and classes clearly defined. That, and you've mentioned specific idioms in a few of your messages. Idioms are great, but not everybody follows them (especially people new to a language). Making something part of the language standardizes it. That's why having unittests, asserts, preconditions, etc., in D is such a boon: while that could all be done using a library, there'd probably be three or four different libraries out there to do it and 75% of users wouldn't know about them or use those features at all.
Aug 16 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Robert Fraser wrote:
 Making something part of the language
 standardizes it. That's why having unittests, asserts, preconditions,
 etc., in D is such a boon: while that could all be done using a
 library, there'd probably be three or four different libraries out
 there to do it and 75% of users wouldn't know about them or use those
 features at all.

Right on.
Aug 16 2007
parent James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 Robert Fraser wrote:
 Making something part of the language
 standardizes it. That's why having unittests, asserts, preconditions,
 etc., in D is such a boon: while that could all be done using a
 library, there'd probably be three or four different libraries out
 there to do it and 75% of users wouldn't know about them or use those
 features at all.

Right on.

It's true; one problem with C++ is that there is too much diversity in the (huge) set of idioms and libraries, and standardization has many benefits (which is why it's something I work on). Sometimes it *can* even be better to have one preferred approach to something even if it's not technically the greatest, for the other benefits that it can bring. With the complexity of C++, it's a real problem finding developers with enough knowledge to use it safely and efficiently (more so than for C, in my experience, though the best C++ runs rings around C). Simplicity and uniformity has great merit, but does require taking calculated risks and making trade-offs. -- James
Aug 16 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
James Dennett wrote:
 Walter Bright wrote:
 That's known as the 'slicing' problem. It's pernicious in that it can be
 extremely hard to expose via testing or code reviews, yet will expose
 the program to unpredictable behavior.

It's trivially detected by various automated tools, which can flag any non-abstract base class. (Such classes almost invariably indicate bad design in any case.) Clearly it would be simple for a compiler to detect when a concrete class was used as a base class. There's no need to remove value semantics in order to solve this problem; it's something of a sledgehammer solution.

That's true if you agree with the notion that only abstract classes should be used as base classes, which I do not agree with.
Aug 16 2007
parent James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 James Dennett wrote:
 Walter Bright wrote:
 That's known as the 'slicing' problem. It's pernicious in that it can be
 extremely hard to expose via testing or code reviews, yet will expose
 the program to unpredictable behavior.

It's trivially detected by various automated tools, which can flag any non-abstract base class. (Such classes almost invariably indicate bad design in any case.) Clearly it would be simple for a compiler to detect when a concrete class was used as a base class. There's no need to remove value semantics in order to solve this problem; it's something of a sledgehammer solution.

That's true if you agree with the notion that only abstract classes should be used as base classes, which I do not agree with.

Then you lose benefits of static type checking for operations such as equality comparisons, unless you have ugly special cases. How does D handle this? Guess I'll go look it up. -- James
Aug 16 2007
prev sibling parent reply Russell Lewis <webmaster villagersonline.com> writes:
Walter Bright wrote:
 That's known as the 'slicing' problem. It's pernicious in that it can be 
 extremely hard to expose via testing or code reviews, yet will expose 
 the program to unpredictable behavior.

It frustrates me that people keep conflating what are orthogonal questions, such as: - Should we allow copy-assignments of class types? - Should we allow classes on the stack? - Should we use '*' to declare pointers-to-classes? Those are 3 *VERY* different questions. IMHO, we should disallow copy-assignments of class types (compiler detects the problem and spits out a clear error message, thus trivially educating the beginners), but still require the '*'.
Aug 17 2007
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Russell Lewis wrote:
 Walter Bright wrote:
 That's known as the 'slicing' problem. It's pernicious in that it can 
 be extremely hard to expose via testing or code reviews, yet will 
 expose the program to unpredictable behavior.

It frustrates me that people keep conflating what are orthogonal questions, such as: - Should we allow copy-assignments of class types? - Should we allow classes on the stack? - Should we use '*' to declare pointers-to-classes? Those are 3 *VERY* different questions. IMHO, we should disallow copy-assignments of class types (compiler detects the problem and spits out a clear error message, thus trivially educating the beginners), but still require the '*'.

And I assume you would still allow classes on the stack in that case. Good point. That way you would retain uniform struct/class syntax, but still avoid the slicing problem. The only down side I can think of is what someone mentioned before. Since you can't use operator overloading on pointers, it would mean either 1) you suffer through lots of dereferencing (*a + *b), or 2) you need to introduce a full-fledged C++-like reference type (a pointer that acts like a value). Or 3) you require a sigil to do all pointer math. More about 3): I was tempted to to say "only for pointer-to-struct/class" but that interferes with the ability for class/struct to mimic built-in syntax. E.g. with this pattern: for (T* p=&TArray[0]; p!=end; p++) {...} you don't want to have to do something different depending upon what T is. With the approach 3) you'd have to make that something like for (T* p=&TArray[0]; p!=end; as_ptr(p)++) {...} Maybe you could use # for the sigil instead of as_ptr(). It has an intuitive meaning of "treat this as a simple number" instead of something more complex: for (T* p=&TArray[0]; p!=end; #p++) {...} That doesn't seem half bad to me, but it would certainly be annoying for code that does lots of pointer math. --bb
Aug 17 2007
prev sibling parent James Dennett <jdennett acm.org> writes:
Johan Granberg wrote:
 Russell Lewis wrote:
 I don't see any fundamental reason why classes need to
 be reference types, other than history.

What about this situation. //begin C++ class A{ int val; }; class B:public A{ int foo; }; int main(int argc,char**argvs){ A a; B b; a=b;//HERE what happens to b's member foo? } //end C++ it's my impression that D's classes are reference types to avoid that specific problem.

// begin non-naive C++ class A { // a base class public: int val; protected: A() {} }; class B : public A { public: int foo; }; int main() { A a; // compilation error here. B b; a = b; // mu } // end C++ It's a slight exaggeration to say that much of D's design exists to compensate for programmers finding it too much trouble/too hard to learn idioms needed to use C++ safely. However, value semantics (such as comparing for equality) don't work well with polymorphism. C++ avoids this problem when used conventionally; I'm not sure if D falls into the same trap as most OO languages by allowing equality comparisons between objects of different classes. -- James
Aug 15 2007
prev sibling next sibling parent Sean Kelly <sean f4.ca> writes:
Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the 
 pointers to classes.  It seemed kinda neat when I was first using D that 
 I could avoid typing *s all the time.  But as time wears on it's 
 starting to seem more like a liability.

Funny. I started out having concerns about the inconsistency here and have decided it's a good thing :-) I think the salient point is that classes in D do not and will never (as far as I know) have a value type--they are always a reference type, even when "scope" is used. However, requiring the user to supply an asterisk for all class variable declarations suggests to me that I can remove the asterisk and get a value type, which I cannot. Back before the addition of "scope" I'd thought the syntax of scoped objects would be more like a value type: MyClass c = MyClass(); in which case I think the presence of an asterisk may have been more justified, though the lack of pass-by-value would likely still have rendered it too confusing to be practical IMO. I think in D we just have to accept that classes are special and deserve special syntax.
 In large part it all stems from the decision to make classes and structs 
 different.  That seems nice in theory, but as time goes on the two are 
 becoming more and more alike.

Personally, I think structs and classes are nothing alike in D and I consider this a good thing. One of my pet peeves about C++ is that it has two keywords to represent the same darn thing. The distinction between structs and classes really has more to do with how they are intended to be used than how they look. Structs are simply simple aggregate data types that are allowed to contain functions for convenience. In fact, I think the need for a copy ctor is debatable, given the purpose of structs (even though I've wanted one in the past), as is the need for a dtor. Real ctor support seems entirely reasonable, however, because everything needs to be initialized, and ctors make this both convenient and efficient (the static opCall approach is a confusing hack). Sean
Aug 15 2007
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the 
 pointers to classes.  It seemed kinda neat when I was first using D that 
 I could avoid typing *s all the time.  But as time wears on it's 
 starting to seem more like a liability.

Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided. 2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant. 3) Value types just don't work for polymorphic behavior. They must be by reference. There's no way in C++ to ensure that your class instances are used properly by reference only (hence (2)). In fact, in C++, it's *extra work* to use them properly. Value types are fundamentally different from reference types. D gives you the choice.
Aug 15 2007
next sibling parent reply Brad Roberts <braddr puremagic.com> writes:
On Wed, 15 Aug 2007, Walter Bright wrote:

 Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the pointers
 to classes.  It seemed kinda neat when I was first using D that I could
 avoid typing *s all the time.  But as time wears on it's starting to seem
 more like a liability.

Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided. 2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant.

They're _less_ relevant. There's still the valid usecase of acting like a value to avoid instance sharing where copy construction and assignment's are. This is where it's the author of the class making that decision rather than the user that you talk about in other responses to this thread. That's the distinction between 'by value' vs 'by reference' and 'as a value' vs 'as references'. IE, access vs behavior. I like a strong enforcement and distinction between the access part, but I do believe that it should be possible for a class write to achieve value semantics.
 3) Value types just don't work for polymorphic behavior. They must be by
 reference. There's no way in C++ to ensure that your class instances are used
 properly by reference only (hence (2)). In fact, in C++, it's *extra work* to
 use them properly.
 
 Value types are fundamentally different from reference types. D gives you the
 choice.

Aug 15 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Brad Roberts wrote:
 On Wed, 15 Aug 2007, Walter Bright wrote:
 
 Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the pointers
 to classes.  It seemed kinda neat when I was first using D that I could
 avoid typing *s all the time.  But as time wears on it's starting to seem
 more like a liability.

1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided. 2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant.

They're _less_ relevant. There's still the valid usecase of acting like a value to avoid instance sharing where copy construction and assignment's are. This is where it's the author of the class making that decision rather than the user that you talk about in other responses to this thread.

I strongly feel that for that usecase, one should be using a value type, i.e. a struct, not a class. C++ classes are neither one nor the other, thereby doing neither well.
 That's the distinction between 'by value' vs 'by reference' and 'as a 
 value' vs 'as references'.  IE, access vs behavior.  I like a strong 
 enforcement and distinction between the access part, but I do believe that 
 it should be possible for a class write to achieve value semantics.

I don't agree, I think there is much to be gained by drawing a strong distinction. Value and reference types are *fundamentally* different. Classes are an OOP type, and giving them value semantics introduces all the gotchas C++ has with them (like the slicing problem). However, it is possible to use a struct to wrap a class reference.
 3) Value types just don't work for polymorphic behavior. They must be by
 reference. There's no way in C++ to ensure that your class instances are used
 properly by reference only (hence (2)). In fact, in C++, it's *extra work* to
 use them properly.

 Value types are fundamentally different from reference types. D gives you the
 choice.


Aug 15 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Walter Bright wrote:
 Brad Roberts wrote:
 On Wed, 15 Aug 2007, Walter Bright wrote:

 Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the 
 pointers
 to classes.  It seemed kinda neat when I was first using D that I could
 avoid typing *s all the time.  But as time wears on it's starting to 
 seem
 more like a liability.

1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided. 2) You can't write a class in C++ without paying attention to assignment overloading and copy constructors. With classes as a reference type, these issues are simply irrelevant.

They're _less_ relevant. There's still the valid usecase of acting like a value to avoid instance sharing where copy construction and assignment's are. This is where it's the author of the class making that decision rather than the user that you talk about in other responses to this thread.

I strongly feel that for that usecase, one should be using a value type, i.e. a struct, not a class. C++ classes are neither one nor the other, thereby doing neither well.
 That's the distinction between 'by value' vs 'by reference' and 'as a 
 value' vs 'as references'.  IE, access vs behavior.  I like a strong 
 enforcement and distinction between the access part, but I do believe 
 that it should be possible for a class write to achieve value semantics.

I don't agree, I think there is much to be gained by drawing a strong distinction. Value and reference types are *fundamentally* different. Classes are an OOP type, and giving them value semantics introduces all the gotchas C++ has with them (like the slicing problem).

The problem I see is that it's really not always clear whether a value or reference type is desired. Currently I'm thinking about container classes. Value semantics are convenient and efficient for small containers. D's built-in containers are basically structs as is often pointed out. If you create lots of little Sets here and there you don't want each Set to require two allocations (one for the object itself, and another for adding some content to it). Only one allocation is really required there. But you might also want your set to implement some interfaces (a la Tango). Then you're forced into a class even though you don't particularly want anyone to subclass your Set. And even though you'd rather use it primarily as a value type (for efficiency, plus since the intent is a 'final' class there will be no derived classes and thus there are no slicing issues). --bb
Aug 15 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
Bill Baxter wrote:
 The problem I see is that it's really not always clear whether a value 
 or reference type is desired.  Currently I'm thinking about container 
 classes.  Value semantics are convenient and efficient for small 
 containers.  D's built-in containers are basically structs as is often 
 pointed out.  If you create lots of little Sets here and there you don't 
 want each Set to require two allocations (one for the object itself, and 
 another for adding some content to it).  Only one allocation is really 
 required there.  But you might also want your set to implement some 
 interfaces (a la Tango).  Then you're forced into a class even though 
 you don't particularly want anyone to subclass your Set.  And even 
 though you'd rather use it primarily as a value type (for efficiency, 
 plus since the intent is a 'final' class there will be no derived 
 classes and thus there are no slicing issues).

If you're using interfaces in a class, it's consuming 8 bytes plus 4 bytes per interface. That means it's cheaper to pass around by reference than by value. I don't think you're going to see a real efficiency gain by adding value semantics for such cases.
Aug 16 2007
prev sibling next sibling parent reply James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the
 pointers to classes.  It seemed kinda neat when I was first using D
 that I could avoid typing *s all the time.  But as time wears on it's
 starting to seem more like a liability.

Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided.

Trivially avoided in C++ also.
 2) You can't write a class in C++ without paying attention to assignment
 overloading and copy constructors. With classes as a reference type,
 these issues are simply irrelevant.

Trivial in C++: entity types are declared as "noncopyable", base classes are made abstract, and problems disappear.
 3) Value types just don't work for polymorphic behavior. They must be by
 reference. There's no way in C++ to ensure that your class instances are
 used properly by reference only (hence (2)). In fact, in C++, it's
 *extra work* to use them properly.

Declaring them as noncopyable seems to avoid the problems to which you refer, and should be normal for entity types in C++. (I'm not sure which extra work you're referring to: using some form of GC for memory management?)
 Value types are fundamentally different from reference types. D gives
 you the choice.

C++ gives you *more* choice by allowing easier migration between the two without imposing performance or syntactic differences. -- James
Aug 15 2007
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
James Dennett wrote:
 Walter Bright wrote:
 Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the
 pointers to classes.  It seemed kinda neat when I was first using D
 that I could avoid typing *s all the time.  But as time wears on it's
 starting to seem more like a liability.

1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided.

Trivially avoided in C++ also.
 2) You can't write a class in C++ without paying attention to assignment
 overloading and copy constructors. With classes as a reference type,
 these issues are simply irrelevant.

Trivial in C++: entity types are declared as "noncopyable", base classes are made abstract, and problems disappear.
 3) Value types just don't work for polymorphic behavior. They must be by
 reference. There's no way in C++ to ensure that your class instances are
 used properly by reference only (hence (2)). In fact, in C++, it's
 *extra work* to use them properly.

Declaring them as noncopyable seems to avoid the problems to which you refer, and should be normal for entity types in C++. (I'm not sure which extra work you're referring to: using some form of GC for memory management?)
 Value types are fundamentally different from reference types. D gives
 you the choice.

C++ gives you *more* choice by allowing easier migration between the two without imposing performance or syntactic differences.

Yes, that's pretty much the same conclusion I came to. C++'s way gives you a little more rope. You can use it to hang yourself, or to fashion a nifty lasso if you've learned how to use a lasso. I think Walter's plan for D was always to hit the middle ground between Java ("NO ROPE FOR YOU!") and C++ ("here have some more"), and give you more freedom than Java, but be easier to learn and use than C++. --bb
Aug 16 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
James Dennett wrote:
 Walter Bright wrote:
 Value types are fundamentally different from reference types. D gives
 you the choice.

C++ gives you *more* choice by allowing easier migration between the two without imposing performance or syntactic differences.

That isn't my experience. In my work on DMDscript in D, I changed some types between struct and class to try out some variations, and found it to be a lot easier than in C++. For one thing, I didn't have to find and edit all the -> and .'s. But you're right that C++ allows you more choice - you can create a polymorphic type with value semantics. I think D is better off without them, however.
Aug 16 2007
parent reply James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 James Dennett wrote:
 Walter Bright wrote:
 Value types are fundamentally different from reference types. D gives
 you the choice.

C++ gives you *more* choice by allowing easier migration between the two without imposing performance or syntactic differences.

That isn't my experience. In my work on DMDscript in D, I changed some types between struct and class to try out some variations, and found it to be a lot easier than in C++. For one thing, I didn't have to find and edit all the -> and .'s.

In C++, no, you didn't, as structs and classes are no different in their use of "->" and "." (as you're aware, they're all just classes). Now, if you changed from using objects by value to using them by reference, it would be dangerous to take the D route and use almost the same syntax for both (for example, you inherit the ubiquitous Java bug of writing MyType foo; and finding that foo is null rather than being a usable object). Of course there are two things being conflated here: (1) the difference between structs and classes, and (2) the difference between entity/reference types and value types In D, this "confusion" is not a confusion, as struct/class are there to model the latter distinction. In C++, you can take entity objects and drop them on the stack (in perfectly sane code), and you can take value objects and work with them on the heap (as you can in D, I believe); it makes the two dimensions orthogonal. (This has an analog in access control: in C++ access control and virtual-ness are orthogonal, in Java and D they are not. Orthogonality has advantages, but also has some consequences that are arguably counter-intuitive.)
 But you're right that C++ allows you more choice - you can create a
 polymorphic type with value semantics. I think D is better off without
 them, however.

Your call. I don't agree, but that's fine! -- James
Aug 16 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
James Dennett wrote:
 Walter Bright wrote:
 James Dennett wrote:
 Walter Bright wrote:
 Value types are fundamentally different from reference types. D gives
 you the choice.

the two without imposing performance or syntactic differences.

types between struct and class to try out some variations, and found it to be a lot easier than in C++. For one thing, I didn't have to find and edit all the -> and .'s.

In C++, no, you didn't, as structs and classes are no different in their use of "->" and "." (as you're aware, they're all just classes).

I meant I switched between value and reference semantics for an object.
Aug 17 2007
parent James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 James Dennett wrote:
 Walter Bright wrote:
 James Dennett wrote:
 Walter Bright wrote:
 Value types are fundamentally different from reference types. D gives
 you the choice.

the two without imposing performance or syntactic differences.

types between struct and class to try out some variations, and found it to be a lot easier than in C++. For one thing, I didn't have to find and edit all the -> and .'s.

In C++, no, you didn't, as structs and classes are no different in their use of "->" and "." (as you're aware, they're all just classes).

I meant I switched between value and reference semantics for an object.

Careful with selective quoting! I recognized that you probably meant this fundamental change of semantics with my very next paragraph, reproduced below:
 Now, if you changed from using objects by value to using them
 by reference, it would be dangerous to take the D route and use
 almost the same syntax for both (for example, you inherit the
 ubiquitous Java bug of writing MyType foo; and finding that foo
 is null rather than being a usable object).


Similar concept should look similar; similar syntax should have similar semantics. You've chosen to use (almost) the same syntax to mean two very different things, with subtle gotchas such as the simple but pervasive bug noted above. -- James
Aug 18 2007
prev sibling parent reply eao197 <eao197 intervale.ru> writes:
On Thu, 16 Aug 2007 01:35:17 +0400, Walter Bright  
<newshound1 digitalmars.com> wrote:

 Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the  
 pointers to classes.  It seemed kinda neat when I was first using D  
 that I could avoid typing *s all the time.  But as time wears on it's  
 starting to seem more like a liability.

Having classes be by reference only has some serious advantages: 1) Classes are polymorphic and inheritable. By making them reference only, the "slicing" problem inherent in C++ is completely and cleanly avoided.

From my expirience this is a problem of C++ beginners.
 2) You can't write a class in C++ without paying attention to assignment  
 overloading and copy constructors. With classes as a reference type,  
 these issues are simply irrelevant.

 3) Value types just don't work for polymorphic behavior. They must be by  
 reference. There's no way in C++ to ensure that your class instances are  
 used properly by reference only (hence (2)). In fact, in C++, it's  
 *extra work* to use them properly.

But from another side all types in C++ (internal or user defined) is a first class citizens. It is especially important in generic programming, where I can write: template< class T > class ValueHolder { T * m_value public : ValueHolder() : m_value( new T() ) {} ... }; and this code will work as with int, as with std::string, as with ValueHolder<SomeAnotherType>.
 Value types are fundamentally different from reference types. D gives  
 you the choice.

After some languages where there aren't distinction beetwen various kind of types (e.g. C++/Eiffel/Ruby) D looks very strange here. And this is one of the troubles in studying D. -- Regards, Yauheni Akhotnikau
Aug 16 2007
next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
eao197 wrote:
 On Thu, 16 Aug 2007 01:35:17 +0400, Walter Bright 
 <newshound1 digitalmars.com> wrote:
 

 3) Value types just don't work for polymorphic behavior. They must be 
 by reference. There's no way in C++ to ensure that your class 
 instances are used properly by reference only (hence (2)). In fact, in 
 C++, it's *extra work* to use them properly.

But from another side all types in C++ (internal or user defined) is a first class citizens. It is especially important in generic programming, where I can write: template< class T > class ValueHolder { T * m_value public : ValueHolder() : m_value( new T() ) {} ... }; and this code will work as with int, as with std::string, as with ValueHolder<SomeAnotherType>.

I hear you. Fortunately it's pretty trivial to throw some static-if-is-zzle at the problem in D. May not be so pretty, but it's straightforward at least. --bb
Aug 16 2007
next sibling parent eao197 <eao197 intervale.ru> writes:
On Thu, 16 Aug 2007 12:28:07 +0400, Bill Baxter  
<dnewsgroup billbaxter.com> wrote:

 3) Value types just don't work for polymorphic behavior. They must be  
 by reference. There's no way in C++ to ensure that your class  
 instances are used properly by reference only (hence (2)). In fact, in  
 C++, it's *extra work* to use them properly.

first class citizens. It is especially important in generic programming, where I can write: template< class T > class ValueHolder { T * m_value public : ValueHolder() : m_value( new T() ) {} ... }; and this code will work as with int, as with std::string, as with ValueHolder<SomeAnotherType>.

I hear you. Fortunately it's pretty trivial to throw some static-if-is-zzle at the problem in D. May not be so pretty, but it's straightforward at least.

I suppose that the problem becomes more complex in the case when template class has some methods which receive or return objects by reference or by value. In C++ it is straightforward, but in D it is required some static-if-metaprogramming. -- Regards, Yauheni Akhotnikau
Aug 16 2007
prev sibling parent Matthias Walter <walter mail.math.uni-magdeburg.de> writes:
Bill Baxter Wrote:

 eao197 wrote:
 On Thu, 16 Aug 2007 01:35:17 +0400, Walter Bright 
 <newshound1 digitalmars.com> wrote:
 

 3) Value types just don't work for polymorphic behavior. They must be 
 by reference. There's no way in C++ to ensure that your class 
 instances are used properly by reference only (hence (2)). In fact, in 
 C++, it's *extra work* to use them properly.

But from another side all types in C++ (internal or user defined) is a first class citizens. It is especially important in generic programming, where I can write: template< class T > class ValueHolder { T * m_value public : ValueHolder() : m_value( new T() ) {} ... }; and this code will work as with int, as with std::string, as with ValueHolder<SomeAnotherType>.

I hear you. Fortunately it's pretty trivial to throw some static-if-is-zzle at the problem in D. May not be so pretty, but it's straightforward at least. --bb

There's an example going to the reverse direction: I recently worked on a GMP port in D. For this case, one _must_ avoid typing * to ensure that x = b + c; works. (Nobody wants to type *x = *b + *c, I guess) With C++, there would be a temporary for (b+c) which is assigned to x later - to avoid this copying, the GMP library authors used heavy template metaprogramming (which is ugly to read, although straightforward). In D I'm happy that I just can write a GMP class, overload the operators and use it as x = b + c; - There is temporary construction, but the assignment of (b+c) to a is just copying the pointer and thus no performance penalty. What about letting "new int" do nothing and new int (2) be a simple "2"? Then, T foo = new T(); and T foo = new T(2); would work, too. best regards Matthias
Aug 16 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
eao197 wrote:
  From my expirience this is a problem of C++ beginners.

I was talking to a person who is a C++ developer for a major game company just today. He told me that it is very difficult to find experienced C++ developers, hire them, or even recognize them in a job interview. When you are able to hire them, they cost plenty. It's true that every problem with C++ can be solved by getting more advanced C++ programmers. The problem is getting those C++ programmers. And even the best ones have bad days and make mistakes <g>. Defining a problem out of existence is preferable, cheaper, and more reliable than depending on convention or more training. If I was paying top dollar for a programming expert, I'd rather he focused his energies on something more productive than dodging C++'s potholes. If I was in charge of writing software that absolutely, positively must work correctly, I'll prefer a language guarantee over reliance that my programmers, no matter how good they are, didn't overlook something.
Aug 16 2007
next sibling parent reply eao197 <eao197 intervale.ru> writes:
On Thu, 16 Aug 2007 13:20:28 +0400, Walter Bright  
<newshound1 digitalmars.com> wrote:

  From my expirience this is a problem of C++ beginners.

I was talking to a person who is a C++ developer for a major game company just today. He told me that it is very difficult to find experienced C++ developers, hire them, or even recognize them in a job interview. When you are able to hire them, they cost plenty. It's true that every problem with C++ can be solved by getting more advanced C++ programmers. The problem is getting those C++ programmers. And even the best ones have bad days and make mistakes <g>.

I know. But I know that getting experienced programmer is very hard for _any_ language. And they cost plenty.
 Defining a problem out of existence is preferable, cheaper, and more  
 reliable than depending on convention or more training.

 If I was paying top dollar for a programming expert, I'd rather he  
 focused his energies on something more productive than dodging C++'s  
 potholes.

 If I was in charge of writing software that absolutely, positively must  
 work correctly, I'll prefer a language guarantee over reliance that my  
 programmers, no matter how good they are, didn't overlook something.

I'm totaly agree with you. This is why I'm interested in D, not in C++. But in the case with value type/reference type separation there are some benefits and drawbacks in the current D version. So I think that 'slicing' is not an important argument in defense of such separation. Because value/reference separation it is a fundamental feature of D so it is better to concentrate to minimization of value/reference/pointer conceptions. For example, what's about removing 'reference' at all? Lets D has only values and pointers: int i; // value. int * pi; // pointer to value. struct S { ... } S s; // value. S * ps; // pointer to value. class C { ... } C c; // Error! Value type cannot be used as value. C * c; // OK. Pointer to reference type. void f( int i, S s, S * s, C * c ) { ... } -- Regards, Yauheni Akhotnikau
Aug 16 2007
next sibling parent eao197 <eao197 intervale.ru> writes:
On Thu, 16 Aug 2007 21:04:23 +0400, eao197 <eao197 intervale.ru> wrote:

 class C { ... }
 C c; // Error! Value type cannot be used as value.
 C * c; // OK. Pointer to reference type.

Oops! Typo :(( Should be: C c; // Error! Reference type cannot be used as value. -- Regards, Yauheni Akhotnikau
Aug 16 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
eao197 wrote:
 I'm totaly agree with you. This is why I'm interested in D, not in C++.
 But in the case with value type/reference type separation there are some 
 benefits and drawbacks in the current D version. So I think that 
 'slicing' is not an important argument in defense of such separation.
 
 Because value/reference separation it is a fundamental feature of D so 
 it is better to concentrate to minimization of value/reference/pointer 
 conceptions. For example, what's about removing 'reference' at all? Lets 
 D has only values and pointers:
 
 int i; // value.
 int * pi; // pointer to value.
 struct S { ... }
 S s; // value.
 S * ps; // pointer to value.
 class C { ... }
 C c; // Error! Value type cannot be used as value.
 C * c; // OK. Pointer to reference type.
 
 void f( int i, S s, S * s, C * c ) { ... }

But I think if one pulls on that string, one eventually winds up with how C++ does it, with all of the problems.
Aug 16 2007
parent reply eao197 <eao197 intervale.ru> writes:
On Thu, 16 Aug 2007 22:36:53 +0400, Walter Bright  =

<newshound1 digitalmars.com> wrote:

 eao197 wrote:
 I'm totaly agree with you. This is why I'm interested in D, not in C+=


 But in the case with value type/reference type separation there are  =


 some benefits and drawbacks in the current D version. So I think that=


 'slicing' is not an important argument in defense of such separation.=


  Because value/reference separation it is a fundamental feature of D =


 it is better to concentrate to minimization of value/reference/pointe=


 conceptions. For example, what's about removing 'reference' at all?  =


 Lets D has only values and pointers:
  int i; // value.
 int * pi; // pointer to value.
 struct S { ... }
 S s; // value.
 S * ps; // pointer to value.
 class C { ... }
 C c; // Error! Value type cannot be used as value.
 C * c; // OK. Pointer to reference type.
  void f( int i, S s, S * s, C * c ) { ... }

But I think if one pulls on that string, one eventually winds up with =

 how C++ does it, with all of the problems.

(excuse me, but my poor English didn't allow me fully understand that. I= = could understand that you fear that such approach is too C++ish and it = would lead to repeating typical C++ errors in D code) I think such fear is a little strange for the language that already has = = pointer arithmetics and facilities like malloc/free/delete (and targetin= g = to the same niches as C++). But let to return to your goal: "Defining a problem out of existence is = = preferable, cheaper, and more reliable than depending on convention or = more training." How about eluminating such problem as null pointers (I = think it is much more common and dangerous problem than object slicing).= Some languages (Nice [1], Eiffel (in ECMA standard) [2], Spec# [3]) = targeted this problem by 'non-null' references (pointers). For example, = in = Nice there are two kinds of reference declarations: class C {} C non_null =3D ...; // Can't be null. C? nullable =3D ...; // Can be null. void f( C non_null_arg ) { ... } // non_null_arg can't be null. non_null =3D nullable; // Error! Checked by compiler. f( nullable ); // Error! Checked by compiler. nullable =3D non_null; // Ok. f( non_null ); // Ok. if( null !=3D nullable ) f( nullable ); // Ok. In this branch nullable is not null. I think if there was only one reference/pointer type in D than it would = be = easier to add such non-null references into the language. For example, i= t = is possible to use another symbol for pointers: class C {}; C # non_null =3D ...; // Can't be null. C * nullable =3D ...; // Can be null. void f( C # non_null_arg ) { ... } // non_null_arg can't be null. non_null =3D nullable; // Error! Checked by compiler. f( nullable ); // Error! Checked by compiler. nullable =3D non_null; // Ok. f( non_null ); // Ok. if( nullable !is null ) f( nullable ); // Ok. In this branch nullable is not null. It would be much cleaner (imho) than something like: C nullable_ref =3D ...; // Reference, can be null. C# non_null_ref =3D ...; // Non-null reference. C * nullable_ptr =3D ...; // Pointer, can be null. C# * non_null_ptr =3D ...; // Non-null pointer. [1] http://nice.sf.net [2] http://se.ethz.ch/~meyer/publications/lncs/attached.pdf [3] http://research.microsoft.com/specsharp/papers/krml136.pdf -- = Regards, Yauheni Akhotnikau
Aug 16 2007
next sibling parent reply "Jb" <jb nowhere.com> writes:
"eao197" <eao197 intervale.ru> wrote in message 
news:op.tw6uouycsdcfd2 eao197nb2.intervale.ru...
On Thu, 16 Aug 2007 22:36:53 +0400, Walter Bright
<newshound1 digitalmars.com> wrote:
But let to return to your goal: "Defining a problem out of existence is 
preferable, cheaper, and more reliable than depending on convention or 
more training." How about eluminating such problem as null pointers (I 
think it is much more common and dangerous problem than object slicing).

Because null pointers are easy to debug. And it's a lot less dangerous because you get an exception generated when you try to use a null pointer. Where as with the slicing problem anything can happen and it's incredibly hard to debug.
Aug 17 2007
parent reply eao197 <eao197 intervale.ru> writes:
On Fri, 17 Aug 2007 18:58:19 +0400, Jb <jb nowhere.com> wrote:

 "eao197" <eao197 intervale.ru> wrote in message
 news:op.tw6uouycsdcfd2 eao197nb2.intervale.ru...
 On Thu, 16 Aug 2007 22:36:53 +0400, Walter Bright
 <newshound1 digitalmars.com> wrote:
 But let to return to your goal: "Defining a problem out of existence is
 preferable, cheaper, and more reliable than depending on convention or
 more training." How about eluminating such problem as null pointers (I
 think it is much more common and dangerous problem than object slicing).

Because null pointers are easy to debug. And it's a lot less dangerous because you get an exception generated when you try to use a null pointer.

It is, probably, a good consolidation when null-pointer error happens in production code.
 Where as with the slicing problem anything can happen and it's incredibly
 hard to debug.

Any examples? What's bad and dangerous happens in the case of slicing? -- Regards, Yauheni Akhotnikau
Aug 17 2007
parent reply "Jb" <jb nowhere.com> writes:
"eao197" <eao197 intervale.ru> wrote in message 
news:op.tw7obrzwsdcfd2 eao197nb2.intervale.ru...
 On Fri, 17 Aug 2007 18:58:19 +0400, Jb <jb nowhere.com> wrote:

 Where as with the slicing problem anything can happen and it's incredibly
 hard to debug.

Any examples? What's bad and dangerous happens in the case of slicing?

Never mind, i had misunderstood what the slicing problem was. Sorry. jb
Aug 17 2007
parent eao197 <eao197 intervale.ru> writes:
On Fri, 17 Aug 2007 21:46:16 +0400, Jb <jb nowhere.com> wrote:

 "eao197" <eao197 intervale.ru> wrote in message
 news:op.tw7obrzwsdcfd2 eao197nb2.intervale.ru...
 On Fri, 17 Aug 2007 18:58:19 +0400, Jb <jb nowhere.com> wrote:

 Where as with the slicing problem anything can happen and it's  
 incredibly
 hard to debug.

Any examples? What's bad and dangerous happens in the case of slicing?

Never mind, i had misunderstood what the slicing problem was. Sorry.

No problem. It is good that D has less problems in language than C++. D hasn't slicing problem -- it is good. But having values, pointers and references (which can be used only with classes) seems too overcomplicated for me. And if we would have only values and pointes it could make simple solutiton for null-pointers problem (may be not simple, because Nice has some bugs, but more simple that in case of references, I think). -- Regards, Yauheni Akhotnikau
Aug 17 2007
prev sibling parent eao197 <eao197 intervale.ru> writes:
I'm sorry for my agressive behaviour, I was wrong.

On Fri, 17 Aug 2007 09:25:34 +0400, eao197 <eao197 intervale.ru> wrote:

 I think if there was only one reference/pointer type in D than it woul=

 be easier to add such non-null references into the language. For  =

 example, it is possible to use another symbol for pointers:

 class C {};
 C # non_null =3D ...; // Can't be null.
 C * nullable =3D ...; // Can be null.

 void f( C # non_null_arg ) { ... } // non_null_arg can't be null.

 non_null =3D nullable; // Error! Checked by compiler.
 f( nullable ); // Error! Checked by compiler.
 nullable =3D non_null; // Ok.
 f( non_null ); // Ok.
 if( nullable !is null )
    f( nullable ); // Ok. In this branch nullable is not null.

I totally forgot about operator overloading. So it is obviously necessar= y = to have not only values and pointers, but also references. I think that if introduce special syntax for references than problem for= = non-null references could be solved. Moreover with explicitely defined = references it is possible to remove 'in'/'out' argument modifiers. For example, let use ' ' as nullable reference and '#' as non-null = reference: int i; // Value. int i; // Nullable reference to int. int # i =3D ...; // Non-null reference to int. Must be initialized. int * p; // Nullable pointer to int. struct S; S s; // Value. S s; S # s =3D ...; S * s; class C; C c; // Error! C is a reference type. C c; C # c =3D ...; C * c; // Various kinds of arguments. void f( // Pass by value. S a1, // Pass by reference, like 'out' argument. S a2, // Pass by reference, like 'in' argument. const(S) a3, // Same as two previous, but require non-null references. S # a4, const(S) # a5, // Pass by pointer, like 'out' argument. S * a6, // Pass by pointer, like 'in' argument. const(S) * a7 ); I don't think that non-null pointer has much sence. And if we try to = introduce non-null pointers we can end up with things like non-null = pointer to nullable pointer to non-null pointer to something. Unfortunately, syntax 'C c' is ugly in comparision with current 'C c'.= = But I hope it make type system more consistent -- it is easy to = distinguish values form references in code. -- = Regards, Yauheni Akhotnikau
Aug 17 2007
prev sibling next sibling parent reply Stephen Waits <steve waits.net> writes:
Walter Bright wrote:
 
 I was talking to a person who is a C++ developer for a major game 
 company just today. He told me that it is very difficult to find 
 experienced C++ developers, hire them, or even recognize them in a job 
 interview. When you are able to hire them, they cost plenty.

I can second this. I'm a game developer, and I also happen to do most of the interviewing and hiring for my group. C++ and entry-level (i.e. fresh out of college) is almost non-existent. There's quite a bit of "java syndrome" going on out there. Experienced C++ game devs are indeed expensive. And also, *'s suck. Please don't infect D with this. --Steve
Aug 16 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Stephen Waits wrote:
 Walter Bright wrote:
 I was talking to a person who is a C++ developer for a major game 
 company just today. He told me that it is very difficult to find 
 experienced C++ developers, hire them, or even recognize them in a job 
 interview. When you are able to hire them, they cost plenty.

I can second this. I'm a game developer, and I also happen to do most of the interviewing and hiring for my group. C++ and entry-level (i.e. fresh out of college) is almost non-existent. There's quite a bit of "java syndrome" going on out there. Experienced C++ game devs are indeed expensive.

Right. One of the goals of D is to enable regular programmers to get better results. Programming ordinary apps shouldn't require the top tier programmers. One reason Java, Ruby and VB are so popular is because this is true for them.
 And also, *'s suck.  Please don't infect D with this.

I'm happy to debate this as I feel it is important to explain why this design is the way it is, but don't worry, I feel very strongly that D's behavior here is the right thing.
Aug 16 2007
parent kris <foo bar.com> writes:
Walter Bright wrote:
 Stephen Waits wrote:
 Walter Bright wrote:
 I was talking to a person who is a C++ developer for a major game 
 company just today. He told me that it is very difficult to find 
 experienced C++ developers, hire them, or even recognize them in a 
 job interview. When you are able to hire them, they cost plenty.

I can second this. I'm a game developer, and I also happen to do most of the interviewing and hiring for my group. C++ and entry-level (i.e. fresh out of college) is almost non-existent. There's quite a bit of "java syndrome" going on out there. Experienced C++ game devs are indeed expensive.

Right. One of the goals of D is to enable regular programmers to get better results. Programming ordinary apps shouldn't require the top tier programmers. One reason Java, Ruby and VB are so popular is because this is true for them.

amen
Aug 16 2007
prev sibling parent James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 eao197 wrote:
  From my expirience this is a problem of C++ beginners.

I was talking to a person who is a C++ developer for a major game company just today. He told me that it is very difficult to find experienced C++ developers, hire them, or even recognize them in a job interview. When you are able to hire them, they cost plenty.

An important observation. And given that programmers starting out in their careers are often looking at more "modern" languages, the pool of competent C++ developers isn't growing fast, while demand is still pretty strong.
 It's true that every problem with C++ can be solved by getting more
 advanced C++ programmers. The problem is getting those C++ programmers.
 And even the best ones have bad days and make mistakes <g>.

I assure you, on bad days I have made mistakes in many, many languages ;)
 Defining a problem out of existence is preferable, cheaper, and more
 reliable than depending on convention or more training.
 
 If I was paying top dollar for a programming expert, I'd rather he
 focused his energies on something more productive than dodging C++'s
 potholes.

And if you get an expert who knows C++, she won't spend much time avoiding its potholes.
 If I was in charge of writing software that absolutely, positively must
 work correctly, I'll prefer a language guarantee over reliance that my
 programmers, no matter how good they are, didn't overlook something.

Yup; I look forward to a future in which correctness proofs play a larger role in practical situations. -- James
Aug 16 2007
prev sibling parent reply Jason House <jason.james.house gmail.com> writes:
Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the 
 pointers to classes.  It seemed kinda neat when I was first using D that 
 I could avoid typing *s all the time.  But as time wears on it's 
 starting to seem more like a liability.

The simple distinction between reference types and value types has irked me from the very beginning. As a programmer using something that somebody else wrote, I shouldn't have to know its storage type. Distinctions between the two pop up here and there throughout D code. For example, value_type[] and ref_type[] have completely different copy behaviors.
 
 Bad points:
 - Harder to tell whether you're dealing with a pointer or not
   (c.f. the common uninitialized 'MyObject obj;' bug)
 - To build on the stack, have to use 'scope'
 
 Good points:
 + No need to type '*' everywhere when you use class objects
 + ??? anything else ???

I was thinking recently of an interesting syntax twist... Always require & when trying to get to an address, and use the raw variable name and "." to refer to whatever is pointed to. Obtaining an address would require &. It's an interesting change, but would likely be too confusing to switch over. Somehow I bet even suggesting the idea will mark me as a crack pot :)
Aug 15 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Jason House wrote:
 Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the 
 pointers to classes.  It seemed kinda neat when I was first using D 
 that I could avoid typing *s all the time.  But as time wears on it's 
 starting to seem more like a liability.

The simple distinction between reference types and value types has irked me from the very beginning. As a programmer using something that somebody else wrote, I shouldn't have to know its storage type. Distinctions between the two pop up here and there throughout D code. For example, value_type[] and ref_type[] have completely different copy behaviors.
 Bad points:
 - Harder to tell whether you're dealing with a pointer or not
   (c.f. the common uninitialized 'MyObject obj;' bug)
 - To build on the stack, have to use 'scope'

 Good points:
 + No need to type '*' everywhere when you use class objects
 + ??? anything else ???

I was thinking recently of an interesting syntax twist... Always require & when trying to get to an address, and use the raw variable name and "." to refer to whatever is pointed to. Obtaining an address would require &. It's an interesting change, but would likely be too confusing to switch over. Somehow I bet even suggesting the idea will mark me as a crack pot :)

Well realistically none of this is likely to change anyway, so you're free to suggest anything you want. :-) I was thinking about something like that as well, though. But couldn't really think how to make it useful. My thinking was that in D we have classes sort of "shifted" by one from structs pointer-to-pointer pointer value struct &(&p) &p p struct* &p p *p class &p p N/A So instead of that make structs default to pointers too, to shift everything back to be the same: pointer-to-pointer pointer value struct# &(&p) &p p struct &p p *p class &p p N/A '#' (or whatever -- perhaps re-use 'scope') becomes the "by value" indicator. So to do a struct-by-value you'd declare: MyStruct# Foo; while MyStruct Bar; would be a pointer/reference just like MyClass is. Some problems are: - what do you do about built-in value types like 'int'? Doesn't make sense to make 'int x' be a pointer type, IMHO. And if the built-in types behave differently from structs have you gained much? - for structs, as-value should be the common case, so you'll have to use a '#' most every time you use a struct. - it still won't make the struct -> class transition all that much easier unless classes gain the ability to be passed around by value. --bb
Aug 15 2007
next sibling parent reply Jason House <jason.james.house gmail.com> writes:
Bill Baxter wrote:
 Jason House wrote:
 I was thinking recently of an interesting syntax twist...  Always 
 require & when trying to get to an address, and use the raw variable 
 name and "." to refer to whatever is pointed to.  Obtaining an address 
 would require &.

 It's an interesting change, but would likely be too confusing to 
 switch over.  Somehow I bet even suggesting the idea will mark me as a 
 crack pot :)

Well realistically none of this is likely to change anyway, so you're free to suggest anything you want. :-) I was thinking about something like that as well, though. But couldn't really think how to make it useful. My thinking was that in D we have classes sort of "shifted" by one from structs pointer-to-pointer pointer value struct &(&p) &p p struct* &p p *p class &p p N/A So instead of that make structs default to pointers too, to shift everything back to be the same: pointer-to-pointer pointer value struct# &(&p) &p p struct &p p *p class &p p N/A

I was thinking of something more consistent. pointer-to-pointer pointer value struct N/A &p p struct* &&p &p p class &&p &p p T ??? &p p simply doing p will always give the value and &p will always give a pointer. How many &'s allowed would be specific to the circumstance/implementation. In my naive view, the last valid pointer (&p for struct, &&p for struct*, etc...) would have to be const. T above is supposed to be some kind of templated type
Aug 16 2007
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Jason House wrote:
 Bill Baxter wrote:
 Jason House wrote:
 I was thinking recently of an interesting syntax twist...  Always 
 require & when trying to get to an address, and use the raw variable 
 name and "." to refer to whatever is pointed to.  Obtaining an 
 address would require &.

 It's an interesting change, but would likely be too confusing to 
 switch over.  Somehow I bet even suggesting the idea will mark me as 
 a crack pot :)

Well realistically none of this is likely to change anyway, so you're free to suggest anything you want. :-) I was thinking about something like that as well, though. But couldn't really think how to make it useful. My thinking was that in D we have classes sort of "shifted" by one from structs pointer-to-pointer pointer value struct &(&p) &p p struct* &p p *p class &p p N/A So instead of that make structs default to pointers too, to shift everything back to be the same: pointer-to-pointer pointer value struct# &(&p) &p p struct &p p *p class &p p N/A

I was thinking of something more consistent. pointer-to-pointer pointer value struct N/A &p p struct* &&p &p p class &&p &p p T ??? &p p

Oh, I see. So you mean that no matter what, the compiler would figure out what dereferencing was necessary, and you always have to explicitly say whether you want a pointer or not. Interesting. So pointer math becomes pretty tedious for one. Maybe that's ok. And then what happens when you're given some T (which could be pointer to pointer to int, say) and you want dereference it one level further? And what about aliases? If I have alias int* intp; alias int** intpp; intp x; Then x can simultaneously be described as int**, intp*, or intpp. But why is struct pointer-to-pointer N/A. Shouldn't the N/A be under 'value' for class? --bb
Aug 16 2007
prev sibling parent Russell Lewis <webmaster villagersonline.com> writes:
Bill Baxter wrote:
 Jason House wrote:
 Bill Baxter wrote:
 I'm starting to seriously wonder if it was a good idea to hide the 
 pointers to classes.  It seemed kinda neat when I was first using D 
 that I could avoid typing *s all the time.  But as time wears on it's 
 starting to seem more like a liability.

The simple distinction between reference types and value types has irked me from the very beginning. As a programmer using something that somebody else wrote, I shouldn't have to know its storage type. Distinctions between the two pop up here and there throughout D code. For example, value_type[] and ref_type[] have completely different copy behaviors.
 Bad points:
 - Harder to tell whether you're dealing with a pointer or not
   (c.f. the common uninitialized 'MyObject obj;' bug)
 - To build on the stack, have to use 'scope'

 Good points:
 + No need to type '*' everywhere when you use class objects
 + ??? anything else ???

I was thinking recently of an interesting syntax twist... Always require & when trying to get to an address, and use the raw variable name and "." to refer to whatever is pointed to. Obtaining an address would require &. It's an interesting change, but would likely be too confusing to switch over. Somehow I bet even suggesting the idea will mark me as a crack pot :)

Well realistically none of this is likely to change anyway, so you're free to suggest anything you want. :-) I was thinking about something like that as well, though. But couldn't really think how to make it useful. My thinking was that in D we have classes sort of "shifted" by one from structs pointer-to-pointer pointer value struct &(&p) &p p struct* &p p *p class &p p N/A So instead of that make structs default to pointers too, to shift everything back to be the same: pointer-to-pointer pointer value struct# &(&p) &p p struct &p p *p class &p p N/A '#' (or whatever -- perhaps re-use 'scope') becomes the "by value" indicator. So to do a struct-by-value you'd declare: MyStruct# Foo; while MyStruct Bar; would be a pointer/reference just like MyClass is. Some problems are: - what do you do about built-in value types like 'int'? Doesn't make sense to make 'int x' be a pointer type, IMHO. And if the built-in types behave differently from structs have you gained much? - for structs, as-value should be the common case, so you'll have to use a '#' most every time you use a struct. - it still won't make the struct -> class transition all that much easier unless classes gain the ability to be passed around by value.

It also doesn't help clarity (completely) because you can still have typedefs and other user-defined types that would have value semantics.
Aug 17 2007