May 11, 2008
Janice Caron wrote:
> On 11/05/2008, Yigal Chripun <yigal100@gmail.com> wrote:
>> I don't follow. How can there be only one sort instance?
> 
> Because I only instantiated it once - specifically with the delegate named "compare".
> 
>>  the mysort function uses both sorts
> 
> No, the mysort function is handed a delegate an input parameter.
> 
> 
>> so the code for both needs to be in
>> the executable...
> 
> The comparison functions need to be in the executable, but not two separate instantiations of sort.
> 
> Why is my last post not clear? You get two instantiations with
> 
>     sort!(English)(text);
>     sort!(Czech)(text);
> 
> because I statically passed in two separate delegates, but you only get one instantiation with
> 
>     sort!(compare)(array)
> 
> because I statically passed in only one delegate. That you passed two into mysort is irrelevant, because that's a runtime thing.

OK, I think i understand you now. there is only one sort instance that calls compare. compare changes at run-time. in that case, I was wrong. sorry for the confusion.
May 12, 2008
Janice Caron wrote:
> On 11/05/2008, Yigal Chripun <yigal100@gmail.com> wrote:
>> From what I know, an efficient STL implementation would use C style
>>  containers with void* under the hood to minimize executable bloat.
> 
> I /think/ I get what you're saying here, which is (correct me if I'm
> wrong, but) given
> 
>     class C {}
>     class D {}
> 
> then
> 
>     Vector!(C) c;
>     Vector!(D) d;
> 
> produces two instantiations of Vector! - one for C and one for D - and yet those instantiations will produce byte-for-byte identical machine code.
> 
> However - this is a linker problem, not a template problem, and Walter has said that this will be fixed in the future. So in the future, whenever any two or more segments consist of identical bytes, the linker may keep one and throw the rest away.
> 
> The moral of the story is: specify your intent, but leave the implementation to the compiler/linker.
> 
> If you try to "outguess" the compiler, by instantiating only one single Vector!(void*), then in the long run, all you are doing is creating obfuscated code for short term gain.

I don't know about any plans to change the dm linker. it is currently
written directly in assembly and highly optimized. any change would
require a lot of work and probably a rewrite. also, if walter ever
decides to work on the linker he should change the format of the object
files. Personally i don't understand a great deal about that subject,
but i got the impression in this NG from more knowledgeable people that
such a change is needed.
what i mean was doing something like this:
for example for list - defining a regular LinkedListImpl class which
holds void* as items, and then defining a class LinkedList(T) which uses
internally the LinkedListImpl class. this way the templates and
interface hierarchy is separate from the implementation.
the templatized interfaces and classes in the hierarchy provide the high
level API and the *Impl classes provide efficient implementations
without bloating the executable code. you get a different instances of
vector as per your example for each unique type T, BUT those
instantiations are merely small shells that call common code in the
matching vectorImpl class.
that is not the same as using linkedList!(void*) since you do get a
layer that provides the convenience and type safety. If the
linker/compiler could automate this, that would be great. but it can be
accomplished without that too.
May 12, 2008
Dee Girl wrote:
<snip>
thanks for the effort explaining me the issue. I've already understood
my mistake thanks to Janice' explanation, though.
May 12, 2008
Koroskin Denis wrote:
> On Sun, 11 May 2008 19:47:14 +0400, Yigal Chripun <yigal100@gmail.com> wrote:
> 
>> Janice Caron wrote:
>>> On 11/05/2008, Yigal Chripun <yigal100@gmail.com> wrote:
>>>> I want to be able to do this:
>>>>  List!(T) col = new LinkedList!(T);
>>>>
>>>>  and be able to change the linked list to a vector in the future (or
>>>> any
>>>>  other suitable collection) with the changes limited to this line
>>>> alone.
>>>>  is it possible with templates?
>>>
> 
> Well, yes, is possible. Even with STL.
> 
> A typical trick I often do is this:
> 
> typedef std::vector<Item*> Items;
> Items items;
> 
> items.pushBack(new Item());
> Item* item = items.front();
> Items::iterator it = std::find(items.begin(), items.end(), someItem);
> if (it != items.end()) {
>     items.erase(it);
> }
> std::sort(items.begin(), items.end());
> // etc
> 
> And now if I decide to exchange vector with list or deque or anything else, all I need to do is to change a single line:
> 
> typedef std::list<Item*> Items;
> 
> I don't say that it is the best way to stick with, but it usually works.
> 
>> ight...
>> and what about my function?
>> void WalkListAndDoSomething(List!(T) list);
> 
> Use templates. Or use polymorhism, if you expect containers to implement some generic interface.

all I can say is yuk. i can use tricks like that, or i can just use D's
interface. I personally prefer the later.
thanks for the info, though.
May 12, 2008
On 12/05/2008, Yigal Chripun <yigal100@gmail.com> wrote:
>  if [re]interpret_cast can be implemented by the user and it is quite
>  dangerous, than i think people who need that should implement that
>  themselves with union as you showed. no need for D to provide that
>  functionality by default.

Agreed. As a general principle, if it can be implemented in a library, then there's no need for new syntax. /However/ - it /should/ be implemented in a library, because we'd want to outlaw

    void* p;
    char* q = cast(char*) p; /* ERROR -- cannot cast from void* to char* */

reinterpret_cast should be the only kind of cast to allow this:

    char* q = reinterpret_cast!(char*)(p); /* OK */

[EDIT - see below]


>  I'm still not sure about constancy casts. but in order to provide the
>  minimal solution, I'd prefer cast!(T) to do constancy casts over adding
>  another construct for that.

OK.


>  also, I don't like the static cast(), and it
>  might cause ambiguity problems. what is this good for?

There can't be any ambiguity problems (yet), because you can't (yet)
call functions which take a "this" parameter at compile time. If you
could, then "static cast(int)x", might invoke typeof(x)'s opCast()
function, so arguably there might be ambiguity problems in the future
-- if you're entering an obfuscated D contest! Let's ignore that for
now.

What's it good for? Performance. If B is a subclass of A, and I *know* that my A is actualy a B, static_cast<B> allows me avoid the runtime penalty that I'd get with dynamic_cast<B>. There would also be no need to check the return value, because static_cast always succeeds.

Obviously, reinterpret_cast<B> will work everywhere that
static_cast<B> works, but static_cast<B>(a) gets me a compile-time
check that typeof(a) is a subclass or superclass of B, wheras with
reinterpret_cast<B>(a) there's no compile-time checking at all.

static_cast<T> can also do plain conversion, e.g.

    double d;
    int n = static_cast<int>(d);

So, if the old-fashioned default cast were ever outlawed in C++, static_cast is the one you'd use to replace it for "plain" casts.

However - given that we choose to define cast(T) to mean "if T is a class then use RTTI, otherwise don't", then, for non-classes, our cast(T) achieves what static_cast<T> does for C++. For classes ... well, who needs static cast anyway? Let's ditch it.

So that leaves us with:

    cast(T)x   // (T is a class) ? dynamic_cast : static_cast
    cast!(T)x   // const_cast
    reinterpret_cast!(T)(x)    // reinterpret_cast, implemented in library

That pretty much covers it.



>  open question: if this proposal does not provide a reinterpret_cast, can
>  we split the cast!(T) to perform either a type change /or/ a constancy
>  change?

I don't see how that could work in general.

We could allow it for specific cases though - e.g casting from void* to T*, or from void[] to T[], etc. I think in those special cases, it could work.

So, to turn an S* into a T* (where both are structs), you would do

    S* s = cast!(S*)cast(void*)t



>  // below case uses the same bit pattern, exception on invalid double
>  // value
>  auto d2 = cast!(double)n;

I don't like that, and I see no need for it. reinterpret_cast!(double) can do that job. And there's no need for exception throwing - that would entail a runtime check, and at this level, we must assume the programmer knows what they're doing.


>  same goes for classes with user defined cast and cast! operators.

There should never be any such thing as a user-defined cast! operator. It's meaningless. Look at it like this - C++ has four different kinds of cast, but only one kind of cast operator. It's all you need.


>  downcast can be treated as a compiler pre-defined conversion from a
>  class to its derived classes. uses the cast(Derived) form and throws
>  exception on error.

It's not an error to fail an RTTI cast! e.g.

    void f(A a)
    {
        B1 b1 = cast(B1)a;
        if (b1 !is null) { ... }

        B2 b2 = cast(B2)a;
        if (b2 !is null) { ... }

        B3 b3 = cast(B3)a;
        if (b3 !is null) { ... }
    }

So no need for throwing exceptions.


>  constancy is performed via cast!(T).

OK.


>  pointers are cast with cast!(T)

Only pointers to void.

>  so to convert const(int)* to const(long*) you'll need something like:
>  auto res = cast!(const(long*))cast(const(long)*)x;

I would have said

    cast!(const(long*))cast(const(void)*)x;

or

    reinterpret_cast!(const(long*))x;
May 12, 2008
Koroskin Denis wrote:
> Use templates. Or use polymorhism, if you expect containers to implement some generic interface.

Problem with using interfaces or base classes is, you can't do the following with interfaces, and you can't do it efficiently with base classes:

interface Collection(T)
{
	void addRange(U : T)(Collection!(U) collection);
}


While having an abstract class Collection that defines that template would make the end users a bit happier, it'd basically boil down to:

void addRange(U : T)(Collection!(U) collection)
{
	this.reserve(collection.length);
	foreach (u; collection) this.add(u);
}
May 12, 2008
On 12/05/2008, Yigal Chripun <yigal100@gmail.com> wrote:
> I don't know about any plans to change the dm linker.

You do now. :-)

The following is an exact word-for-word quote, posted 23 Mar by Walter Bright:

"Templatizing is the language solution. I know about the bloat problem, but that is really an implementation issue. I know the linker doesn't do it now, but identical blocks of generated code with different names should be merged."



>  what i mean was doing something like this:
>  <snip>

And that's a perfectly reasonable strategy, but it only works for pointers to structs. (In D, it won't work for class references). Structs, by contrast, should probably be written into a collection by value, not by reference. In short, it's a C++-specific strategy.
May 12, 2008
Janice Caron wrote:
> On 12/05/2008, Yigal Chripun <yigal100@gmail.com> wrote:
>> I don't know about any plans to change the dm linker.
> 
> You do now. :-)
> 
> The following is an exact word-for-word quote, posted 23 Mar by Walter Bright:
> 
> "Templatizing is the language solution. I know about the bloat problem, but that is really an implementation issue. I know the linker doesn't do it now, but identical blocks of generated code with different names should be merged."
> 
>
I know about that quote. that doesn't mean anything. saying that
something should be done in a specific way is not the same as saying one
would work to make that something work in that specific way.
Walter acknowledges some problems with the linker, he also said that
fixing that is a huge amount of work for the reasons I've stated in a
previous post. so that doesn't mean Walter is planning to fix those
problems in the near future.



> 
>>  what i mean was doing something like this:
>>  <snip>
> 
> And that's a perfectly reasonable strategy, but it only works for pointers to structs. (In D, it won't work for class references). Structs, by contrast, should probably be written into a collection by value, not by reference. In short, it's a C++-specific strategy.
hmm... you're right... :(
maybe there is a way to adapt that way of thinking to D or maybe this
would be optimized automatically by the liker in the future, as you say
all i can say is that I hope the linker will be worked on...
May 12, 2008
Janice Caron wrote:
<snip>
I agree on mostly everything, I'm still not convinced about the rtti
cast not throwing on error, though.
I'll just add that cast! won't be used to generally change types but
since we added _specific_ cases that cast! handles, it's worth
considering adding another special case which is the static_cast for
classes.
May 12, 2008
Yigal Chripun wrote:
> hmm... you're right... :(
> maybe there is a way to adapt that way of thinking to D or maybe this
> would be optimized automatically by the liker in the future, as you say
> all i can say is that I hope the linker will be worked on...

(light-bulb)
since D has Object from which all the classes inherit (unlike C++) the
same idea I previously suggested could be employed, only instead of
void* use Object.
again, since it is buried inside the implementation, you still get
proper generic interfaces and classes. this sounds even cleaner than
void*. what do you think?