August 16, 2007
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.
>> 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.

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
August 16, 2007
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
August 16, 2007
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.

August 16, 2007
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.
August 16, 2007
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.
>>  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.

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
August 16, 2007
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.
August 16, 2007
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.

August 16, 2007
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.
August 16, 2007
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.
August 16, 2007
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