www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - compile-time variables?

reply Fraser, Robert Fraser <fraserofthenight gmail.com> writes:
Hi,

D has some pretty impressive metaprogramming features, but I was wondering if
there was any way to do compile-time variables, or fake it with recursive
templates or something. In particular, I want a mixin template that mixes in a 
unique numeric ID as part of a function every time it's used in a mixin.
Thouands of these functions will be used in the program, and each must have a
unique ID in an associative array (after they're called for the first time,
lazy eval is being done at runtime).

If I can avoid code bloat, all the Better!

Thanks,
Fraser
May 22 2007
next sibling parent reply BCS <ao pathlink.com> writes:
Reply to Robert,

 Hi,
 
 D has some pretty impressive metaprogramming features, but I was
 wondering if there was any way to do compile-time variables, or fake
 it with recursive templates or something. In particular, I want a
 mixin template that mixes in a  unique numeric ID as part of a
 function every time it's used in a mixin. Thouands of these functions
 will be used in the program, and each must have a unique ID in an
 associative array (after they're called for the first time, lazy eval
 is being done at runtime).
 
 If I can avoid code bloat, all the Better!
 
 Thanks,
 Fraser

it's does stuff at runtime but this should work int val = 0 class UID(char[] file, int line, A...) { static const uid; static this(){uid = val++} } UID!(__FILE__, __LINE__).uid if it is used in a template, add enough of the template args to make each instance have different args.
May 22 2007
parent Pragma <ericanderton yahoo.removeme.com> writes:
BCS wrote:
 Reply to Robert,
 
 Hi,

 D has some pretty impressive metaprogramming features, but I was
 wondering if there was any way to do compile-time variables, or fake
 it with recursive templates or something. In particular, I want a
 mixin template that mixes in a  unique numeric ID as part of a
 function every time it's used in a mixin. Thouands of these functions
 will be used in the program, and each must have a unique ID in an
 associative array (after they're called for the first time, lazy eval
 is being done at runtime).

 If I can avoid code bloat, all the Better!

 Thanks,
 Fraser

it's does stuff at runtime but this should work int val = 0 class UID(char[] file, int line, A...) { static const uid; static this(){uid = val++} } UID!(__FILE__, __LINE__).uid if it is used in a template, add enough of the template args to make each instance have different args.

BCS, I like that hack. Technically, isn't this a runtime solution since the evaluation of 'val++' is done at runtime? Then again, if all Robert wants is a registry of template instances or somesuch (regardless of performance), this might do the trick. -- - EricAnderton at yahoo
May 22 2007
prev sibling parent reply Pragma <ericanderton yahoo.removeme.com> writes:
Fraser wrote:
 In particular, I want a mixin template that mixes in a  unique numeric ID as
part of a function every time it's used in a mixin. 

This is possible, but it will take some mildly ugly code to accomplish*. The major hurdle to overcome is that templates are effectively stateless. This is because every template invocation is dependent upon its arguments and global constants for evaluation, and is unable to change anything but its own definition. The result is that there's no compile-time analog for simple stuff like this: uint value; void Counter(){ value++; } ... since 'Counter' modifies 'value', which would be a stateful evaluation. The solution is to have the template evaluate to a counter value that is used as an argument for subsequent calls to the template. // un-tested template Counter(){ const uint Counter = 1; } template Counter(lastCounter){ const uint Counter= lastCounter + 1; } tempalte MyTemplate(){ alias Counter!() first; alias Counter!(first) second; alias Counter!(second) last; alias last next; // use MyTemplate!().next where needed to keep counting } The gist is to keep passing around your 'counter' as you would an object at runtime. It's kind of ugly this way, but it should clean up a little if you use CTFE: // un-tested uint Counter(){ return 1; } uint Counter(in uint value){ return value+1; } uint MyFn(){ const auto first = Counter(); const auto second = Counter(first); const auto third = Counter(second); return third; } (* welcome to meta-programming) -- - EricAnderton at yahoo
May 22 2007
next sibling parent reply Fraser <fraserofthenight gmail.com> writes:
Thanks for the ideas! Unfortunately, it doesn't seem to be working (either
way). Here's the complete code I tried:

--------------------
import tango.io.Stdout;

char[] ctfe_itoa(uint value)
{
	if (value < 10) return "0123456789"[value .. value+1];
	return ctfe_itoa(value / 10) ~ ctfe_itoa(value % 10);
}

uint Counter(){
	return 1;
}

uint Counter(in uint value){
	return value+1;
}

uint nextID()
{
	const auto first = Counter();
	const auto second = Counter(first);
	const auto third = Counter(second);
	return third;
}

template Foo(char[] name)
{    
    const char[] text = "const char[] " ~ name ~ " = \"Name: " ~ name ~ ", ID:
" ~ ctfe_itoa(nextID()) ~ "\n\";";
}

mixin(Foo!("a").text);
mixin(Foo!("b").text);
mixin(Foo!("c").text);
mixin(Foo!("d").text);
mixin(Foo!("e").text);
mixin(Foo!("f").text);

int main(char[][] args)
{
	Stdout(a)(b)(c)(d)(e)(f);
	return 0;
}
--------------------

The result was:
Name: a, ID: 3
Name: b, ID: 3
Name: c, ID: 3
Name: d, ID: 3
Name: e, ID: 3
Name: f, ID: 3

A similar thing happened with the template example.

Pragma Wrote:

 Fraser wrote:
 <snip>


May 22 2007
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Fraser wrote:
 Thanks for the ideas! Unfortunately, it doesn't seem to be working (either
way). Here's the complete code I tried:
 
 --------------------
 import tango.io.Stdout;
 
 char[] ctfe_itoa(uint value)
 {
 	if (value < 10) return "0123456789"[value .. value+1];
 	return ctfe_itoa(value / 10) ~ ctfe_itoa(value % 10);
 }
 
 uint Counter(){
 	return 1;
 }
 
 uint Counter(in uint value){
 	return value+1;
 }
 
 uint nextID()
 {
 	const auto first = Counter();
 	const auto second = Counter(first);
 	const auto third = Counter(second);
 	return third;
 }

Looks to me like this is doing exactly what you asked it to: return 3 no matter what. I think you need a static variable like uint nextID() { static uint id=0; return ++id; } Unless I'm missing something. --bb
May 22 2007
prev sibling parent reply Pragma <ericanderton yahoo.removeme.com> writes:
Fraser wrote:
 Thanks for the ideas! Unfortunately, it doesn't seem to be working (either
way). Here's the complete code I tried:
 
 --------------------
 import tango.io.Stdout;
 
 char[] ctfe_itoa(uint value)
 {
 	if (value < 10) return "0123456789"[value .. value+1];
 	return ctfe_itoa(value / 10) ~ ctfe_itoa(value % 10);
 }
 
 uint Counter(){
 	return 1;
 }
 
 uint Counter(in uint value){
 	return value+1;
 }
 
 uint nextID()
 {
 	const auto first = Counter();
 	const auto second = Counter(first);
 	const auto third = Counter(second);
 	return third;
 }
 
 template Foo(char[] name)
 {    
     const char[] text = "const char[] " ~ name ~ " = \"Name: " ~ name ~ ", ID:
" ~ ctfe_itoa(nextID()) ~ "\n\";";
 }
 
 mixin(Foo!("a").text);
 mixin(Foo!("b").text);
 mixin(Foo!("c").text);
 mixin(Foo!("d").text);
 mixin(Foo!("e").text);
 mixin(Foo!("f").text);
 
 int main(char[][] args)
 {
 	Stdout(a)(b)(c)(d)(e)(f);
 	return 0;
 }
 --------------------
 
 The result was:
 Name: a, ID: 3
 Name: b, ID: 3
 Name: c, ID: 3
 Name: d, ID: 3
 Name: e, ID: 3
 Name: f, ID: 3
 
 A similar thing happened with the template example.

You need to prime your sequence with the zero counter value, and then keep passing a template instance around to continue to count up: template StartCounter(){ const uint next = 0; } template Foo(char[] name,alias counter) { const char[] text = "const char[] " ~ name ~ " = \"Name: " ~ name ~ ", ID: " ~ ctfe_itoa(counter.next) ~ "\n\";"; const uint next = counter.next + 1; } alias Foo!("a",StartCounter!()) foo_a; alias Foo!("b",foo_a) foo_b; alias Foo!("c",foo_b) foo_c; alias Foo!("d",foo_c) foo_d; // <-- note how we feed the previous template back into the next template instance alias Foo!("e",foo_d) foo_e; mixin(foo_a.text); mixin(foo_b.text); mixin(foo_c.text); mixin(foo_d.text); mixin(foo_e.text); -- - EricAnderton at yahoo
May 23 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Pragma wrote:
 Fraser wrote:
 Thanks for the ideas! Unfortunately, it doesn't seem to be working 
 (either way). Here's the complete code I tried:

 --------------------
 import tango.io.Stdout;

 char[] ctfe_itoa(uint value)
 {
     if (value < 10) return "0123456789"[value .. value+1];
     return ctfe_itoa(value / 10) ~ ctfe_itoa(value % 10);
 }

 uint Counter(){
     return 1;
 }

 uint Counter(in uint value){
     return value+1;
 }

 uint nextID()
 {
     const auto first = Counter();
     const auto second = Counter(first);
     const auto third = Counter(second);
     return third;
 }

 template Foo(char[] name)
 {        const char[] text = "const char[] " ~ name ~ " = \"Name: " ~ 
 name ~ ", ID: " ~ ctfe_itoa(nextID()) ~ "\n\";";
 }

 mixin(Foo!("a").text);
 mixin(Foo!("b").text);
 mixin(Foo!("c").text);
 mixin(Foo!("d").text);
 mixin(Foo!("e").text);
 mixin(Foo!("f").text);

 int main(char[][] args)
 {
     Stdout(a)(b)(c)(d)(e)(f);
     return 0;
 }
 --------------------

 The result was:
 Name: a, ID: 3
 Name: b, ID: 3
 Name: c, ID: 3
 Name: d, ID: 3
 Name: e, ID: 3
 Name: f, ID: 3

 A similar thing happened with the template example.

You need to prime your sequence with the zero counter value, and then keep passing a template instance around to continue to count up: template StartCounter(){ const uint next = 0; } template Foo(char[] name,alias counter) { const char[] text = "const char[] " ~ name ~ " = \"Name: " ~ name ~ ", ID: " ~ ctfe_itoa(counter.next) ~ "\n\";"; const uint next = counter.next + 1; } alias Foo!("a",StartCounter!()) foo_a; alias Foo!("b",foo_a) foo_b; alias Foo!("c",foo_b) foo_c; alias Foo!("d",foo_c) foo_d; // <-- note how we feed the previous template back into the next template instance alias Foo!("e",foo_d) foo_e; mixin(foo_a.text); mixin(foo_b.text); mixin(foo_c.text); mixin(foo_d.text); mixin(foo_e.text);

The problem with that (I'm assuming) is that if he could arrange it so that the generated code contained a nice ordered sequence of template names in increasing order, then that means he could also just as easily generate IDs themselves without going through all the template hoops. --bb
May 23 2007
parent reply Pragma <ericanderton yahoo.removeme.com> writes:
Bill Baxter wrote:
 Pragma wrote:
 Fraser wrote:
 Thanks for the ideas! Unfortunately, it doesn't seem to be working 
 (either way). Here's the complete code I tried:

 --------------------
 import tango.io.Stdout;

 char[] ctfe_itoa(uint value)
 {
     if (value < 10) return "0123456789"[value .. value+1];
     return ctfe_itoa(value / 10) ~ ctfe_itoa(value % 10);
 }

 uint Counter(){
     return 1;
 }

 uint Counter(in uint value){
     return value+1;
 }

 uint nextID()
 {
     const auto first = Counter();
     const auto second = Counter(first);
     const auto third = Counter(second);
     return third;
 }

 template Foo(char[] name)
 {        const char[] text = "const char[] " ~ name ~ " = \"Name: " ~ 
 name ~ ", ID: " ~ ctfe_itoa(nextID()) ~ "\n\";";
 }

 mixin(Foo!("a").text);
 mixin(Foo!("b").text);
 mixin(Foo!("c").text);
 mixin(Foo!("d").text);
 mixin(Foo!("e").text);
 mixin(Foo!("f").text);

 int main(char[][] args)
 {
     Stdout(a)(b)(c)(d)(e)(f);
     return 0;
 }
 --------------------

 The result was:
 Name: a, ID: 3
 Name: b, ID: 3
 Name: c, ID: 3
 Name: d, ID: 3
 Name: e, ID: 3
 Name: f, ID: 3

 A similar thing happened with the template example.

You need to prime your sequence with the zero counter value, and then keep passing a template instance around to continue to count up: template StartCounter(){ const uint next = 0; } template Foo(char[] name,alias counter) { const char[] text = "const char[] " ~ name ~ " = \"Name: " ~ name ~ ", ID: " ~ ctfe_itoa(counter.next) ~ "\n\";"; const uint next = counter.next + 1; } alias Foo!("a",StartCounter!()) foo_a; alias Foo!("b",foo_a) foo_b; alias Foo!("c",foo_b) foo_c; alias Foo!("d",foo_c) foo_d; // <-- note how we feed the previous template back into the next template instance alias Foo!("e",foo_d) foo_e; mixin(foo_a.text); mixin(foo_b.text); mixin(foo_c.text); mixin(foo_d.text); mixin(foo_e.text);

The problem with that (I'm assuming) is that if he could arrange it so that the generated code contained a nice ordered sequence of template names in increasing order, then that means he could also just as easily generate IDs themselves without going through all the template hoops. --bb

Possibly. It all depends on the complexity of his solution - the trivial example above doesn't do it much justice. Where this really comes in handy is when you need to repeat any such block of declarations: template Foo!(alias counter){ alias Foo!("a",StartCounter!()) foo_a; alias Foo!("b",foo_a) foo_b; alias Foo!("c",foo_b) foo_c; mixin(foo_a.text); mixin(foo_b.text); mixin(foo_c.text); alias foo_c.next next; // save the counter state for the next use } alias Foo!(StartCounter!()) foo1; alias Foo!(foo1) foo2; alias Foo!(foo2) foo3; -- - EricAnderton at yahoo
May 23 2007
parent Fraser <fraserofthenight gmail.com> writes:
Thanks. Yeah, that seems to work, but Bill is right - generating the IDs is
probably simpler than trying to ensure I keep a reference to the template each
time. Since it might need to go between multiple files, I think I'm probably
going to have to look for a runtime solution, anyway. Thanks for your help.

Pragma Wrote:

 Bill Baxter wrote:
 Pragma wrote:
 Fraser wrote:
 Thanks for the ideas! Unfortunately, it doesn't seem to be working 
 (either way). Here's the complete code I tried:

 --------------------
 import tango.io.Stdout;

 char[] ctfe_itoa(uint value)
 {
     if (value < 10) return "0123456789"[value .. value+1];
     return ctfe_itoa(value / 10) ~ ctfe_itoa(value % 10);
 }

 uint Counter(){
     return 1;
 }

 uint Counter(in uint value){
     return value+1;
 }

 uint nextID()
 {
     const auto first = Counter();
     const auto second = Counter(first);
     const auto third = Counter(second);
     return third;
 }

 template Foo(char[] name)
 {        const char[] text = "const char[] " ~ name ~ " = \"Name: " ~ 
 name ~ ", ID: " ~ ctfe_itoa(nextID()) ~ "\n\";";
 }

 mixin(Foo!("a").text);
 mixin(Foo!("b").text);
 mixin(Foo!("c").text);
 mixin(Foo!("d").text);
 mixin(Foo!("e").text);
 mixin(Foo!("f").text);

 int main(char[][] args)
 {
     Stdout(a)(b)(c)(d)(e)(f);
     return 0;
 }
 --------------------

 The result was:
 Name: a, ID: 3
 Name: b, ID: 3
 Name: c, ID: 3
 Name: d, ID: 3
 Name: e, ID: 3
 Name: f, ID: 3

 A similar thing happened with the template example.

You need to prime your sequence with the zero counter value, and then keep passing a template instance around to continue to count up: template StartCounter(){ const uint next = 0; } template Foo(char[] name,alias counter) { const char[] text = "const char[] " ~ name ~ " = \"Name: " ~ name ~ ", ID: " ~ ctfe_itoa(counter.next) ~ "\n\";"; const uint next = counter.next + 1; } alias Foo!("a",StartCounter!()) foo_a; alias Foo!("b",foo_a) foo_b; alias Foo!("c",foo_b) foo_c; alias Foo!("d",foo_c) foo_d; // <-- note how we feed the previous template back into the next template instance alias Foo!("e",foo_d) foo_e; mixin(foo_a.text); mixin(foo_b.text); mixin(foo_c.text); mixin(foo_d.text); mixin(foo_e.text);

The problem with that (I'm assuming) is that if he could arrange it so that the generated code contained a nice ordered sequence of template names in increasing order, then that means he could also just as easily generate IDs themselves without going through all the template hoops. --bb

Possibly. It all depends on the complexity of his solution - the trivial example above doesn't do it much justice. Where this really comes in handy is when you need to repeat any such block of declarations: template Foo!(alias counter){ alias Foo!("a",StartCounter!()) foo_a; alias Foo!("b",foo_a) foo_b; alias Foo!("c",foo_b) foo_c; mixin(foo_a.text); mixin(foo_b.text); mixin(foo_c.text); alias foo_c.next next; // save the counter state for the next use } alias Foo!(StartCounter!()) foo1; alias Foo!(foo1) foo2; alias Foo!(foo2) foo3; -- - EricAnderton at yahoo

May 23 2007
prev sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Pragma wrote:
 Fraser wrote:
 In particular, I want a mixin template that mixes in a  unique numeric 
 ID as part of a function every time it's used in a mixin. 

This is possible, but it will take some mildly ugly code to accomplish*. The major hurdle to overcome is that templates are effectively stateless. This is because every template invocation is dependent upon its arguments and global constants for evaluation, and is unable to change anything but its own definition. The result is that there's no compile-time analog for simple stuff like this:

 
 (* welcome to meta-programming)
 

It's more specific than that. Like "welcome to D meta-programming". Meta-programming in other languages, like macros in Lisp, are done as normal Lisp code and thus also have state (as I believe you know already). In D that's not the case, and I find it interesting, and even kinda of funny and ironic, that D meta-programming is a *completly* pure functional programming world :P . Funny and ironic because the first thing that poped to mind when I first "entered" D meta-programming, was the Scheme pure functional programs we (me and my fellow colleagues) did some years ago in my college's first-year, SICP-based college course. :) -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 26 2007
parent reply "David B. Held" <dheld codelogicconsulting.com> writes:
Bruno Medeiros wrote:
 [...]
 It's more specific than that. Like "welcome to D meta-programming". 
 Meta-programming in other languages, like macros in Lisp, are done as 
 normal Lisp code and thus also have state (as I believe you know already).
 In D that's not the case, and I find it interesting, and even kinda of 
 funny and ironic, that D meta-programming is a *completly* pure 
 functional programming world :P . Funny and ironic because the first 
 thing that poped to mind when I first "entered" D meta-programming, was 
 the Scheme pure functional programs we (me and my fellow colleagues) did 
 some years ago in my college's first-year, SICP-based college course. :)

It's true that D shares C++'s pure metaprogramming facilities, but I think it is fairly easy to see why. Both of these languages already know how to do constant folding and template instantiation, which don't require any heap management. Since dynamic languages like Lisp generally have a runtime evaluation engine, running that in the compile phase is no big deal, whereas it's a lot less trivial to do that in D (though Walter allows dynamic strings as the exception to the compile-time heap rule). Perhaps D should support phased compilation, but I think a better approach would be to have better support for functional programming (if functions were really first class values in D, you could create them as easily as the builtins and do so at compile time, which would make it possible to do things like monads). Dave
May 26 2007
parent reply BCS <ao pathlink.com> writes:
Reply to David,

 Bruno Medeiros wrote:
 
 [...]
 I find it interesting, and even kinda of
 funny and ironic, that D meta-programming is a *completly* pure
 functional programming world :P . Funny and ironic because the first
 thing that poped to mind when I first "entered" D meta-programming,
 was the Scheme pure functional programs we (me and my fellow 
 colleagues) did some years ago in my college's first-year, SICP-based
 college course.
 :)


same here, meta stuff really clicked after doing some scheme
 It's true that D shares C++'s pure metaprogramming facilities, but I
 think it is fairly easy to see why.  Both of these languages already
 know how to do constant folding and template instantiation, which
 don't require any heap management.  Since dynamic languages like Lisp
 generally have a runtime evaluation engine, running that in the
 compile phase is no big deal, whereas it's a lot less trivial to do
 that in D (though Walter allows dynamic strings as the exception to
 the compile-time heap rule).
 

why is it just char[], why not T[] for any T that can be used by it's self? I have some cases where I want to do that.
May 28 2007
parent reply "David B. Held" <dheld codelogicconsulting.com> writes:
BCS wrote:
 [...]
 why is it just char[], why not T[] for any T that can be used by it's 
 self? I have some cases where I want to do that.

I don't know the full story, but after looking through the front-end code, my guess is that it's just easier that way. That is, when checking to see whether a particular type can be a compile-time array, it's easier to check against one type than against a set of types, or detect a set of type properties. Also, since dynamic char[] already violates the purity of metaprogramming, I think that Walter didn't want to expand and encourage that without thinking very hard about it (it's easier to give people new features than to take them away). Dave
May 28 2007
next sibling parent reply BCS <ao pathlink.com> writes:
Reply to David,

 BCS wrote:
 
 [...]
 why is it just char[], why not T[] for any T that can be used by it's
 self? I have some cases where I want to do that.

I don't know the full story, but after looking through the front-end code, my guess is that it's just easier that way. That is, when checking to see whether a particular type can be a compile-time array, it's easier to check against one type than against a set of types, or detect a set of type properties.

Hmmm.
 Also, since dynamic char[] already
 violates the purity of metaprogramming, I think that Walter didn't
 want to expand and encourage that without thinking very hard about it
 (it's easier to give people new features than to take them away).
 

How is that? If everthing in meta land is pass by value (including strings) how are arrays any different than normal types?
 Dave
 

May 29 2007
parent reply "David B. Held" <dheld codelogicconsulting.com> writes:
BCS wrote:
 Reply to David,
 [...]
 Also, since dynamic char[] already
 violates the purity of metaprogramming, I think that Walter didn't
 want to expand and encourage that without thinking very hard about it
 (it's easier to give people new features than to take them away).

How is that? If everthing in meta land is pass by value (including strings) how are arrays any different than normal types?

How about the fact that resizing implies a dynamic allocation? Dave
May 29 2007
parent reply BCS <ao pathlink.com> writes:
Reply to David,

 BCS wrote:
 
 Reply to David,
 [...]
 Also, since dynamic char[] already
 violates the purity of metaprogramming, I think that Walter didn't
 want to expand and encourage that without thinking very hard about
 it
 (it's easier to give people new features than to take them away).

strings) how are arrays any different than normal types?

Dave

the closest you can get to resize in meta land is "~" and that /is/ allowed for strings. | template Foo(char[] str) | { | const char[] Foo = \" ~ str ~ \" | } what specific problems exist for non char arrays that don't exist for char arrays?
May 30 2007
parent "David B. Held" <dheld codelogicconsulting.com> writes:
BCS wrote:
 [...]
 the closest you can get to resize in meta land is "~" and that /is/ 
 allowed for strings.
 
 | template Foo(char[] str)
 | {
 |   const char[] Foo = \" ~ str ~ \"
 | }
 
 what specific problems exist for non char arrays that don't exist for 
 char arrays?

According to Don, it's an accident that it works for strings. ;) A happy accident, no doubt; but that explains why it doesn't work for anything else. Dave
May 30 2007
prev sibling parent Don Clugston <dac nospam.com.au> writes:
David B. Held wrote:
 BCS wrote:
 [...]
 why is it just char[], why not T[] for any T that can be used by it's 
 self? I have some cases where I want to do that.

I don't know the full story, but after looking through the front-end code, my guess is that it's just easier that way. That is, when checking to see whether a particular type can be a compile-time array, it's easier to check against one type than against a set of types, or detect a set of type properties. Also, since dynamic char[] already violates the purity of metaprogramming, I think that Walter didn't want to expand and encourage that without thinking very hard about it (it's easier to give people new features than to take them away). Dave

Nope. It's because D had built-in support for string literals from way back (whereas array literals are very recent). Concatenation of string literals was accidentally possible, and I showed that it could be used to do some interesting metaprogramming. I explicitly asked for the other char [] operations to be constant folded at compile time. See the change log for around DMD 0.135-0.145 to see the history.
May 30 2007