12

If I write

enum chars = digits ~ uppercase;

will the string be concatenated at compile time? I'm assuming it will. If I replace it with a string literal or a CTFE function I can't measure any significant performance differences (even calling it a hundred million times). I do get a difference if I replace enum with const. I've been told it's inefficient to write it like this. I thought it was kind of convenient and I don't see the inefficiency. (BTW, the line is in a function that's called recursively).

The full code (converting to a numeral system with a different base)

import std.string;

string toBase(long n, int b) 
in {
    assert(2 <= b && b <= 35);
} body {
    static string sign;
    if (n < 0) {
        n *= -1;
        sign = "-";
    }
    enum chars = digits ~ uppercase;
    size_t r = cast(size_t)(n % b);
    if (n == r) {
        return sign ~ chars[r];
    }
    return toBase((n - r) / b, b) ~ chars[r];
}

Edit: updated code, in response to comments, not relevant to the question

string toBase(long n, int b) 
in {
    assert(2 <= b && b <= 35);
} body {
    enum chars = digits ~ uppercase;
    long r = n % b;
    char c = chars[cast(size_t) abs(r)];
    if (n == r) {
        return (n < 0 ? "-" : "") ~ c;
    }
    return toBase((n - r) / b, b) ~ c;
}
menjaraz
  • 7,551
  • 4
  • 41
  • 81
fwend
  • 1,813
  • 2
  • 15
  • 17
  • 2
    you forgot to reset the sign string (you can use `scope(exit)sign="";` for that) so `toBase(-10,10)` followed by `toBase(10,10)` will give the same result – ratchet freak Jul 03 '11 at 14:38
  • Right. Ha ha ha, first time I use a static variable. Tricky. That scope(exit) thing is really useful isn't it? I'm a bit surprised a recursive call doesn't qualify as a scope exit. – fwend Jul 03 '11 at 15:02
  • 1
    a scope(exit) is executed when the current stack frame is popped of the stack (like try...finally really but without the extra indentation) recursion doesn't do that – ratchet freak Jul 03 '11 at 15:09
  • 1
    I'd dump that static, it's not thread safe and (as written) not exception safe (not that this code would encounter that). One way to avoid a static would be to move the recursive bit into a nested function. – BCS Jul 03 '11 at 19:51
  • It's the size_t that's the problem. It forces me to keep track of the sign. I need to leave n and r alone and cast to size_t only when I'm accessing the char array. – fwend Jul 03 '11 at 20:43
  • 2
    @BCS that static var has the same semantics as a private global which in D is in thread local storage (and thus thread safe) – ratchet freak Jul 03 '11 at 20:50
  • @ratchet: I forgot that bit. OTOH dumping it removes the sign check conditional from the recursive part. – BCS Jul 08 '11 at 16:29
  • 1
    @BCS I tried your suggestion and it worked well. It's a better way of keeping state. – fwend Jul 08 '11 at 17:23

2 Answers2

9

enum instantiations like that are always evaluated at compile-time (and throw compile errors when the evaluation is impossible at compile time)

so the concatenation is done at compile-time and an immutable version is stored in the code and referenced at runtime

ratchet freak
  • 47,288
  • 5
  • 68
  • 106
5

One way to check for yourself whether the string is concatenated at compile time is to compile the code and examine the object file. Assuming your file is called test.d:

dmd -c test.d
objdump test.o | grep -C3 "012345"

...should produce something like:

Contents of section .rodata:
 0000 2d000000 00000000 00000000 00000000  -...............
 0010 01000000 00000000 00000000 00000000  ................
 0020 30313233 34353637 38394142 43444546  0123456789ABCDEF
 0030 4748494a 4b4c4d4e 4f505152 53545556  GHIJKLMNOPQRSTUV
 0040 5758595a 00000000 00000000 00000000  WXYZ............
 0050 24000000 00000000 20000000 00000000  $....... .......

(This is on Linux; on other platforms, you'll need different tools to inspect the object file.)

If you change your enum to const or string, you will (probably) get no output: there will be no concatenated string for grep to find.

But the compiler may concatenate strings at compile-time even when enum is not used. Consider this program:

 import std.stdio;

 enum a = "Aaaa";
 enum b = "Bbbb";
 enum c = "Cccc";

 void main() 
 {
   enum   x = a ~ b;
   const  y = b ~ a;
   string z = a ~ c;
   writeln(x, y, z);
 }

Now, compile it, and examine the object file:

% dmd -c test2.d && objdump -s test2.o | egrep "(Aaaa|Bbbb)"
 0000 42626262 41616161 00000000 00000000  BbbbAaaa........
 0020 41616161 43636363 00000000 00000000  AaaaCccc........
 0040 41616161 42626262 00000000 00000000  AaaaBbbb........

We see that x, y and z are all static literals. (Mark a, b and c as const instead of enum, and you may see different behaviour.) So, while enum is a guarantee of compile-time evaluation, the absence of enum doesn't prevent compile-time evaluation.

gmfawcett
  • 391
  • 1
  • 8