www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - User-defined "opIs"

reply "Meta" <jared771 gmail.com> writes:
The following code fails under DMD 2.065:

struct Test
{
     bool opBinary(string op: "is", T: typeof(null))(T val)
     {
         return false;
     }

     bool opBinaryRight(string op: "is", T: typeof(null))(T val)
     {
         return false;
     }
}

void main()
{
     auto t = Test();
     //Error: incompatible types for ((t) is (null)): 'Test' and 
'typeof(null)'
     //assert(t !is null);
     //assert(null !is t);
}

Is this supposed to work, and if not, should an enhancement be 
made to allow it? This is stopping std.typecons.Proxy from being 
a true proxy. See the following:

struct Test
{
	int* ptr;
	mixin Proxy!ptr;
	
	this(int* p)
	{
		ptr = p;
	}
}

auto i = new int;
assert(i !is null);
auto t = Test(i);
//Error: incompatible types for ((t) !is (null)): 'Test' and 
'typeof(null)'
//assert(t !is null);
Sep 24 2014
next sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Wednesday, 24 September 2014 at 22:48:36 UTC, Meta wrote:
 Is this supposed to work, and if not, should an enhancement be 
 made to allow it?
It is not supposed to work - the docs don't list is as overridable (http://dlang.org/expression.html look for "Identity expressions") - and I don't think it should work either. Consider: class A { opIs() } if(a is null) If that were rewritten into if(a.opIs(null))... and a were null, you'd have some trouble. Putting in an automatic null pre-check would start to complicate that is currently a simple operator.
Sep 24 2014
parent "Meta" <jared771 gmail.com> writes:
On Wednesday, 24 September 2014 at 23:08:26 UTC, Adam D. Ruppe 
wrote:
 On Wednesday, 24 September 2014 at 22:48:36 UTC, Meta wrote:
 Is this supposed to work, and if not, should an enhancement be 
 made to allow it?
It is not supposed to work - the docs don't list is as overridable (http://dlang.org/expression.html look for "Identity expressions") - and I don't think it should work either. Consider: class A { opIs() } if(a is null) If that were rewritten into if(a.opIs(null))... and a were null, you'd have some trouble. Putting in an automatic null pre-check would start to complicate that is currently a simple operator.
I'm thinking more for structs, which can't be null. The only other option right now is to use alias this, but that won't work in Proxy's case. For classes, I agree it's tricky... I know we're trying to *remove* things from Object, but what if we added: class Object { static bool opIs(T, U)(inout(T) a, inout(U) b) inout { //Some implementation } } And calls to is on an object get rewritten as: Object.opIs!(typeof(a), typeof(b))(a, b) So `if (a is null)` becomes `if (Object.opIs(a, null))` only when a is a class. When it's a struct, it would just be rewritten as `<struct name>.opIs!T(val)`.
Sep 24 2014
prev sibling next sibling parent ketmar via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Wed, 24 Sep 2014 22:48:35 +0000
Meta via Digitalmars-d <digitalmars-d puremagic.com> wrote:

 Is this supposed to work, and if not, should an enhancement be=20
 made to allow it?
i don't think that ER is needed. `is` is non-overloadable by design. making it overloadable will bring in the requrements for something like `is_which_is_not_overloadable`. which in turn will be asked to make overloadable, which will bring... ah, and so on. ;-) i see some little problems with Proxy here, though. well, Proxy is not ideal. ;-)
Sep 24 2014
prev sibling next sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 9/24/14 6:48 PM, Meta wrote:
 The following code fails under DMD 2.065:

 struct Test
 {
      bool opBinary(string op: "is", T: typeof(null))(T val)
      {
          return false;
      }

      bool opBinaryRight(string op: "is", T: typeof(null))(T val)
      {
          return false;
      }
 }

 void main()
 {
      auto t = Test();
      //Error: incompatible types for ((t) is (null)): 'Test' and
 'typeof(null)'
      //assert(t !is null);
      //assert(null !is t);
 }

 Is this supposed to work, and if not, should an enhancement be made to
 allow it? This is stopping std.typecons.Proxy from being a true proxy.
 See the following:

 struct Test
 {
      int* ptr;
      mixin Proxy!ptr;

      this(int* p)
      {
          ptr = p;
      }
 }

 auto i = new int;
 assert(i !is null);
 auto t = Test(i);
 //Error: incompatible types for ((t) !is (null)): 'Test' and 'typeof(null)'
 //assert(t !is null);
You're looking at it the wrong way. 'is' is not overridable, and shouldn't be. But the type of null should be overridable (or you should be able to tell the compiler "this type can be compared with null"). I believe there has been discussion on this in the past, can't remember where it led. -Steve
Sep 25 2014
prev sibling parent reply Marco Leise <Marco.Leise gmx.de> writes:
I'm against overloading identity checking, too. Instead I
would propose to change its definition to make Proxy structs
work. The rules are currently:

  For class objects, identity is defined as the object
  references are for the same object. Null class objects can
  be compared with is. 

  For struct objects, identity is defined as the bits in the
  struct being identical. 

  For static and dynamic arrays, identity is defined as
  referring to the same array elements and the same number of
  elements. 

  For other operand types, identity is defined as being the
  same as equality. 

If we changed that to:

  A byte for byte comparison of both operands is performed.
  For reference types this is the reference itself.

Breakage is limited to current uses of "is" that clone the
behavior of "==", which a deprecation warning could catch.
And to comparisons of slices with static arrays. (You'd have
to write `dynamicArr is staticArr[]` then.)
This is all comprehensible since you need to be educated about
the difference between value types and reference types anyways.
What it breaks it makes good for by allowing these to compare
equal:

struct CrcProxy { ubyte[4] value; }

CrcProxy crc32a;
ubyte[4] crc32b;

assert (crc32a is crc32b); // Yes, it is byte identical.

If empowering structs to fully emulate built-in types is still
on the agenda it might be the way of least resistance.

-- 
Marco
Sep 27 2014
parent reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Saturday, 27 September 2014 at 11:38:51 UTC, Marco Leise wrote:
 I'm against overloading identity checking, too. Instead I
 would propose to change its definition to make Proxy structs
 work. The rules are currently:

   For class objects, identity is defined as the object
   references are for the same object. Null class objects can
   be compared with is.

   For struct objects, identity is defined as the bits in the
   struct being identical.

   For static and dynamic arrays, identity is defined as
   referring to the same array elements and the same number of
   elements.

   For other operand types, identity is defined as being the
   same as equality.

 If we changed that to:

   A byte for byte comparison of both operands is performed.
   For reference types this is the reference itself.
Maybe allow this only for types that somehow implicitly convert to each other, i.e. via alias this?
 Breakage is limited to current uses of "is" that clone the
 behavior of "==", which a deprecation warning could catch.
 And to comparisons of slices with static arrays. (You'd have
 to write `dynamicArr is staticArr[]` then.)
 This is all comprehensible since you need to be educated about
 the difference between value types and reference types anyways.
 What it breaks it makes good for by allowing these to compare
 equal:

 struct CrcProxy { ubyte[4] value; }

 CrcProxy crc32a;
 ubyte[4] crc32b;

 assert (crc32a is crc32b); // Yes, it is byte identical.

 If empowering structs to fully emulate built-in types is still
 on the agenda it might be the way of least resistance.
Sep 28 2014
parent reply Marco Leise <Marco.Leise gmx.de> writes:
Am Sun, 28 Sep 2014 10:44:47 +0000
schrieb "Marc Sch=C3=BCtz" <schuetzm gmx.net>:

 On Saturday, 27 September 2014 at 11:38:51 UTC, Marco Leise wrote:
   A byte for byte comparison of both operands is performed.
   For reference types this is the reference itself.
=20 Maybe allow this only for types that somehow implicitly convert=20 to each other, i.e. via alias this?
That sounds like a messy rule set on top of the original when alias this does not represent all of the type. You have a size_t and a struct with a pointer that aliases itself to some size_t that can be retrieved through that pointer. The alias this will make it implicitly convert to size_t and the byte for byte comparison will happily compare two equally sized varibles (size_t and pointer). So how narrow would the rule have to be defined before it reads: If you compare with a struct that consists only of one member that the struct aliases itself with, a variable of the type of that member will be compared byte for byte with the struct. --=20 Marco
Sep 28 2014
parent reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Sunday, 28 September 2014 at 14:27:56 UTC, Marco Leise wrote:
 Am Sun, 28 Sep 2014 10:44:47 +0000
 schrieb "Marc Schütz" <schuetzm gmx.net>:

 On Saturday, 27 September 2014 at 11:38:51 UTC, Marco Leise 
 wrote:
   A byte for byte comparison of both operands is performed.
   For reference types this is the reference itself.
Maybe allow this only for types that somehow implicitly convert to each other, i.e. via alias this?
That sounds like a messy rule set on top of the original when alias this does not represent all of the type. You have a size_t and a struct with a pointer that aliases itself to some size_t that can be retrieved through that pointer. The alias this will make it implicitly convert to size_t and the byte for byte comparison will happily compare two equally sized varibles (size_t and pointer). So how narrow would the rule have to be defined before it reads: If you compare with a struct that consists only of one member that the struct aliases itself with, a variable of the type of that member will be compared byte for byte with the struct.
Yeah, it wasn't a good idea. Somehow it felt strange to throw all type safety out of the window, but on the other hand, bit-level comparison is the purpose of `is`, which isn't typesafe to begin with.
Sep 28 2014
parent Marco Leise <Marco.Leise gmx.de> writes:
Am Sun, 28 Sep 2014 14:43:03 +0000
schrieb "Marc Sch=C3=BCtz" <schuetzm gmx.net>:

 Yeah, it wasn't a good idea. Somehow it felt strange to throw all=20
 type safety out of the window, but on the other hand, bit-level=20
 comparison is the purpose of `is`, which isn't typesafe to begin=20
 with.
I have the same feeling. No matter how we do it, one of the comparisons will always be left behind: 1a) identity of references with same referred type - objects with same reference and common inheritance branch - static arrays and the full slice over them - pointers of same type 1b) reference identity after implicit casts per language rules - void* with reference type - void[] with typed array 2) raw bit level equality (includes 1a) and parts of 1b)) - proxy struct with wrapped type - signed with unsigned types on the bit level 3a) logical / overridable equality for user defined types - comparison of uint.max with -1 results in false - safe type widening 1.0f with 1.0, -1 with -1L, char types - opEquals() 3b) 3a) with "C-style" equality for built-in types - equal after integer type promotion - equal after implicit cast One can define these from other perspectives, and the bit-wise struct comparison may be seen as a default opEquals (which I do here) or as a raw bit equality. "=3D=3D" is doing 3b). "is" comprises 1a), 1b) and 3b) as a fallback to be defined for all types. Defining "is" as 2) would remove the overlap for both operators in 3b), but loose type-safety in 1a+b) and disallow comparison of `void[] is ulong[]` since their lengths will differ even if they span the same memory area. With two operators we can capture any two subsets which sound logical on their own, but not all of the possible notions of identity and equality. And since people already wonder about "is" in D and "=3D=3D=3D" in JavaScript it is probably not a good idea to add even more comparison operators. So I conclude that we will have to live with that shortcoming. --=20 Marco
Sep 29 2014