October 12, 2013
On Saturday, 12 October 2013 at 07:47:18 UTC, Jonathan M Davis wrote:
> On Saturday, October 12, 2013 09:32:09 luminousone wrote:
>> And again, the casting solution is a bloody hack, it is loaded
>> with corner cases that will break things if you are not aware of
>> them. It also requires an allocated instance of that object
>
> Of course, it requires an instance of the object. The point is to test whether
> an instance is of a type derived from a particular type, not whether a
> particular type is derived from a particular type. If the OP wants to test
> whether a particular type is derived from another type, then casting is not
> the right solution. The way to do that is to use an is expression, e.g.
> is(Derived : Base). If you really care that the type is a derived type and
> want to avoid any other type of implicit conversions, then you need to use
> some compile time reflection to do that, but that's often overkill and
> completely kills the ability to create class which "inherits" via alias this,
> which is the main purpose for alias this existing in the first place.
>
>> It is bad practice.
>
> It's standard practice. Both the online docs and TDPL will tell you to use a
> cast to determine whether a particular object's type is derived from a
> particular type. And I completely disagree that it's bad practice, but clearly
> we're not going to agree on that.
>
> - Jonathan M Davis

1.

opCast, and alias can break the cast approach. You pointed this out yourself.

2.

The requested function is testing types not instance state.

bool instanceOf(A,B)( B value ) {
        assert( value !is null );
        return inheritsFrom(A,typeof(value));
}

3.

Cast breaks on null, so this must be checked on every call, and leads to a 3 state outcome, true, false, and null, null then returns false, which is potentially incorrect and could cause odd bugs hard to trace.

Again it is bad practice regardless of what any document says.
October 12, 2013
On Saturday, October 12, 2013 10:24:13 luminousone wrote:
> 1.
> 
> opCast, and alias can break the cast approach. You pointed this out yourself.

And as I pointed out, that's perfectly acceptable in most cases, because what you care about is the conversion. alias this is specifically designed with the idea that you can subtype a struct or class with it. Checking with typeid to get the exact type would be circumventing that design. So, casting is almost always the correct option.

> 2.
> 
> The requested function is testing types not instance state.
> 
> bool instanceOf(A,B)( B value ) {
>          assert( value !is null );
>          return inheritsFrom(A,typeof(value));
> }

Fine. That doesn't invalidate casting to check an instance. They're two different things.

> 3.
> 
> Cast breaks on null, so this must be checked on every call, and leads to a 3 state outcome, true, false, and null, null then returns false, which is potentially incorrect and could cause odd bugs hard to trace.

null is _supposed_ to be false. The whole point is to check whether the object in question can be used as the type that you're casting to. If it's null, it isn't the type that you're casting to - it's null. So, false is exactly what it should be doing. It's specifically designed so that you can do

if(auto c = cast(MyClass)obj)
{
    ...
}

> Again it is bad practice regardless of what any document says.

And I completely disagree. Casting is doing precisely what it's designed to do, and I really don't see a problem with it.

- Jonathan M Davis
October 12, 2013
On Saturday, 12 October 2013 at 08:40:42 UTC, Jonathan M Davis wrote:
> On Saturday, October 12, 2013 10:24:13 luminousone wrote:
>> 1.
>> 
>> opCast, and alias can break the cast approach. You pointed this
>> out yourself.
>
> And as I pointed out, that's perfectly acceptable in most cases, because what
> you care about is the conversion. alias this is specifically designed with the
> idea that you can subtype a struct or class with it. Checking with typeid to
> get the exact type would be circumventing that design. So, casting is almost
> always the correct option.
>
>> 2.
>> 
>> The requested function is testing types not instance state.
>> 
>> bool instanceOf(A,B)( B value ) {
>>          assert( value !is null );
>>          return inheritsFrom(A,typeof(value));
>> }
>
> Fine. That doesn't invalidate casting to check an instance. They're two
> different things.
>
>> 3.
>> 
>> Cast breaks on null, so this must be checked on every call, and
>> leads to a 3 state outcome, true, false, and null, null then
>> returns false, which is potentially incorrect and could cause odd
>> bugs hard to trace.
>
> null is _supposed_ to be false. The whole point is to check whether the object
> in question can be used as the type that you're casting to. If it's null, it
> isn't the type that you're casting to - it's null. So, false is exactly what
> it should be doing. It's specifically designed so that you can do
>
> if(auto c = cast(MyClass)obj)
> {
>     ...
> }
>
>> Again it is bad practice regardless of what any document says.
>
> And I completely disagree. Casting is doing precisely what it's designed to
> do, and I really don't see a problem with it.
>
> - Jonathan M Davis


1.

Your hand waving corner cases.

2.

Your throwing away perfectly good opportunities for the compiler to use CTFE by depending on allocated instance state(or at least its address) to get your answers.

To say nothing of the fact, that reflection won't have the list of corner case problems that casting does.

Reflection is simply correct in more cases then casting for this activity, it is also more generic, and requires fewer function parameters.

3.

Hardly, casting null works perfectly fine in c and c++, D strives towards more correct code, its one of the things I love about the language.

It is precisely because D checks null on cast, that creates the potential ternary return. A corner case I grant you, but still.

Further D being a statically typed language, it could very well be argued, that a nulled reference, still has a type.

meaning...

bool instanceOf(A,B)( B value ) {
         return inheritsFrom(A,B);
}

would be more correct then using typeOf(value) version above.
October 12, 2013
On Saturday, October 12, 2013 11:50:27 luminousone wrote:
> On Saturday, 12 October 2013 at 08:40:42 UTC, Jonathan M Davis
> 
> wrote:
> > On Saturday, October 12, 2013 10:24:13 luminousone wrote:
> >> 1.
> >> 
> >> opCast, and alias can break the cast approach. You pointed this out yourself.
> > 
> > And as I pointed out, that's perfectly acceptable in most
> > cases, because what
> > you care about is the conversion. alias this is specifically
> > designed with the
> > idea that you can subtype a struct or class with it. Checking
> > with typeid to
> > get the exact type would be circumventing that design. So,
> > casting is almost
> > always the correct option.
> > 
> >> 2.
> >> 
> >> The requested function is testing types not instance state.
> >> 
> >> bool instanceOf(A,B)( B value ) {
> >> 
> >>          assert( value !is null );
> >>          return inheritsFrom(A,typeof(value));
> >> 
> >> }
> > 
> > Fine. That doesn't invalidate casting to check an instance.
> > They're two
> > different things.
> > 
> >> 3.
> >> 
> >> Cast breaks on null, so this must be checked on every call, and
> >> leads to a 3 state outcome, true, false, and null, null then
> >> returns false, which is potentially incorrect and could cause
> >> odd
> >> bugs hard to trace.
> > 
> > null is _supposed_ to be false. The whole point is to check
> > whether the object
> > in question can be used as the type that you're casting to. If
> > it's null, it
> > isn't the type that you're casting to - it's null. So, false is
> > exactly what
> > it should be doing. It's specifically designed so that you can
> > do
> > 
> > if(auto c = cast(MyClass)obj)
> > {
> > 
> >     ...
> > 
> > }
> > 
> >> Again it is bad practice regardless of what any document says.
> > 
> > And I completely disagree. Casting is doing precisely what it's
> > designed to
> > do, and I really don't see a problem with it.
> > 
> > - Jonathan M Davis
> 
> 1.
> 
> Your hand waving corner cases.

No. I'm saying that where there are corner cases, they are intentional and expected. Casting takes conversions into account that are supposed to be there, and using any kind reflection would bypass that.

And if you know that you're dealing with a reference to a base class, and you're testing whether that reference points to a particular derived class (as opposed to simply checking whether the conversion works), you know that the corner cases don't even exist unless the base class overrode opCast or alias this to that derived class (which would be a really broken design, since base classes shouldn't know about derived classes).

> 2.
> 
> Your throwing away perfectly good opportunities for the compiler to use CTFE by depending on allocated instance state(or at least its address) to get your answers.

It's _impossible_ to use CTFE for testing whether a particular instance is of a particular type. You can test its _reference_. You can test whether a particular type is derived from another. But you _can't_ test a particular instance, because it doesn't even exist at compile time.

> 3.
> 
> Hardly, casting null works perfectly fine in c and c++, D strives towards more correct code, its one of the things I love about the language.
> 
> It is precisely because D checks null on cast, that creates the potential ternary return. A corner case I grant you, but still.
> 
> Further D being a statically typed language, it could very well be argued, that a nulled reference, still has a type.

Sure, the reference has a type, but the object doesn't because there isn't one. It's typeof(null). And the whole point of casting to check the type is to check the obect's type, not the reference. If it's null, the reference does _not_ point to an object, so there's no object to test, and it is _not_ of the type that you're testing for.

> meaning...
> 
> bool instanceOf(A,B)( B value ) {
>           return inheritsFrom(A,B);
> }
> 
> would be more correct then using typeOf(value) version above.

Again, this is completely wrong for the case where you're checking an object's type. That is a _runtime_ operation for determining what the type of the actual object being referred to is. All you're doing here is checking whether B is a derived class of A, which is for testing the types of the _references_, not the actual type of the object. B could inherit from A, but the object could in fact be of type C which is derived from B - or any other type which is derived from B. e.g.

class A {}
class B : A {}
class C : B {}

A a1 = new A;
A a2 = new B;
A a3 = new C;

assert(cast(B)a1 is null);
assert(cast(B)a2 !is null);
assert(cast(B)a3 !is null);

These are all testing the objects being referred to and _not_ anything about the types themselves.

You use compile time reflection when you're testing _types_ (e.g. whether B is derived from A). You use casts to determine whether a reference of a base type is actually referring to an object of a particular derived class. The two use cases are completely different. If you're using compile time reflection to determine whether a particular instance is of a particular type, then you're doing it wrong, because all you're testing are the references, not the object. If you're using casts to determine whether a particular type is derived from another type, then that's wrong because of the extra conversions that could take place (not to mention, it's then a runtime check instead of a compile- time one). You have to use the right test for the right situation.

- Jonathan M Davis
October 12, 2013
On 10/11/13 06:13, Agustin wrote:
> I have a function that needs to check if the template provided inherit a class.
> 
> For example:
> 
> public void function(T, A...)(auto ref A values)
> {
>   // static assert(IsBaseOf(L, T));
> }
> 
> Check if T inherit class "L". Same result that std::is_base_of<L, T>::value using C++. Any clean way to do it, without a dirty hack.

As others have said, a "T:L" template arg specialization will often be enough.

When you really need to restrict the i/f (which can be a good idea),
something like this will work:

   template isBaseOf(BASE, C) {
      static if (is(C S == super))
         enum isBaseOf = {
            foreach (A; S)
               static if (is(A==BASE))
                  return true;
            return is(C==BASE);
         }();
      else
         enum isBaseOf = is(C==BASE);
   }

artur
October 12, 2013
On 10/12/13 13:42, Artur Skawina wrote:
>    template isBaseOf(BASE, C) {
>       static if (is(C S == super))
>          enum isBaseOf = {
>             foreach (A; S)
>                static if (is(A==BASE))
>                   return true;
>             return is(C==BASE);
>          }();
>       else
>          enum isBaseOf = is(C==BASE);
>    }

That was too verbose.

   template isBaseOf(BASE, C) {
      enum isBaseOf = {
         static if (is(C S == super))
            foreach (A; S)
               static if (is(A==BASE))
                  return true;
         return is(C==BASE);
      }();
   }

artur

October 12, 2013
On Saturday, 12 October 2013 at 11:50:05 UTC, Artur Skawina wrote:
> On 10/12/13 13:42, Artur Skawina wrote:
>>    template isBaseOf(BASE, C) {
>>       static if (is(C S == super))
>>          enum isBaseOf = {
>>             foreach (A; S)
>>                static if (is(A==BASE))
>>                   return true;
>>             return is(C==BASE);
>>          }();
>>       else
>>          enum isBaseOf = is(C==BASE);
>>    }
>
> That was too verbose.
>
>    template isBaseOf(BASE, C) {
>       enum isBaseOf = {
>          static if (is(C S == super))
>             foreach (A; S)
>                static if (is(A==BASE))
>                   return true;
>          return is(C==BASE);
>       }();
>    }
>
> artur

I like that! Avoids importing std.traits, And will correctly handle interfaces as well.
October 12, 2013
On 10/12/13 21:42, luminousone wrote:
> On Saturday, 12 October 2013 at 11:50:05 UTC, Artur Skawina wrote:
>>    template isBaseOf(BASE, C) {
>>       enum isBaseOf = {
>>          static if (is(C S == super))
>>             foreach (A; S)
>>                static if (is(A==BASE))
>>                   return true;
>>          return is(C==BASE);
>>       }();
>>    }
> 
> I like that! Avoids importing std.traits, And will correctly handle interfaces as well.

It's also buggy. A more useful version would be:

   template isBaseOf(BASE, C) {
      enum isBaseOf = {
         static if (is(C S == super))
            foreach (A; S)
               static if (isBaseOf!(BASE, A))
                  return true;
         return is(C==BASE);
      }();
   }

Sorry, didn't test this properly; going to blame the dlang is-expression docs.
It's not like D has multiple inheritance, so using "base classes" (plural)
is very misleading...

artur
October 13, 2013
On Saturday, 12 October 2013 at 23:48:56 UTC, Artur Skawina wrote:
> On 10/12/13 21:42, luminousone wrote:
>> On Saturday, 12 October 2013 at 11:50:05 UTC, Artur Skawina wrote:
>>>    template isBaseOf(BASE, C) {
>>>       enum isBaseOf = {
>>>          static if (is(C S == super))
>>>             foreach (A; S)
>>>                static if (is(A==BASE))
>>>                   return true;
>>>          return is(C==BASE);
>>>       }();
>>>    }
>> 
>> I like that! Avoids importing std.traits, And will correctly handle interfaces as well.
>
> It's also buggy. A more useful version would be:
>
>    template isBaseOf(BASE, C) {
>       enum isBaseOf = {
>          static if (is(C S == super))
>             foreach (A; S)
>                static if (isBaseOf!(BASE, A))
>                   return true;
>          return is(C==BASE);
>       }();
>    }
>
> Sorry, didn't test this properly; going to blame the dlang is-expression docs.
> It's not like D has multiple inheritance, so using "base classes" (plural)
> is very misleading...
>
> artur

yea "is" can be a lil confusing, especially that "is( C S == super)" expression.

I take it that "is( C S == super)" is only the bases directly listed for inheritance by the class and not the bases bases as well? which is the reason for the change you made their?

That expression is weird lol.
October 13, 2013
On 10/13/13 02:25, luminousone wrote:
> On Saturday, 12 October 2013 at 23:48:56 UTC, Artur Skawina wrote:
>>    template isBaseOf(BASE, C) {
>>       enum isBaseOf = {
>>          static if (is(C S == super))
>>             foreach (A; S)
>>                static if (isBaseOf!(BASE, A))
>>                   return true;
>>          return is(C==BASE);
>>       }();
>>    }
>>
>> Sorry, didn't test this properly; going to blame the dlang is-expression docs.
>> It's not like D has multiple inheritance, so using "base classes" (plural)
>> is very misleading...

> yea "is" can be a lil confusing, especially that "is( C S == super)" expression.
> 
> I take it that "is( C S == super)" is only the bases directly listed for inheritance by the class and not the bases bases as well? which is the reason for the change you made their?

Yes, D supports only single inheritance, so there can never be more than one "super" class (plus interfaces). I'm not using classes in D often and when I looked up that is-expr syntax, the wrong doc confused me. It should say "base class" (or "super class"), not "base classes".

artur
1 2 3
Next ›   Last »