www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Compile-time variables

reply Kayomn <spam kayomn.net> writes:
Hi,

I've got a scene graph which contains multiple inheriting types. 
As such, I've been tagging them with a type enum for whenever I 
need to do things such as loading a structure from binary.

Up until now I've been using an enum that looks like this:

-------------------------------------------------------------------------------
enum NodeType : uint {
	None,
	Root,
	Sprite,
	Camera
}
-------------------------------------------------------------------------------

I'm trying to implement a dynamic type ID assignment system that 
utilizes D generics to generate an incremental, unique identifier 
for each node without needing to continuously update a master 
list. This is what I have so far:

-------------------------------------------------------------------------------
alias NodeTypeID = uint;

enum NodeTypeID getNodeID() {
	static NodeTypeID lastID = 0;

	return lastID++;
}

enum NodeTypeID getNodeID(T)() {
	static NodeTypeID typeID = getNodeID();

	return typeID;
}
-------------------------------------------------------------------------------

The expectation is that this is executed at compile time, 
generating a specific function for the given generic parameter 
each time the generic is used, incrementing the static variable 
by 1 and having the compiled generic functions essentially 
contain magic number unique to its type. So, I would be able to 
employ this like so:

-------------------------------------------------------------------------------
switch (someNodeTypeID) {
	case getNodeID!(Sprite)(): // Sprite node-specific behavior.
		break;

	case getNodeID!(Camera)(): // Camera node-specific behavior.
		break;

	default: // Default behavior.
		break;
}
-------------------------------------------------------------------------------

However, I've been struggling with an error pertaining to 
getNodeType. The return statement of lastID++ is flagging the 
error
"Static variable cannot be read at compile time."

I may just be taking too much of a C++ lens to this, but to me 
this seems like it should work? Am I missing a qualifier or 
something here?
Apr 05 2018
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Apr 05, 2018 at 11:53:00PM +0000, Kayomn via Digitalmars-d-learn wrote:
[...]
 -------------------------------------------------------------------------------
 alias NodeTypeID = uint;
 
 enum NodeTypeID getNodeID() {
 	static NodeTypeID lastID = 0;
 
 	return lastID++;
 }
 
 enum NodeTypeID getNodeID(T)() {
 	static NodeTypeID typeID = getNodeID();
 
 	return typeID;
 }
 -------------------------------------------------------------------------------
 
 The expectation is that this is executed at compile time,
[...]
 However, I've been struggling with an error pertaining to getNodeType.
 The return statement of lastID++ is flagging the error "Static
 variable cannot be read at compile time."
`lastID`, as declared above, are runtime variables. The 'static' in this case just means it's thread-local, rather than allocated on the stack. You cannot modify these variables at compile-time.
 I may just be taking too much of a C++ lens to this, but to me this
 seems like it should work? Am I missing a qualifier or something here?
You appear to be wanting to increment a global variable during compile-time. Unfortunately, there is no such thing as a compile-time global variable. You will have to find some other way to implement what you want. One way to do this would be to use compile-time introspection to construct a list of nodes, and then use a CTFE function or static foreach to generate node IDs all at once. For example: string generateEnum(T...)() { if (__ctfe) { // only run at compile-time string code = "enum NodeIds {"; foreach (ident; T) { code ~= ident ~ ", "; } code ~= "}"; return code; } else assert(0); } alias MyNodes = List!( // Just an example; you probably want to generate this // list via introspection, e.g. via __traits(getMembers) // or something like that. identifier1, identifier2, ... ); mixin(generateEnum!MyNodes); // defines `enum NodeIds` static assert(NodeIds.identifier1 == 0); static assert(NodeIds.identifier2 == 1); ... There are probably other ways to do it too, but I chose enum because it naturally assigns incrementing IDs to its members, so it's a convenient construct for this purpose. T -- Having a smoking section in a restaurant is like having a peeing section in a swimming pool. -- Edward Burr
Apr 05 2018
parent reply Kayomn <spam kayomn.net> writes:
On Friday, 6 April 2018 at 00:21:54 UTC, H. S. Teoh wrote:
 On Thu, Apr 05, 2018 at 11:53:00PM +0000, Kayomn via 
 Digitalmars-d-learn wrote: [...]
 [...]
[...]
 [...]
`lastID`, as declared above, are runtime variables. The 'static' in this case just means it's thread-local, rather than allocated on the stack. You cannot modify these variables at compile-time.
 [...]
You appear to be wanting to increment a global variable during compile-time. Unfortunately, there is no such thing as a compile-time global variable. You will have to find some other way to implement what you want. One way to do this would be to use compile-time introspection to construct a list of nodes, and then use a CTFE function or static foreach to generate node IDs all at once. For example: string generateEnum(T...)() { if (__ctfe) { // only run at compile-time string code = "enum NodeIds {"; foreach (ident; T) { code ~= ident ~ ", "; } code ~= "}"; return code; } else assert(0); } alias MyNodes = List!( // Just an example; you probably want to generate this // list via introspection, e.g. via __traits(getMembers) // or something like that. identifier1, identifier2, ... ); mixin(generateEnum!MyNodes); // defines `enum NodeIds` static assert(NodeIds.identifier1 == 0); static assert(NodeIds.identifier2 == 1); ... There are probably other ways to do it too, but I chose enum because it naturally assigns incrementing IDs to its members, so it's a convenient construct for this purpose. T
I think I didn't explain well enough, I'm not trying to generate an enum from a list of pre-defined known quantities. The idea is that a compile-time generic function exists and it generates a unique identifier for that node. My reasons for doing this is to remove the need to keep a master enum that holds an identifying value for each node. I've implemented this same thing in C++ before and it was extremely straightforward.
Apr 05 2018
next sibling parent reply Kayomn <spam kayomn.net> writes:
I'll give a better example of what it is I'm trying to do.

These are node types. Their contents are not important in this 
explanation, only that they operate as a tree structure.

class Node;

class RootNode : Node;

class SpriteNode : Node;

The result of getNodeID on a specific type is always the same. A 
value representing it that is applied during compilation. The 
value does not matter to the programmer, only that it is unique 
and gets applied.

----------------------------------------------------------------------
uint nodeId1 = getNodeID!(Node)(); // 0.

uint nodeId2 = getNodeID!(SpriteNode)(); // 1.

uint comparison = getNodeID!(Node)(); // 0.

// True.
if (getNodeID!(Node)() == getNodeID!(Node)()) {

}

// False.
if (getNodeID!(SpriteNode)() == getNodeID!(Node)()) {

}
----------------------------------------------------------------------
Apr 05 2018
parent reply ketmar <ketmar ketmar.no-ip.org> writes:
Kayomn wrote:

 I'll give a better example of what it is I'm trying to do.

 These are node types. Their contents are not important in this 
 explanation, only that they operate as a tree structure.

 class Node;

 class RootNode : Node;

 class SpriteNode : Node;

 The result of getNodeID on a specific type is always the same. A value 
 representing it that is applied during compilation. The value does not 
 matter to the programmer, only that it is unique and gets applied.

 ----------------------------------------------------------------------
 uint nodeId1 = getNodeID!(Node)(); // 0.

 uint nodeId2 = getNodeID!(SpriteNode)(); // 1.

 uint comparison = getNodeID!(Node)(); // 0.

 // True.
 if (getNodeID!(Node)() == getNodeID!(Node)()) {

 }

 // False.
 if (getNodeID!(SpriteNode)() == getNodeID!(Node)()) {

 }
 ----------------------------------------------------------------------
it is already done for you, free of charge. class Node {} class RootNode : Node {} class SpriteNode : Node {} void main () { auto nodeId1 = typeid(Node); auto nodeId2 = typeid(SpriteNode); auto comparison = typeid(Node); Node n = new SpriteNode(); assert(typeid(Node) is typeid(Node)); // obviously assert(typeid(SpriteNode) !is typeid(Node)); // sure assert(typeid(Node) is nodeId1); assert(typeid(n) is nodeId2); }
Apr 05 2018
parent reply Kayomn <spam kayomn.net> writes:
On Friday, 6 April 2018 at 01:14:37 UTC, ketmar wrote:
 Kayomn wrote:

 [...]
it is already done for you, free of charge. class Node {} class RootNode : Node {} class SpriteNode : Node {} void main () { auto nodeId1 = typeid(Node); auto nodeId2 = typeid(SpriteNode); auto comparison = typeid(Node); Node n = new SpriteNode(); assert(typeid(Node) is typeid(Node)); // obviously assert(typeid(SpriteNode) !is typeid(Node)); // sure assert(typeid(Node) is nodeId1); assert(typeid(n) is nodeId2); }
Oh neat, thanks. I've been really scraping my head over this, and was worried I wouldn't be able to keep supporting my D version of the project.
Apr 05 2018
parent reply Kayomn <spam kayomn.net> writes:
On Friday, 6 April 2018 at 01:22:42 UTC, Kayomn wrote:
 On Friday, 6 April 2018 at 01:14:37 UTC, ketmar wrote:
 Kayomn wrote:

 [...]
it is already done for you, free of charge. class Node {} class RootNode : Node {} class SpriteNode : Node {} void main () { auto nodeId1 = typeid(Node); auto nodeId2 = typeid(SpriteNode); auto comparison = typeid(Node); Node n = new SpriteNode(); assert(typeid(Node) is typeid(Node)); // obviously assert(typeid(SpriteNode) !is typeid(Node)); // sure assert(typeid(Node) is nodeId1); assert(typeid(n) is nodeId2); }
Oh neat, thanks. I've been really scraping my head over this, and was worried I wouldn't be able to keep supporting my D version of the project.
Hmm... doesn't seem to operate at compile-time, which is an issue if I want to use it in case switch table like I was going to: -------------------------------------------------------------------------------- switch (queryInteger(childJson,"type")) { case NodeType.Sprite: child = this.addChild!(Sprite)(childName); break; default: child = this.addChild!(Node)(childName); break; } --------------------------------------------------------------------------------
Apr 05 2018
parent reply Kayomn <spam kayomn.net> writes:
On Friday, 6 April 2018 at 02:18:28 UTC, Kayomn wrote:
 On Friday, 6 April 2018 at 01:22:42 UTC, Kayomn wrote:
 On Friday, 6 April 2018 at 01:14:37 UTC, ketmar wrote:
 Kayomn wrote:

 [...]
it is already done for you, free of charge. class Node {} class RootNode : Node {} class SpriteNode : Node {} void main () { auto nodeId1 = typeid(Node); auto nodeId2 = typeid(SpriteNode); auto comparison = typeid(Node); Node n = new SpriteNode(); assert(typeid(Node) is typeid(Node)); // obviously assert(typeid(SpriteNode) !is typeid(Node)); // sure assert(typeid(Node) is nodeId1); assert(typeid(n) is nodeId2); }
Oh neat, thanks. I've been really scraping my head over this, and was worried I wouldn't be able to keep supporting my D version of the project.
Hmm... doesn't seem to operate at compile-time, which is an issue if I want to use it in case switch table like I was going to: -------------------------------------------------------------------------------- switch (queryInteger(childJson,"type")) { case NodeType.Sprite: child = this.addChild!(Sprite)(childName); break; default: child = this.addChild!(Node)(childName); break; } --------------------------------------------------------------------------------
Wrong example code, here's the correct example: -------------------------------------------------------------------------------- switch (queryString(childJson,"type")) { case (typeof (Sprite).name): child = this.addChild!(Sprite)(childName); break; case (typeof (Camera).name): child = this.addChild!(Camera)(childName); break; default: child = this.addChild!(Node)(childName); break; } --------------------------------------------------------------------------------
Apr 05 2018
next sibling parent reply Alex <sascha.orlov gmail.com> writes:
On Friday, 6 April 2018 at 02:20:29 UTC, Kayomn wrote:
 Wrong example code, here's the correct example:
 --------------------------------------------------------------------------------
 switch (queryString(childJson,"type")) {
 	case (typeof (Sprite).name):
 		child = this.addChild!(Sprite)(childName);

 		break;

 	case (typeof (Camera).name):
 		child = this.addChild!(Camera)(childName);

 		break;

  	default:
  		child = this.addChild!(Node)(childName);

 		break;
 }
 --------------------------------------------------------------------------------
Ok, first of all, I'm not sure about the question, because of the following: Does the default case handle an unspecified class or does it handle a class which is specified, but is not mentioned in any of previous cases? Or does the default case handle both of these circumstances? Without a master list this play an important role, I think. Another information shortage is: are the tags exclusive or not? So, is it possible that a class has more then one tag (still being unique (tuple))? Besides this, I tried something with types used as user defined attributes. https://dlang.org/spec/attribute.html#uda Automatic compile time tagging is not my speciality, however, I think is also achievable with mixins somehow? But I don't know how to workaround the bug https://issues.dlang.org/show_bug.cgi?id=18718 at this moment... https://run.dlang.io/is/DmBhO5
Apr 06 2018
parent reply Kayomn <spam kayomn.net> writes:
 Besides this, I tried something with types used as user defined 
 attributes.
 https://dlang.org/spec/attribute.html#uda

 Automatic compile time tagging is not my speciality, however, I 
 think is also achievable with mixins somehow?
 But I don't know how to workaround the bug
 https://issues.dlang.org/show_bug.cgi?id=18718
 at this moment...

 https://run.dlang.io/is/DmBhO5
 Does the default case handle an unspecified class or does it 
 handle a class which is specified, but is not mentioned in any 
 of previous cases?
So in this example code the switch table is being used for loading data serialized into text. If the class cannot determine the node ID or it uses the default node type ID (e.g. the Node type if super "Node") it will create a simple node, as you can always be sure no matter what the type there will be sufficient information stored in the data to construct a default Node.
 Another information shortage is: are the tags exclusive or not? 
 So, is it possible that a class has more then one tag (still 
 being unique (tuple))?
ID tags are unique and spsecific to the class type. There shouldn't be more than one type ID assigned to one class type. The idea behind what it is I am doing is I am implementing a solution to getting a type index, similar to std.variant.Variant.type(). The way that I implemented this in C++ is like so: -------------------------------------------------------------------------------- inline unsigned int getNodeTypeID() { static unsigned int lastID = 0; return lastID++; } template<typename T> inline unsigned int getNodeTypeID() { static unsigned int typeID = getNodeTypeID(); return typeID; } -------------------------------------------------------------------------------- In this C++ example I am exploiting the fact that templates are generated at compile-time to execute getNodeTypeID for each new type instance generated. After initial type generation, whenever they are called at runtime they were return the ID assigned to that function template instance that was generated at compile-time. It's pretty simple, and to be honest I'm surprised this has been causing me such a headache implementing it in D.
Apr 06 2018
parent reply nkm1 <t4nk074 openmailbox.org> writes:
On Friday, 6 April 2018 at 13:10:23 UTC, Kayomn wrote:
 ID tags are unique and spsecific to the class type. There 
 shouldn't be more than one type ID assigned to one class type.

 The idea behind what it is I am doing is I am implementing a 
 solution to getting a type index, similar to 
 std.variant.Variant.type(). The way that I implemented this in 
 C++ is like so:

 --------------------------------------------------------------------------------
 inline unsigned int getNodeTypeID() {
     static unsigned int lastID = 0;

     return lastID++;
 }

 template<typename T> inline unsigned int getNodeTypeID() {
     static unsigned int typeID = getNodeTypeID();

     return typeID;
 }
 --------------------------------------------------------------------------------

 In this C++ example I am exploiting the fact that templates are 
 generated at compile-time to execute getNodeTypeID for each new 
 type instance generated. After initial type generation, 
 whenever they are called at runtime they were return the ID 
 assigned to that function template instance that was generated 
 at compile-time.

 It's pretty simple, and to be honest I'm surprised this has 
 been causing me such a headache implementing it in D.
That's because the C++ code doesn't do what you think it does. Apparently you think that getNodeID() is executed at compile time. This is not the case, which you can verify by adding "constexpr" to it: $ g++ -std=c++14 -Wall -Wextra -c -o example example.cpp main.cpp: In function ‘constexpr unsigned int getNodeTypeID()’: main.cpp:2:25: error: ‘lastID’ declared ‘static’ in ‘constexpr’ function static unsigned int lastID = 0; In fact, you're "exploiting" the fact that static variables in C++ can be initialized at runtime (which is probably not what you want). The equivalent D code is: uint newID() { static uint lastID; return lastID++; } uint getNodeID(T)() { static bool inited; static uint id; if (!inited) { id = newID(); inited = true; } return id; } (yes, C++ does insert some hidden bool that tells it whether the variable was initialized or not). Naturally, you can't use that for constructing switches or other compile time constructs.
Apr 06 2018
parent reply Kayomn <spam kayomn.net> writes:
On Friday, 6 April 2018 at 13:55:55 UTC, nkm1 wrote:
 On Friday, 6 April 2018 at 13:10:23 UTC, Kayomn wrote:
 ID tags are unique and spsecific to the class type. There 
 shouldn't be more than one type ID assigned to one class type.

 The idea behind what it is I am doing is I am implementing a 
 solution to getting a type index, similar to 
 std.variant.Variant.type(). The way that I implemented this in 
 C++ is like so:

 --------------------------------------------------------------------------------
 inline unsigned int getNodeTypeID() {
     static unsigned int lastID = 0;

     return lastID++;
 }

 template<typename T> inline unsigned int getNodeTypeID() {
     static unsigned int typeID = getNodeTypeID();

     return typeID;
 }
 --------------------------------------------------------------------------------

 In this C++ example I am exploiting the fact that templates 
 are generated at compile-time to execute getNodeTypeID for 
 each new type instance generated. After initial type 
 generation, whenever they are called at runtime they were 
 return the ID assigned to that function template instance that 
 was generated at compile-time.

 It's pretty simple, and to be honest I'm surprised this has 
 been causing me such a headache implementing it in D.
That's because the C++ code doesn't do what you think it does. Apparently you think that getNodeID() is executed at compile time. This is not the case, which you can verify by adding "constexpr" to it: $ g++ -std=c++14 -Wall -Wextra -c -o example example.cpp main.cpp: In function ‘constexpr unsigned int getNodeTypeID()’: main.cpp:2:25: error: ‘lastID’ declared ‘static’ in ‘constexpr’ function static unsigned int lastID = 0; In fact, you're "exploiting" the fact that static variables in C++ can be initialized at runtime (which is probably not what you want). The equivalent D code is: uint newID() { static uint lastID; return lastID++; } uint getNodeID(T)() { static bool inited; static uint id; if (!inited) { id = newID(); inited = true; } return id; } (yes, C++ does insert some hidden bool that tells it whether the variable was initialized or not). Naturally, you can't use that for constructing switches or other compile time constructs.
Figured I had a handle on how it worked doing but guess not. One final question, however. I've implemented this test bed with your example to what I think is your exact implementation, but it seems to be giving unexpected output. -------------------------------------------------------------------------------- import std.stdio; uint newID() { static uint lastID; return lastID; } uint getNodeID(T)() { static bool inited; static uint id; if (!inited) { id = newID(); inited = true; } return id; } class Node {} class Sprite {} class Camera {} void main() { // Test 01. writeln("Test 01."); writeln(getNodeID!(Node)(),'\t',getNodeID!(Sprite)(),'\t',getNodeID!(Camera)()); // Test 02. writeln("Test 02."); writeln(getNodeID!(Node)(),'\t',getNodeID!(Sprite)(),'\t',getNodeID!(Camera)()); } -------------------------------------------------------------------------------- Output: -------------------------------------------------------------------------------- Performing "debug" build using gdc for x86_64. dlangtest ~master: building configuration "application"... Running ./dlangtest Test 01. 0 0 0 Test 02. 0 0 0 -------------------------------------------------------------------------------- Have I misunderstood something?
Apr 06 2018
parent Kayomn <spam kayomn.net> writes:
On Friday, 6 April 2018 at 14:15:08 UTC, Kayomn wrote:
 On Friday, 6 April 2018 at 13:55:55 UTC, nkm1 wrote:
 [...]
Figured I had a handle on how it worked doing but guess not. One final question, however. [...]
Nevermind, I'm blind. I missed the post-increment in newID().
Apr 06 2018
prev sibling parent aliak <something something.com> writes:
On Friday, 6 April 2018 at 02:20:29 UTC, Kayomn wrote:
 Wrong example code, here's the correct example:
 --------------------------------------------------------------------------------
 switch (queryString(childJson,"type")) {
 	case (typeof (Sprite).name):
 		child = this.addChild!(Sprite)(childName);

 		break;

 	case (typeof (Camera).name):
 		child = this.addChild!(Camera)(childName);

 		break;

  	default:
  		child = this.addChild!(Node)(childName);

 		break;
 }
 --------------------------------------------------------------------------------
Hi, could you show a bit more implementation details in the C++ version that works? Maybe someone can translate that to the appropriate D version? The problem seems to be that switching on a runtime value and pattern matching with a compile time value is hard to pull off automatically without some sort of bridge (or of course I've misunderstood this exercise) So if you had: class R { string type() { return R.stringof; } } class A: R { override string type() { return A.stringof; } } class B: R { override string type() { return B.stringof; } } string type(T: R)() { return T.stringof; } void main() { R node = new A; final switch (node.type) { case type!R: writeln("R"); break; case type!A: writeln("A"); break; case type!B: writeln("B"); break; } } (maybe not a good idea to use stringof though, typeid probably better) Cheers
Apr 06 2018
prev sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, April 06, 2018 00:35:39 Kayomn via Digitalmars-d-learn wrote:
 On Friday, 6 April 2018 at 00:21:54 UTC, H. S. Teoh wrote:
 On Thu, Apr 05, 2018 at 11:53:00PM +0000, Kayomn via
 Digitalmars-d-learn wrote: [...]

 [...]
[...]
 [...]
`lastID`, as declared above, are runtime variables. The 'static' in this case just means it's thread-local, rather than allocated on the stack. You cannot modify these variables at compile-time.
 [...]
You appear to be wanting to increment a global variable during compile-time. Unfortunately, there is no such thing as a compile-time global variable. You will have to find some other way to implement what you want. One way to do this would be to use compile-time introspection to construct a list of nodes, and then use a CTFE function or static foreach to generate node IDs all at once. For example: string generateEnum(T...)() { if (__ctfe) { // only run at compile-time string code = "enum NodeIds {"; foreach (ident; T) { code ~= ident ~ ", "; } code ~= "}"; return code; } else assert(0); } alias MyNodes = List!( // Just an example; you probably want to generate this // list via introspection, e.g. via __traits(getMembers) // or something like that. identifier1, identifier2, ... ); mixin(generateEnum!MyNodes); // defines `enum NodeIds` static assert(NodeIds.identifier1 == 0); static assert(NodeIds.identifier2 == 1); ... There are probably other ways to do it too, but I chose enum because it naturally assigns incrementing IDs to its members, so it's a convenient construct for this purpose. T
I think I didn't explain well enough, I'm not trying to generate an enum from a list of pre-defined known quantities. The idea is that a compile-time generic function exists and it generates a unique identifier for that node. My reasons for doing this is to remove the need to keep a master enum that holds an identifying value for each node. I've implemented this same thing in C++ before and it was extremely straightforward.
If you want to generate a value using CTFE, then you can use a normal function that simply takes arguments and returns a value, and you can use enums that already have a value. So, you can do something like enum foo = "string"; enum bar = baz("2"); string baz(string str) { return foo ~ str; } void main() { assert(foo == "string"); assert(bar == "string2"); } but you can't use any variables other than local variables unless they're const or immutable. So, you can't do something like increment a global variable. It's is absolutely impossible to do something like enum id = getID(); and have getID return a different value on each call. So, if you're looking to do something like enum value1 = getID(); enum value2 = getID(); enum value3 = getID(); enum value4 = getID(); and have each of those enums have a unique ID, then you are completely out of luck. You'll need go about solving the problem in a different way, be it by passing different values to the function being used so that it can generate different values on each call: enum value1 = getID(1); enum value2 = getID(2); enum value3 = getID(3); enum value4 = getID(4); or by generating a string to mix in as code as H. S. Teoh suggested. Eponymous templates or template mixins could also be used, but they don't really do anything more, just differently, and usually less efficiently. Basically, even though functions used during CTFE don't have to be marked with pure, they effectively have to be pure in order to work. So, if you try to initialize an enum with the result of a function call, the only way you have to alter the output is to pass in different arguments. If you need any kind of state to be maintained in order to initialize multiple enums with values that relate to one another (e.g. an incrementing ID), then a mixin is your only choice. If you can initialize what you need to initialize at runtime (meaning that it can't be an enum), then you have more options, but at compile time, initializations have to be pure. - Jonathan M Davis
Apr 05 2018