View mode: basic / threaded / horizontal-split · Log in · Help
December 16, 2007
Re: what was wrong with struct & class in C++?
Walter Bright wrote:
> Christopher Wright wrote:
>> That would be allowed in D, if you could overload T.opAssign(T).
> 
> C++ still has well-known slicing problems, even with overloading 
> assignment.

Yes, of course. You'd need to have a reference type behaving as a value 
type, with copy on assignment, in order to get rid of the issue. Though 
I'm not sure you're allowed to overload assignment from a reference or 
pointer to another reference or pointer in C++.

Besides which, people are going to use polymorphic types by value 
(actually by value, rather than some by-reference-and-by-value stuff) 
for performance reasons. Overload all you want, it won't help you when 
there's only 32 bytes for your struct on the stack and you need to pack 
in 48 bytes.

> The question is not "can this be done", it's more "is there a compelling 
> reason to support such behavior". I think the answer is no. Do you 
> really want a language where a class designer feels compelled to define 
> an opassign overload, then make it private to prevent people from using 
> the class as a value type? Where the base class designer needs to know 
> what the derived classes are doing so he can make the destructor virtual 
> or not? Where you cannot have arrays of base classes?

You'd need to create an opAssign overload in order to use a class as a 
value type, so you'd have to do zero work to prevent a class from being 
used as a value type. If you mean, create one and make it private in 
order for derived classes not to be used as value types, well, there'd 
better be a compelling reason for that. And making a private opAssign 
wouldn't help matters; I'd just make a new one on my derived class.

I'm not sure how arrays would be hindered by this.

Of course, if it's too much of an issue, you could define a construct 
'ref struct' that allows inheritance but has value semantics. If you 
think that programmers will have too much trouble with overloading 
assignment with classes. But that's introducing a new language construct 
rather than eliminating an exception to a rule.
December 16, 2007
Re: what was wrong with struct & class in C++?
Yigal Chripun wrote:
> Bill Baxter wrote:
>> Walter Bright wrote:
>>> Christopher Wright wrote:
>>>> That would be allowed in D, if you could overload T.opAssign(T).
>>>
>>> C++ still has well-known slicing problems, even with overloading 
>>> assignment.
>>>
>>> The question is not "can this be done", it's more "is there a 
>>> compelling reason to support such behavior". I think the answer is 
>>> no. Do you really want a language where a class designer feels 
>>> compelled to define an opassign overload, then make it private to 
>>> prevent people from using the class as a value type? Where the base 
>>> class designer needs to know what the derived classes are doing so he 
>>> can make the destructor virtual or not? Where you cannot have arrays 
>>> of base classes?
>>
>> At least C++ lets you control copying behavior completely in all 
>> circumstances.  And documents what you are supposed to do clearly. 
>> Right now D gives you the choice of
>>
>> 1) [structs] copy by value only, with no option to customize or 
>> intercept copy operations
>> 2) [classes] no defined copy behavior whatsoever
>>
>> 1) makes interesting ref counting wrappers etc impossible, but I think 
>> you're planning to fix that one.
>>
>> I think 2) is a problem also.  It means that developers will each come 
>> up with their own way to copy objects because there's no clear way to 
>> do it.  I think some particular scheme should be crowned, like any 
>> class that wants to be copyable should provide a the pair of methods 
>> dup(), and copy(ThisType).  And maybe there should be something in 
>> Object to this effect also, even if they don't do much of anything.  
>> Looks like java implements a clone() method in the base Object class.  
>> But it looks like that just makes it so you get a runtime exception if 
>> the class you're trying to clone doesn't support cloning.  That 
>> doesn't seem like an improvement over a compile time error.
>>
>> So I'm not really sure what should be done, but I definitely think 
>> something should be done to specify "the D way" to polymorphically 
>> copy objects.  Built-ins mostly have .dup properties, but I don't 
>> think the spec actually says anywhere that user classes that want to 
>> be copyable should have a .dup.  But even specifying a .dup is not 
>> enough I think, because if I derive from some class A, I have to 
>> create my derived class, then get class A to copy the A parts of the 
>> new object somehow, say via a method like A.copy(A copy_from).
>>
>> C++ may have problems regarding copying classes, but D's solution is 
>> effectively to just remove the C++ functionality good and bad.  Ok the 
>> slicing problem is gone, but so is copying.  There should be one 
>> obvious way to make classes in D copyable, whether it be enforced by 
>> the language, compiler, or simply the spec and D community.
>>
>> --bb
> 
> In java you also need to implement a clonable interface which is a 
> marker interface.
> I think that adding a clonable interface to the standard library should 
> be enough. something like:
> ---
> interface clonable {
>     object dup();
> }
> ---
> now you need to implement it to support copying. 

Is there really any benefit in making it an interface in D since you can 
just do a static if check to see if it has a dup method?

> I don't think a dup 
> method should be added to object. as you said, it isn't really an 
> improvement, and usually Java experts recommend avoiding clone().

What do you mean they recommend avoiding it?  What do they recommend 
instead?

> also, i don't see why a copy method is required.
> for example, check the following code:
> ---
> class Base : clonable {
>     Base dup() {
>        auto ret = cast(Base) this.classinfo.create;
>        ret.x = this.x;
>        ret.y = this.y;
>        return ret;
>     }
>     int x = 6;
>     double y = 8;
> }
> 
> class DerivedA : Base {
>     override DerivedA dup() {
>        auto ret = cast(DerivedA) super.dup;
>        ret.w = this.w;
>        return ret;
>     }
>     long w = 42;
> }

Ah ha!  Ok, that's great.  This is exactly what I was looking for over 
on the thread I started on D.learn ("implementing dup/clone for class 
hierarchies").   Steven suggested using classinfo.create, but he left 
his solution using a separate copy() method (or I just misunderstood 
him), and that just ends up in a lot of casting.  But your method seems 
to work.

So that seems good.  The trouble then is just that it is not obvious and 
there are no examples in the documentation showing how to do this.

For now I've added this example to the wiki DocComments page for 'class':

http://www.prowiki.org/wiki4d/wiki.cgi?DocComments/Class#section8


But I really think an example like this belongs in the main doc.

--bb
December 16, 2007
Re: what was wrong with struct & class in C++?
Bill Baxter wrote:
> Yigal Chripun wrote:
>> Bill Baxter wrote:
>>> Walter Bright wrote:
>>>> Christopher Wright wrote:
>>>>> That would be allowed in D, if you could overload T.opAssign(T).
>>>>
>>>> C++ still has well-known slicing problems, even with overloading 
>>>> assignment.
>>>>
>>>> The question is not "can this be done", it's more "is there a 
>>>> compelling reason to support such behavior". I think the answer is 
>>>> no. Do you really want a language where a class designer feels 
>>>> compelled to define an opassign overload, then make it private to 
>>>> prevent people from using the class as a value type? Where the base 
>>>> class designer needs to know what the derived classes are doing so 
>>>> he can make the destructor virtual or not? Where you cannot have 
>>>> arrays of base classes?
>>>
>>> At least C++ lets you control copying behavior completely in all 
>>> circumstances.  And documents what you are supposed to do clearly. 
>>> Right now D gives you the choice of
>>>
>>> 1) [structs] copy by value only, with no option to customize or 
>>> intercept copy operations
>>> 2) [classes] no defined copy behavior whatsoever
>>>
>>> 1) makes interesting ref counting wrappers etc impossible, but I 
>>> think you're planning to fix that one.
>>>
>>> I think 2) is a problem also.  It means that developers will each 
>>> come up with their own way to copy objects because there's no clear 
>>> way to do it.  I think some particular scheme should be crowned, like 
>>> any class that wants to be copyable should provide a the pair of 
>>> methods dup(), and copy(ThisType).  And maybe there should be 
>>> something in Object to this effect also, even if they don't do much 
>>> of anything.  Looks like java implements a clone() method in the base 
>>> Object class.  But it looks like that just makes it so you get a 
>>> runtime exception if the class you're trying to clone doesn't support 
>>> cloning.  That doesn't seem like an improvement over a compile time 
>>> error.
>>>
>>> So I'm not really sure what should be done, but I definitely think 
>>> something should be done to specify "the D way" to polymorphically 
>>> copy objects.  Built-ins mostly have .dup properties, but I don't 
>>> think the spec actually says anywhere that user classes that want to 
>>> be copyable should have a .dup.  But even specifying a .dup is not 
>>> enough I think, because if I derive from some class A, I have to 
>>> create my derived class, then get class A to copy the A parts of the 
>>> new object somehow, say via a method like A.copy(A copy_from).
>>>
>>> C++ may have problems regarding copying classes, but D's solution is 
>>> effectively to just remove the C++ functionality good and bad.  Ok 
>>> the slicing problem is gone, but so is copying.  There should be one 
>>> obvious way to make classes in D copyable, whether it be enforced by 
>>> the language, compiler, or simply the spec and D community.
>>>
>>> --bb
>>
>> In java you also need to implement a clonable interface which is a 
>> marker interface.
>> I think that adding a clonable interface to the standard library 
>> should be enough. something like:
>> ---
>> interface clonable {
>>     object dup();
>> }
>> ---
>> now you need to implement it to support copying. 
> 
> Is there really any benefit in making it an interface in D since you can 
> just do a static if check to see if it has a dup method?
> 
>> I don't think a dup method should be added to object. as you said, it 
>> isn't really an improvement, and usually Java experts recommend 
>> avoiding clone().
> 
> What do you mean they recommend avoiding it?  What do they recommend 
> instead?
> 
>> also, i don't see why a copy method is required.
>> for example, check the following code:
>> ---
>> class Base : clonable {
>>     Base dup() {
>>        auto ret = cast(Base) this.classinfo.create;
>>        ret.x = this.x;
>>        ret.y = this.y;
>>        return ret;
>>     }
>>     int x = 6;
>>     double y = 8;
>> }
>>
>> class DerivedA : Base {
>>     override DerivedA dup() {
>>        auto ret = cast(DerivedA) super.dup;
>>        ret.w = this.w;
>>        return ret;
>>     }
>>     long w = 42;
>> }
> 
> Ah ha!  Ok, that's great.  This is exactly what I was looking for over 
> on the thread I started on D.learn ("implementing dup/clone for class 
> hierarchies").   Steven suggested using classinfo.create, but he left 
> his solution using a separate copy() method (or I just misunderstood 
> him), and that just ends up in a lot of casting.  But your method seems 
> to work.

There is one problem with that solution.  classinfo.create only works if 
the object being duplicated has a default constructor.  If you split it 
into dup and copy, then you can have the most derived class create the 
instance, calling whatever constructor it wants to.

I'm not sure why classinfo.create returns null though.  The doc for it 
only says "Create instance of Object represented by 'this'.".  Doesn't 
mention anything about ever returning null.

--bb
December 16, 2007
Re: what was wrong with struct & class in C++?
Bill Baxter wrote:
> Bill Baxter wrote:
>> Yigal Chripun wrote:

> There is one problem with that solution.  classinfo.create only works if 
> the object being duplicated has a default constructor.  If you split it 
> into dup and copy, then you can have the most derived class create the 
> instance, calling whatever constructor it wants to.
> 
> I'm not sure why classinfo.create returns null though.  The doc for it 
> only says "Create instance of Object represented by 'this'.".  Doesn't 
> mention anything about ever returning null.

Another problem is that it's far too easy for derived classes to forget 
that they need to implement their own dup(), so you end up with what's 
effectively another variation of the slicing problem.  You get an object 
that's a Derived but for some reason all the Derived-specific members 
are bogus.  I suppose it's not quite as bad as the real slicing problem 
though, because once detected it can always be fixed at the source, 
whereas with slicing, the line of code needing fixing could be anywhere.

And that problem exists with the split dup/copy solution too.

--bb
December 16, 2007
Re: what was wrong with struct & class in C++?
Bill Baxter wrote:
> Bill Baxter wrote:
>> Bill Baxter wrote:
>>> Yigal Chripun wrote:
> 
>> There is one problem with that solution.  classinfo.create only works 
>> if the object being duplicated has a default constructor.  If you 
>> split it into dup and copy, then you can have the most derived class 
>> create the instance, calling whatever constructor it wants to.
>>
>> I'm not sure why classinfo.create returns null though.  The doc for it 
>> only says "Create instance of Object represented by 'this'.".  Doesn't 
>> mention anything about ever returning null.
> 
> Another problem is that it's far too easy for derived classes to forget 
> that they need to implement their own dup(), so you end up with what's 
> effectively another variation of the slicing problem.  You get an object 
> that's a Derived but for some reason all the Derived-specific members 
> are bogus.  I suppose it's not quite as bad as the real slicing problem 
> though, because once detected it can always be fixed at the source, 
> whereas with slicing, the line of code needing fixing could be anywhere.
> 
> And that problem exists with the split dup/copy solution too.
> 
> --bb

here's a guide about securing Java code, it has a paragraph related to 
this discussion about dup (clone in java)
http://www.securingjava.com/chapter-seven/chapter-seven-1.html
I think it applies to D as well.
another solution to this problem is a copy constructor as in c++.
( it should be only provided by the class designer so no compiler 
generated copy c-tors, and should be explicitly called by the client code.)
also I think that const in D will remove a large portion of the need for 
dup because you could pass a const reference to your object instead of 
dupping it.
December 16, 2007
Re: what was wrong with struct & class in C++?
Bill Baxter Wrote:

> Another problem is that it's far too easy for derived classes to forget 
> that they need to implement their own dup(), so you end up with what's 
> effectively another variation of the slicing problem.  You get an object 
> that's a Derived but for some reason all the Derived-specific members 
> are bogus.  I suppose it's not quite as bad as the real slicing problem 
> though, because once detected it can always be fixed at the source, 
> whereas with slicing, the line of code needing fixing could be anywhere.
> 
> And that problem exists with the split dup/copy solution too.
> 
> --bb


It would be nice to have an attribute to say "this method must be implemented or re-implemented in the most derived class". It's basically the opposite of final, it's a must-override. This would be very useful when implementing cloning, serialization.

For instance, if you derive class B from a concrete class A, but fail to provide a reimplementation of dup(), class B will be abstract.

There are other ways to do this (never derive from concrete class, always carry the parent's interface when inheriting, documentation), but they don't offer strong guarantees.
December 17, 2007
Re: what was wrong with struct & class in C++?
guslay wrote:
> Bill Baxter Wrote:
> 
>> Another problem is that it's far too easy for derived classes to forget 
>> that they need to implement their own dup(), so you end up with what's 
>> effectively another variation of the slicing problem.  You get an object 
>> that's a Derived but for some reason all the Derived-specific members 
>> are bogus.  I suppose it's not quite as bad as the real slicing problem 
>> though, because once detected it can always be fixed at the source, 
>> whereas with slicing, the line of code needing fixing could be anywhere.
>>
>> And that problem exists with the split dup/copy solution too.
>>
>> --bb
> 
> 
> It would be nice to have an attribute to say "this method must be implemented or re-implemented in the most derived class". It's basically the opposite of final, it's a must-override. This would be very useful when implementing cloning, serialization.

Or like 'abstract' that never gets turned off.  "super abstract"!

(Stolen from Walter's original idea of "super const" for what is now 
called "invariant" in D2 ;-))

But seriously, the feature sounds useful, and like it would be not too 
difficult to implement.

> For instance, if you derive class B from a concrete class A, but fail to provide a reimplementation of dup(), class B will be abstract.
> 
> There are other ways to do this (never derive from concrete class, always carry the parent's interface when inheriting, documentation), but they don't offer strong guarantees.

I agree.  D is supposed to be about preventing you from making mistakes 
like that.

--bb
December 26, 2007
Re: what was wrong with struct & class in C++?
Walter Bright wrote:

[snip]

> In C++, one designs a class to be a reference type or a value type.

More generally, in *design*, a class either has entity semantics
(what you call "reference" types and C++ calls "polymorphic"
types) or value semantics.

That's the key terminology here, really.  What you call
reference types, C++ calls polymorphic types.  This is clearly
documented in every decent C++ library I've seen, and it just
does not lead to confusion for any competent C++ programmers.

(Possible exception: smart pointers have handle semantics:
they are copyable values, but act *like* references.)

> Interestingly, I've never once seen in documentation for a C++ class
> whether it is supposed to be used by reference or by value.

Interesting.  You've *never* seen any competent documentation
for C++ code, or you've just never been able to work out from
the documentation whether a type was polymorphic or not?

Or is this the *much* less interesting claim that the
terminology used is not the same, or that this is so obvious
that it's not explicitly flagged?

The C++ code I see (and I might say that's a fair amount) is
entirely clear on this.  I just picked a random type for which
I lacked prior knowledge of the documentation, and found
http://www.boost.org/doc/html/date_time/gregorian.html#date_time.gregorian.date_class
which says explicitly "The class is specifically designed to
NOT contain virtual functions. This design allows for efficient
calculation and memory usage with large collections of dates.",
and defines the semantics of copying.  In your terminology,
that's saying "this is a value type".

Let D stand on its own feet there's no need for misleading
FUD about C++ or the C++ community.

-- James
December 26, 2007
Re: what was wrong with struct & class in C++?
Walter Bright wrote:
> Russell Lewis wrote:
>> Walter Bright wrote:
>>> Russell Lewis wrote:
>>>> Hear, hear!  Good arguments, all!  It still doesn't answer why we
>>>> left out the little star on class-reference variable assignments. 
>>>> As I've argued before, the question of syntax is orthogonal to the
>>>> question of legal operations.  Make value-style operations illegal
>>>> for classes, and keep the star there, IMHO.
>>>
>>> It would make writing generic code unnecessarily difficult.
>>
>> ????  I thought that one of the key arguments for putting the syntax
>> back to the C++ way was that it would make generic code easier to
>> write.  Right now, when we perform an assignment, generic code can't
>> know (without specialization) whether it is assigning a value or a
>> reference type.
> 
> Because if I replace a struct with a class, then I have to go through
> every use of the struct and add a *.

That's the *problem* that we have in D today, that this
change of syntax is intended to solve.

The wonderful thing is that you would NOT have to do this
with explicit dereferencing a la C++, but you DO have to
do this if you change a class to a struct with D today
because D lacks uniform ways to access value and reference
types (as dereferencing is implicit in one case and explicit
in the other).  D uses the same syntax to mean utterly
different things depending on context, and that's anathema
to genericity.

Maybe examples are needed if people are talking past each
other this obviously?

-- James
December 26, 2007
Re: what was wrong with struct & class in C++?
James Dennett wrote:
>>> ????  I thought that one of the key arguments for putting the syntax
>>> back to the C++ way was that it would make generic code easier to
>>> write.  Right now, when we perform an assignment, generic code can't
>>> know (without specialization) whether it is assigning a value or a
>>> reference type.
>> Because if I replace a struct with a class, then I have to go through
>> every use of the struct and add a *.
> 
> (snip)
> 
> Maybe examples are needed if people are talking past each
> other this obviously?

If Walter will permit me, I will put some words in his mouth.  I think 
that I understand what Walter is saying, but I disagree with him. :)

Walter has argued that  structs should represent value-semantics and 
classes should represent reference-semantics.  Basically, the idea is 
that you can have the code:
	Foo thing = <whatever>;
	Foo other_thing = thing;
and have it work magically, correctly.  For a struct, the assignment 
will copy the values from one to the other; for a class, it will copy 
only the reference.  This is very useful for generic programming because 
you can then implement a linked list like this:

struct LinkedList(T) {
  T val;
  LinkedList!(T) next;
};

The point being that it doesn't matter whether T is a struct or a class, 
you will store the "right" think in the linked list node.  I see what 
he's saying there:
	LinkedList!(MyClass)  linkedList_with_reference_semantics;
	LinkedList!(MyStruct) linkedList_with_value_semantics;

But, IMHO, the definition of "right" thing should be up to the *user* of 
the template, not the structure of the language.  In my hoped-for-world, 
where a class reference needed the little '*' just like a 
pointer-to-struct did, we could still use the template above.  However, 
it would mean that the user of the template would need to use the 
template wisely:
	LinkedList!(MyClass*) linkedList_with_reference_semantics;
	LinkedList!(MyStruct) linkedList_with_value_semantics;

I like the idea that the user of the template gets the power (and 
responsibility) to use it wisely.

More importantly to me, I think that the consistency of syntax allows us 
to automatically solve any number of other problems.  Say that you have 
a template which absolutely *must* use reference semantics.  In my 
hoped-for-world, you could declare a single template:
	struct MyRefSemanticsTemplate(T*) {}

But in current D, you have to implement at least 2 different templates, 
one for classes, and another for structs.  Ick.

Walter, please step in if I have misrepresented your position.  Or, 
better yet, everybody else please assume that I've misrepresented his 
position unless he agrees. :)
Next ›   Last »
1 2 3 4
Top | Discussion index | About this forum | D home