www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Enum that can be 0 or null

reply Alex Parrill <initrd.gz gmail.com> writes:
(How) can I make a constant that is either zero or null depending 
on how it is used?

Vulkan has a VK_NULL_HANDLE constant which in C is defined to be 
zero. It is used as a null object for several types of objects. 
In D however, zero is not implicitly convertible to a null 
pointer, and vice versa.

On 64 bit platforms, those types are all pointers, so there I can 
define VK_NULL_HANDLE as null. But on 32 bit platforms, some of 
the types are pointers and others are ulongs, and my definition 
of VK_NULL_HANDLE should be compatible with both.

Anyone have an idea on how to make this work?
May 20 2016
parent reply tsbockman <thomas.bockman gmail.com> writes:
On Friday, 20 May 2016 at 13:39:50 UTC, Alex Parrill wrote:
 (How) can I make a constant that is either zero or null 
 depending on how it is used?
In the general case, I don't think this is possible in D currently. (It will become possible if the proposed "multiple alias this" ever gets implemented, though: http://wiki.dlang.org/DIP66 ) However, various workarounds are possible depending on the context.
 Anyone have an idea on how to make this work?
Why do you need to? Just use null for pointer types, and 0 for integers. D is not C; you aren't *supposed* to be able to just copy-paste any random C snippet into D and expect it to work without modification. If that's not a satisfactory answer, please show some specific examples of code that you don't know how to make work without VK_NULL_HANDLE so that I can propose a workaround.
May 20 2016
next sibling parent reply Alex Parrill <initrd.gz gmail.com> writes:
On Friday, 20 May 2016 at 22:10:51 UTC, tsbockman wrote:
 Why do you need to?

 Just use null for pointer types, and 0 for integers. D is not 
 C; you aren't *supposed* to be able to just copy-paste any 
 random C snippet into D and expect it to work without 
 modification.

 If that's not a satisfactory answer, please show some specific 
 examples of code that you don't know how to make work without 
 VK_NULL_HANDLE so that I can propose a workaround.
Because, as I mentioned in the OP, for VK_NULL_HANDLE to work like it does in C on 32-bit systems, it needs to be compatible with both pointer types (VkDevice, VkInstance, etc) and 64-bit integer types (VkFence, VkSemaphore, many others). There is of course a workaround: since VK_NULL_HANDLE is always zero/null, Vk(Type).init will work as a replacement as long as you match up the types. But then people will try to use VK_NULL_HANDLE (as every C or C++ Vulkan tutorial or reference will instruct) and find out that either a) it doesn't work or b) it works fine on their 64 bit development system, but then cause a ton of errors when they try to compile their project for a 32-bit system. Looks like my best bet is to mark it as deprecated and point them to Vk(Type).init instead.
May 20 2016
next sibling parent reply Mike Parker <aldacron gmail.com> writes:
On Saturday, 21 May 2016 at 01:09:42 UTC, Alex Parrill wrote:

 Looks like my best bet is to mark it as deprecated and point 
 them to Vk(Type).init instead.
I would prefer to do something like this: enum VK_NULL_HANDLE_0 = 0; enum VK_NULL_HANDLE_PTR = null; Then document it clearly. Alias VK_NULL_HANDLE to one of them and keep it deprecated forever. Many users are not going to read the documentation on their own initiative, so the deprecation message telling them how to solve the problem will save you from responding to the same issue again and again and again.
May 20 2016
parent Alex Parrill <initrd.gz gmail.com> writes:
On Saturday, 21 May 2016 at 02:04:23 UTC, Mike Parker wrote:
 On Saturday, 21 May 2016 at 01:09:42 UTC, Alex Parrill wrote:

 Looks like my best bet is to mark it as deprecated and point 
 them to Vk(Type).init instead.
I would prefer to do something like this: enum VK_NULL_HANDLE_0 = 0; enum VK_NULL_HANDLE_PTR = null; Then document it clearly. Alias VK_NULL_HANDLE to one of them and keep it deprecated forever. Many users are not going to read the documentation on their own initiative, so the deprecation message telling them how to solve the problem will save you from responding to the same issue again and again and again.
Hm, I could do `VK_NULL_DISPATCHABLE_HANDLE = null` for the types that are always pointers and `VK_NULL_NONDISPATCHABLE_HANDLE = null ? IS_64BIT : 0` for types that are either pointers are integers depending on arch, but those names are a bit long. Your specific example wouldn't work.
May 20 2016
prev sibling parent reply tsbockman <thomas.bockman gmail.com> writes:
On Saturday, 21 May 2016 at 01:09:42 UTC, Alex Parrill wrote:
 On Friday, 20 May 2016 at 22:10:51 UTC, tsbockman wrote:
 If that's not a satisfactory answer, please show some specific 
 examples of code that you don't know how to make work without 
 VK_NULL_HANDLE so that I can propose a workaround.
Because, as I mentioned in the OP, for VK_NULL_HANDLE to work like it does in C on 32-bit systems, it needs to be compatible with both pointer types (VkDevice, VkInstance, etc) and 64-bit integer types (VkFence, VkSemaphore, many others).
That is not code. As I said, there are many possible workarounds, but it is hard to say which is best for you without actually seeing your code. As an example, if VK_NULL_HANDLE only ever needs to be assigned to opaque types on the D side (that is, types that serve only as an ID or address for communicating with the C side), you could do this: private struct VkNullHandle { } enum VK_NULL_HANDLE = VkNullHandle.init; mixin template VkHandle(bool dispatchable) { static if (dispatchable || (size_t.sizeof == 8)) void* bits = null; else ulong bits = 0; this(typeof(this) that) { this.bits = that.bits; } this(VkNullHandle that) { this.bits = typeof(this.bits).init; } ref typeof(this) opAssign(typeof(this) that) { this.bits = that.bits; return this; } ref typeof(this) opAssign(VkNullHandle that) { this.bits = typeof(this.bits).init; return this; } } struct VkDevice { mixin VkHandle!true; } struct VkInstance { mixin VkHandle!true; } struct VkFence { mixin VkHandle!false; } struct VkSemaphore { mixin VkHandle!false; } void main() { VkInstance a = VK_NULL_HANDLE; VkFence b = VK_NULL_HANDLE; } (DPaste: https://dpaste.dzfl.pl/8f4ce39a907f ) The above is typesafe, and can easily be made compatible with a typical C API, since C does no parameter-related name mangling.
May 20 2016
parent reply ParticlePeter <ParticlePeter gmx.de> writes:
On Saturday, 21 May 2016 at 06:36:53 UTC, tsbockman wrote:
 ...
 As an example, if VK_NULL_HANDLE only ever needs to be assigned 
 to opaque types on the D side (that is, types that serve only 
 as an ID or address for communicating with the C side), you 
 could do this:

 private struct VkNullHandle { }
 enum VK_NULL_HANDLE = VkNullHandle.init;

 mixin template VkHandle(bool dispatchable) {
 	static if (dispatchable || (size_t.sizeof == 8))
 		void* bits = null;
 	else
 		ulong bits = 0;
 	
 	this(typeof(this) that) {
 		this.bits = that.bits; }
 	this(VkNullHandle that) {
 		this.bits = typeof(this.bits).init; }

 	ref typeof(this) opAssign(typeof(this) that) {
 		this.bits = that.bits;
 		return this;
 	}
 	ref typeof(this) opAssign(VkNullHandle that) {
 		this.bits = typeof(this.bits).init;
 		return this;
 	}
 }

 struct VkDevice { mixin VkHandle!true; }
 struct VkInstance { mixin VkHandle!true; }

 struct VkFence { mixin VkHandle!false; }
 struct VkSemaphore { mixin VkHandle!false; }


 void main() {
 	VkInstance a = VK_NULL_HANDLE;
 	VkFence b = VK_NULL_HANDLE;
 }

 (DPaste: https://dpaste.dzfl.pl/8f4ce39a907f )

 The above is typesafe, and can easily be made compatible with a 
 typical C API, since C does no parameter-related name mangling.
In this case I don't see how it would be possible to use your VkDevice, etc. as argument to the C functions, as they are of different type, no?
Jun 06 2016
parent reply Alex Parrill <initrd.gz gmail.com> writes:
On Monday, 6 June 2016 at 18:43:33 UTC, ParticlePeter wrote:
 On Saturday, 21 May 2016 at 06:36:53 UTC, tsbockman wrote:
 ...
 As an example, if VK_NULL_HANDLE only ever needs to be 
 assigned to opaque types on the D side (that is, types that 
 serve only as an ID or address for communicating with the C 
 side), you could do this:

 private struct VkNullHandle { }
 enum VK_NULL_HANDLE = VkNullHandle.init;

 mixin template VkHandle(bool dispatchable) {
 	static if (dispatchable || (size_t.sizeof == 8))
 		void* bits = null;
 	else
 		ulong bits = 0;
 	
 	this(typeof(this) that) {
 		this.bits = that.bits; }
 	this(VkNullHandle that) {
 		this.bits = typeof(this.bits).init; }

 	ref typeof(this) opAssign(typeof(this) that) {
 		this.bits = that.bits;
 		return this;
 	}
 	ref typeof(this) opAssign(VkNullHandle that) {
 		this.bits = typeof(this.bits).init;
 		return this;
 	}
 }

 struct VkDevice { mixin VkHandle!true; }
 struct VkInstance { mixin VkHandle!true; }

 struct VkFence { mixin VkHandle!false; }
 struct VkSemaphore { mixin VkHandle!false; }


 void main() {
 	VkInstance a = VK_NULL_HANDLE;
 	VkFence b = VK_NULL_HANDLE;
 }

 (DPaste: https://dpaste.dzfl.pl/8f4ce39a907f )

 The above is typesafe, and can easily be made compatible with 
 a typical C API, since C does no parameter-related name 
 mangling.
In this case I don't see how it would be possible to use your VkDevice, etc. as argument to the C functions, as they are of different type, no?
They'd be the same type, since you would define the vulkan functions to take these structures instead of pointer or integer types. It relies on a lot of assumptions about the ABI that make a raw pointer work the same as a structure containing just one pointer, which is why I did not give it much consideration.
Jun 06 2016
next sibling parent reply ParticlePeter <ParticlePeter gmx.de> writes:
On Monday, 6 June 2016 at 20:32:23 UTC, Alex Parrill wrote:
 They'd be the same type, since you would define the vulkan 
 functions to take these structures instead of pointer or 
 integer types.

 It relies on a lot of assumptions about the ABI that make a raw 
 pointer work the same as a structure containing just one 
 pointer, which is why I did not give it much consideration.
Is there a way to use opCast (just an idea, not working code) ? private struct VK_HANDLE_HELPER { const void * handle = null; alias handle this; T opCast(T)() if( is( T == uint64_t )) { return 0uL; } } const VK_NULL_HANDLE = VK_HANDLE_HELPER();
Jun 06 2016
parent reply Alex Parrill <initrd.gz gmail.com> writes:
On Tuesday, 7 June 2016 at 04:31:56 UTC, ParticlePeter wrote:
 On Monday, 6 June 2016 at 20:32:23 UTC, Alex Parrill wrote:
 They'd be the same type, since you would define the vulkan 
 functions to take these structures instead of pointer or 
 integer types.

 It relies on a lot of assumptions about the ABI that make a 
 raw pointer work the same as a structure containing just one 
 pointer, which is why I did not give it much consideration.
Is there a way to use opCast (just an idea, not working code) ? private struct VK_HANDLE_HELPER { const void * handle = null; alias handle this; T opCast(T)() if( is( T == uint64_t )) { return 0uL; } } const VK_NULL_HANDLE = VK_HANDLE_HELPER();
I don't think opCast gets called for implicit conversions; it only gets called for explicit casts. I'll test it later.
Jun 07 2016
parent reply ParticlePeter <ParticlePeter gmx.de> writes:
On Tuesday, 7 June 2016 at 14:31:40 UTC, Alex Parrill wrote:
 I don't think opCast gets called for implicit conversions; it 
 only gets called for explicit casts. I'll test it later.
It does for type bool, but I fear that's the only exception.
Jun 07 2016
parent ag0aep6g <anonymous example.com> writes:
On 06/07/2016 08:50 PM, ParticlePeter wrote:
 On Tuesday, 7 June 2016 at 14:31:40 UTC, Alex Parrill wrote:
 I don't think opCast gets called for implicit conversions; it only
 gets called for explicit casts. I'll test it later.
It does for type bool, but I fear that's the only exception.
No, opCast can't be used for implicit conversions to bool. I guess you're thinking of the `if(foo) {...}` case. That's considered an explicit conversion. `bool b = foo;` still won't work.
Jun 07 2016
prev sibling parent tsbockman <thomas.bockman gmail.com> writes:
On Monday, 6 June 2016 at 20:32:23 UTC, Alex Parrill wrote:
 It relies on a lot of assumptions about the ABI that make a raw 
 pointer work the same as a structure containing just one 
 pointer, which is why I did not give it much consideration.
At least some of those assumptions could be verified at compile time: // insert at the end of the VkHandle mixin template: static assert(typeof(this).sizeof == typeof(bits).sizeof); static assert(typeof(this).alignof == typeof(bits).alignof); If you want to be extra careful, you can also use a string mixin to declare each extern(C) function with its official C parameter types, plus a D pragma(inline, true) wrapper that accepts VkDevice or whatever, instead. Of course, it's probably a lot easier to just tel your users to replace VK_NULL_HANDLE with VK_NULL_DISPATCHABLE or VK_NULL_NONDISPATCHABLE like you suggested earlier.
Jun 07 2016
prev sibling parent reply Mike Parker <aldacron gmail.com> writes:
On Friday, 20 May 2016 at 22:10:51 UTC, tsbockman wrote:

 Just use null for pointer types, and 0 for integers. D is not 
 C; you aren't *supposed* to be able to just copy-paste any 
 random C snippet into D and expect it to work without 
 modification.

 If that's not a satisfactory answer, please show some specific 
 examples of code that you don't know how to make work without 
 VK_NULL_HANDLE so that I can propose a workaround.
When binding to a C library, it's desirable to have as close to the original C API as possible so that you *can* drop C snippets and examples into D code and have them just work. Obviously, 100% compatibility is not possible, but it is possible to get pretty close most of the time.
May 20 2016
parent tsbockman <thomas.bockman gmail.com> writes:
On Saturday, 21 May 2016 at 01:53:21 UTC, Mike Parker wrote:
 When binding to a C library, it's desirable to have as close to 
 the original C API as possible so that you *can* drop C 
 snippets and examples into D code and have them just work. 
 Obviously, 100% compatibility is not possible, but it is 
 possible to get pretty close most of the time.
The reason that VK_NULL_HANDLE cannot (currently) be directly implemented in D it is not type safe. D's type safety is usually considered to be a positive selling point for the language; that's what I mean when I say that "you aren't *supposed* to be able to just copy-paste any random C snippet into D".
May 20 2016