February 15, 2009
"Jerry Quinn" <jlquinn@optonline.net> wrote in message news:gn9i7n$2ujl$1@digitalmars.com...
>
> Use a class when you have things that make sense to share, and use structs for when it makes more sense to have distinct copies.
>

Couldn't that rule conflict with cases where you'd want distinct copies but need features of classes that aren't available for structs (like inheritance)?


February 15, 2009
Andrei Alexandrescu wrote:
> Frits van Bommel wrote:
>> Andrei Alexandrescu wrote:
>>> Spot on. My ambitions are actually a tad higher. I want to
>>> implement containers as by-value structs defining value semantics
>>> and the needed primitives. Then, using introspection, I want to
>>> define a template Class that takes a struct and turns it into a
>>> class. For example:
>>>
>>> struct SomeContainer(T) { ref T opIndex(size_t n) { ... } }
>>>
>>> auto byval = SomeContainer!(int)(); auto javalike = new
>>> Class!(SomeContainer!(int));
>>>
>>> The type Class!(SomeContainer!(int)) will store a
>>> SomeContainer!(int) object and will define (by using introspection)
>>> a method opIndex of which implementation will do what the struct's
>>> method does. The net effect is as if the programmer sat down with
>>> SomeContainer and changed "struct" to "class". Of course without
>>> the code duplication and the maintenance nightmare :o).
>>>
>>> Class should work for primitive types, e.g. Class!(int) should do
>>> what Integer does in Java and so on.
>>
>> An interesting idea. Would Class!(T) allow polymorphism, or would all
>>  methods be implicitly final?
> 
> Polymorphic. Then of course there's the Finalize!(C) template that takes
> a class C and makes all of its methods final. Compile-time introspection
> is a treasure trove.
> 
>> Would it also delegate template methods?
> 
> Good question. I don't know how template methods could be handled.

Well, one simple way would be to just implement opDot() to take care of those :). (Though that would not allow my wrapping suggestion)
Another way would be pressuring Walter into allowing compile-time introspection of templates, and providing forwarding aliases or methods (possibly wrapping parameters and return types).

>> And what would happen to any T methods accepting or returning T?
>> Would they keep returning T or would they be replaced by Class!(T)?
>> Or perhaps both, through overloading? Same question for types derived
>> from T (e.g. Nullable!(T), or SomeContainer!(T))?
> 
> I don't know. We need more experience to figure that out.
> 
>> For instance in the case of a container it would be nice to allow Class!(LinkedList!(T)).splice!(Class!(LinkedList!(T)) other) if
>> there's a LinkedList!(T).splice(LinkedList(T) other).
>>
>> If such unwrapping were done, how would such methods react if the Class!(LinkedList!(T)) passed into slice() were null?
> 
> Segfault :o).

I was hoping you'd answer "They'd use non-nullable parameter types" here ;).
February 15, 2009
On Sun, 15 Feb 2009 13:25:24 -0800, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:

>Frits van Bommel wrote:
>> Andrei Alexandrescu wrote:
>>> Spot on. My ambitions are actually a tad higher. I want to implement containers as by-value structs defining value semantics and the needed primitives. Then, using introspection, I want to define a template Class that takes a struct and turns it into a class. For example:
>>> 
>>> struct SomeContainer(T) { ref T opIndex(size_t n) { ... } }
>>> 
>>> auto byval = SomeContainer!(int)(); auto javalike = new
>>> Class!(SomeContainer!(int));
>>> 
>>> The type Class!(SomeContainer!(int)) will store a
>>> SomeContainer!(int) object and will define (by using introspection)
>>> a method opIndex of which implementation will do what the struct's
>>> method does. The net effect is as if the programmer sat down with
>>> SomeContainer and changed "struct" to "class". Of course without
>>> the code duplication and the maintenance nightmare :o).
>>> 
>>> Class should work for primitive types, e.g. Class!(int) should do what Integer does in Java and so on.
>> 
>> An interesting idea. Would Class!(T) allow polymorphism, or would all
>>  methods be implicitly final?
>
>Polymorphic. Then of course there's the Finalize!(C) template that takes a class C and makes all of its methods final. Compile-time introspection is a treasure trove.

It is, but in this particular case why not inherit from C and make the derived class final?

>
>> Would it also delegate template methods?
>
>Good question. I don't know how template methods could be handled.
>
>> And what would happen to any T methods accepting or returning T?
>> Would they keep returning T or would they be replaced by Class!(T)?
>> Or perhaps both, through overloading? Same question for types derived
>> from T (e.g. Nullable!(T), or SomeContainer!(T))?
>
>I don't know. We need more experience to figure that out.
>
>> For instance in the case of a container it would be nice to allow
>> Class!(LinkedList!(T)).splice!(Class!(LinkedList!(T)) other) if
>> there's a LinkedList!(T).splice(LinkedList(T) other).
>> 
>> If such unwrapping were done, how would such methods react if the Class!(LinkedList!(T)) passed into slice() were null?
>
>Segfault :o).
>
>
>Andrei
February 15, 2009
On Sun, 15 Feb 2009 18:50:13 +0300, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:

> Walter and I have been discussing what the regime of statically-sized arrays should be. In short, the behavior that's most consistent with everything else is to treat them as values.
>
> This also brings the problems of e.g. containers - should they have consistent value semantics (like in STL) or consistent reference semantics (like in Java)? Or both, subject to a policy parameter?
>
> At any rate, it looks like the days of cheap by-value copying are over. Even today, large structs become onerous to pass by value. If copy constructors start being used, we can't simply assume that copying things around is not a problem.
>
> Today we use:
>
> void fun(T)(T value) { ... }
>
> or
>
> void fun(T)(ref T value) { ... }
>
> to make a choice in parameter passing. But neither is perfect. The former copies too much, and the latter does not accept rvalues. C++ has found a solution to this problem in the guise of references to const objects. Those can accept lvalues and rvalues alike. It looked like a good solution at the time, but it's caused huge issues in other areas of the language, which ultimately led to a complicated feature called rvalue references. So I'd like to veer a different way.
>
> I was thinking of something like:
>
> void fun(T)(ref? T value) { ... }
>
> This really generates two overloads:
>
> void fun(T)(ref T value) { ... }
> void fun(T)(T value) { return (cast(void (ref T)) fun)(value); }
>
> meaning that there are two fun instantiations, one actually doing the work, and the other just forwarding rvalues to it.
>
> There's one little extra thing that the feature does. A function may want to return its incoming argument, in which case it might say:
>
> ref? T fun(T)(ref? T value) { ... return value; }
>
> in which case the generated code is:
>
> ref T fun(T)(ref T value) { ... }
> T fun(T)(T value) { return (cast(void (ref T)) fun)(value); }
>
> I wanted to discuss this to gather more ideas and insights on the topic.
>
>
> Andrei

Yay,

const?(char)[] substr(const?(char)[] str, int from, int to)
{
   return str[from..to];
}

That's what I saw in your proposal :)

February 16, 2009
Nick Sabalausky Wrote:

> "Jerry Quinn" <jlquinn@optonline.net> wrote in message news:gn9i7n$2ujl$1@digitalmars.com...
> >
> > Use a class when you have things that make sense to share, and use structs for when it makes more sense to have distinct copies.
> >
> 
> Couldn't that rule conflict with cases where you'd want distinct copies but need features of classes that aren't available for structs (like inheritance)?

However, with reference semantics, you have no way to achieve objects laid out in a contiguous array, unless I'm missing something.

If you want inheritance, copy semantics are an issue.  For example, if you have struct A : B, and an array of B, you can't put an A in it, since there's not enough space for an A (unless A adds 0 storage).  If you have an array of A, inheritance isn't really buying you anything over having a B as the first member of A.  Any place where you'd want to use A as a B, you can get to the member B struct directly.



February 16, 2009
Jerry Quinn wrote:
> If you want inheritance, copy semantics are an issue.  For example, if you have struct A : B, and an array of B, you can't put an A in it, since there's not enough space for an A (unless A adds 0 storage).  If you have an array of A, inheritance isn't really buying you anything over having a B as the first member of A.  Any place where you'd want to use A as a B, you can get to the member B struct directly.

There are really three separate issues here: copy policy, storage policy, and clean-up policy.

Copy policy: what are the semantics of 'a = b;'?
  Reference semantics:
    'a' becomes a reference to the same object as 'b'.
  Value semantics:
    The object 'b' is copied into 'a'.

Storage policy: given 'A a;', where is the object stored?
  Direct storage:
     Directly in the variable 'a'.
  Padded direct storage:
     Directly in the variable 'a', but with enough padding to also store
     an object of any subtype of 'A'.  Requires that the full set of the
     subtypes of 'A' be known at compile time.
  Heap storage:
    The object is stored on the heap; the variable 'a' merely contains a
    pointer.

Clean-up policy: given 'A a;', when is 'a''s destructor called?
  RAII:
    Immediately when 'a' goes out of scope.  The object still exists at
    this point.
  Garbage collection:
    Sometime after the last reference to the object is no longer
    reachable from any global or stack variable.  By the time the
    destructor is called, other objects referenced by the object may
    already have been destroyed.


These three policies are mostly conceptually orthogonal, although
garbage collection requires heap storage.  Interesting combinations include:
  Reference semantics, heap storage, garbage collection:
    Classes in D.
  Value semantics, direct storage, RAII:
    Structs in D.
  Value semantics, padded direct storage, RAII:
    One possible approach to value types with inheritance but without
    slicing.
  Value semantics, heap storage, RAII:
    Another possible approach to value types with inheritance but
    without slicing.
  Reference semantics, heap storage, RAII (with reference counting):
    Reference types with destructors that actually work.


-- 
Rainer Deyke - rainerd@eldwood.com
February 16, 2009
On Mon, 16 Feb 2009 00:20:47 +0300, Frits van Bommel <fvbommel@remwovexcapss.nl> wrote:

> Andrei Alexandrescu wrote:
>> Spot on. My ambitions are actually a tad higher. I want to implement containers as by-value structs defining value semantics and the needed primitives. Then, using introspection, I want to define a template Class that takes a struct and turns it into a class. For example:
>>  struct SomeContainer(T)
>> {
>>     ref T opIndex(size_t n) { ... }
>> }
>>  auto byval = SomeContainer!(int)();
>> auto javalike = new Class!(SomeContainer!(int));
>>  The type Class!(SomeContainer!(int)) will store a SomeContainer!(int) object and will define (by using introspection) a method opIndex of which implementation will do what the struct's method does. The net effect is as if the programmer sat down with SomeContainer and changed "struct" to "class". Of course without the code duplication and the maintenance nightmare :o).
>>  Class should work for primitive types, e.g. Class!(int) should do what Integer does in Java and so on.
>
> An interesting idea. Would Class!(T) allow polymorphism, or would all methods be implicitly final?
> Would it also delegate template methods?
>
> And what would happen to any T methods accepting or returning T? Would they keep returning T or would they be replaced by Class!(T)? Or perhaps both, through overloading?
> Same question for types derived from T (e.g. Nullable!(T), or SomeContainer!(T))?
>
> For instance in the case of a container it would be nice to allow Class!(LinkedList!(T)).splice!(Class!(LinkedList!(T)) other) if there's a LinkedList!(T).splice(LinkedList(T) other).
>
> If such unwrapping were done, how would such methods react if the Class!(LinkedList!(T)) passed into slice() were null?

You can't do that, because splice accepts non-nullable argument :)
1 2
Next ›   Last »