Compile-time variables

Jonathan M Davis newsgroup.d at jmdavisprog.com
Fri Apr 6 01:14:32 UTC 2018


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



More information about the Digitalmars-d-learn mailing list