This post will describe the basics of a very powerful feature of the D programming language: the Compile-time Function Execution (CTFE), which allows complicated functions to be fully evaluated at compile-time, irrespective of the optimization levels.
If you are an average C programmer, you know that simple code can be trusted to be evaluated at compile-time thanks to optimizers. For instance, if you write something like:
void square(int x) { return x*x; }
void foo(void)
{
int k = square(32);
/* ... */
}
you trust your compiler to evaluate the square at compile-time, when optimizations are on. When things get more hairy:
int factorial(int x)
{
int result = x;
while (--x)
result *= x;
return result;
}
void foo(void)
{
int k = factorial(8);
/* ... */
}
C programmers get immediately less confident about what will happen at run-time. For instance, would you say that your compiler is able to expand the above code at compile-time or not? Actually, the answer is “yes” in this particular case (unless you are using a very old compiler), but the point is still valid: this is not C code that one would write if he wants to be sure that the whole calculation be folded at compile-time.
There is also another issue: since the language does not mandate that the value is folded (and in fact, it is not folded when optimizations are disabled), you cannot create a constant out of it, such as by assigning it to a const variable.
Now, let’s try with a (very naive and simple) solution of problem #1 of Project Euler:
#include <stdio.h>
int euler1(int max)
{
int i,res=0;
for (i=1; i<max; i++)
{
if ((i % 3) == 0 || (i % 5) == 0)
res += i;
}
return res;
}
int main()
{
int r10 = euler1(10);
int r1000 = euler1(1000);
printf("%d %d\n", r10, r1000);
return 0;
}
This program simply calculates the sum of all divisors of 3 or 5 below 1000. But if you look at the generated code with GCC under -O3, you will see that the actual results are not computed at compile-time, but rather calculated at runtime. I believe any average C programmer would agree that we should not expect this code to be folded at compile time.
Now, meet the equivalent D code:
int euler1(int max)
{
int i,res=0;
for (i=1; i<max; i++)
{
if ((i % 3) == 0 || (i % 5) == 0)
res += i;
}
return res;
}
int main()
{
int r10 = euler1(10);
int r1000 = euler1(1000);
printf("%d %d\n", r10, r1000);
return 0;
}
Deja-vu? Yes, it is exactly the same, barring the initial include statement that is not required (actually, there is no preprocessor in D and modules refer to each other with the import statement, but printf is a builtin). Of course, the above example was hand-crafted to make it both valid C and D code, but being D an evolution of C, the basic syntax is the same.
And now the hattrick: in D, we can request the compiler to evaluate euler1 at compile-time by simply using the static keyword at invocation time:
static int r10 = euler1(10); static int r1000 = euler1(1000);
Great, isn’t it? Now the result of the above function call are evaluated by the compiler, irrespective of the optimization levels. If the function cannot be evaluated at compile-time (usually because it has side-effects, like any kind of I/O), it will trigger a compile-time error.
We can verify that the above constants really do appear in the generated code by compiling with gdc -save-temps euler1.d and then inspecting euler1.s:
D6euler14mainFZi3r10i:
.long 23
.globl _D6euler14mainFZi5r1000i
.align 4
.type _D6euler14mainFZi5r1000i, @object
.size _D6euler14mainFZi5r1000i, 4
_D6euler14mainFZi5r1000i:
.long 233168
.section .rodata
.LC0:
.string "%d %d\n"
.text
.globl _Dmain
.type _Dmain, @function
_Dmain:
.LFB3:
pushq %rbp
.LCFI3:
movq %rsp, %rbp
.LCFI4:
movl _D6euler14mainFZi5r1000i(%rip), %edx
movl _D6euler14mainFZi3r10i(%rip), %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
ret
Notice how the compiler has calculated the values 23 and 233168 (respectively, results of euler1(10) and euler1(1000)) and put them in the data section of the executable.
If you are curious of what happens when the compiler cannot do the whole evaluation at compile-time, it is sufficient to stick a printf() call somewhere in the euler() function. Since printf() does some I/O, it breaks CFTE, and the compiler will happily tell you about that:
euler1.d:9: Error: cannot evaluate printf("hello, world!\x0a") at compile time
euler1.d:15: Error: cannot evaluate euler1(10) at compile time
euler1.d:16: Error: cannot evaluate euler1(1000) at compile time
CTFE is simple yet very powerful. The fact that it is triggered at the call site (rather than being an attribute of the function, like the inline keyword) is a very smart design choice: it makes perfectly sense for the same function to be used at both run-time and compile-time, depending on the inputs.
For the C++ guys reading, C++0x has grown a constexpr keyword that, while looking superficially similar, it is a lot less powerful, since it can only be used on very simple functions (basically, one-liners). In fact, the keyword is meant to be use while declaring a function, and not at the call-site, so it has to apply only on small functions which can be proved to always yield a constant value.
In the next weeks, I will post a more complete real-world example of the power of CTFE from BeRTOS. Stay tuned!
88 Responses
mmmattos
31|May|2010 1Compile-time Function Execution in D language http://bit.ly/9BlvOG #yam #in #programming
This comment was originally posted on Twitter
Dubhead
03|Jun|2010 2D言語のコンパイル時関数実行の簡単な解説 #d_lang → Compile-time Function Execution in D http://ff.im/-lpjeg
This comment was originally posted on Twitter
andralex
03|Jun|2010 3Solid intro. I see the article uses D1. D2 has vastly expanded CTFE abilities, including array and associative array manipulation and transcendental floating-point functions evaluation, but stopping short of allocating memory and creating class objects.
This comment was originally posted on Reddit
plulz
03|Jun|2010 4More awesome CTFE than his example: import std.stdio; string foo() { return "writeln("Fun times.");"; } void bar() { mixin (foo()); } void main() { bar; } writes ‘Fun times.’ when run.
This comment was originally posted on Reddit
mitch
03|Jun|2010 5The code and text switch between r10 = euler1(10) and r10 = euler1(23) – this should be clarified to not confuse the casual reader
anonymous
03|Jun|2010 6s/CFTE/CTFE
fionbio
03|Jun|2010 7In Common Lisp, the following is used to achieve this effect (and was used for years as it is usual with new & cool features in other languages
: #.(euler1 23) Strictly speaking, this is read-time evaluation, but anyway the effect is that the expression is evaluated while compiling the source file.
This comment was originally posted on Reddit
andralex
03|Jun|2010 8As a friend likes to say – many languages learned their diction from Lisp…
This comment was originally posted on Reddit
nefigah
03|Jun|2010 9A note to the author: :%s/CFTE/CTFE/g
This comment was originally posted on Reddit
JoeCoder
03|Jun|2010 10Seems nicer than C++ 0x’s [constexpr](http://en.wikipedia.org/wiki/C%2B%2B0x#Generalized_constant_expressions). The caller gets to decide what’s at compile time instead of the callee.
This comment was originally posted on Reddit
wozer
03|Jun|2010 11Related: http://rosettacode.org/wiki/Compile-time_calculation
This comment was originally posted on Reddit
giovannibajo
03|Jun|2010 12changed, thanks
This comment was originally posted on Reddit
baxissimo
03|Jun|2010 13That’s odd. I thought ‘static’ inside a function acted as a storage class in D, just as in C/C++. I would have thought you would need to make it ‘const’, ‘immutable’ or ‘enum’ (in D2) to trigger CTFE.
D_Programming
03|Jun|2010 14#d_lang Compile-time Function Execution by Giovanni Bajo http://giovanni.bajo.it/2010/05/compile-time-function-execution-in-d/
This comment was originally posted on Twitter
leaflord
03|Jun|2010 15You know, often I wonder if they mean "Lisp" or "Common Lisp" cuz most of the time it seems to be that it’s a library somewhere inside Common Lisp instead.
This comment was originally posted on Reddit
andralex
03|Jun|2010 16Excellent insight (bad ID choice I guess
)). Since the caller gets to decide, many functions are "portable" across compile-time and run-time, which is a key reuse point (and a frequent case in D’s standard library). The second issue with constexpr is it’s not Turing complete, which makes it of very limited usability. Walter Bright and myself have tried to argue these two points on the Usenet newsgroup comp.lang.c++.moderated, without success.
This comment was originally posted on Reddit
zxqdms
03|Jun|2010 17Here’s how to accomplish something similar in PLT Scheme: I split it into two files. First file: definition of euler1 function: #lang scheme (provide euler1) ; define the function like normal here (define (euler1 max) (printf "We’re in euler1!!!n") ; IO is allowed (let recur ((i 1) (res 0)) (if (< i max) (if (or (= 0 (remainder i 3)) (= 0 (remainder i 5))) (recur (add1 i) (+ res i)) (recur (add1 i) res)) res))) Second file with runtime and expansion time calls: #lang scheme (require "euler.scm" ; import function for runtime use (for-syntax "euler.scm"); import function for expansion time use ) ; A macro that generates a macro to evaluate the argument at expansion time (define-syntax (ct-expr stx) (syntax-case stx () ((_ expr) #’(let-syntax ((eval-expr (lambda (ignored) ; this is evaluated at expansion time ; with-syntax is used to evaluate the expression ; and then turn result into a syntax object, which is what macros ; must return (with-syntax ((res expr)) #’res)))) (eval-expr))))) (ct-expr (euler1 1000)) (euler1 1000)
This comment was originally posted on Reddit
gsg_
03|Jun|2010 18> Strictly speaking, this is read-time evaluation Yeah, there’s no exact analogue because compile time is quite a flexible notion in Lisp. `(load-time-value …)` might be a bit closer.
This comment was originally posted on Reddit
baryluk
03|Jun|2010 19We have this in D for years. It is amazing that most hackers doesn’t know about it.
This comment was originally posted on Reddit
pkhuong
04|Jun|2010 20macros?
This comment was originally posted on Reddit
thechao
04|Jun|2010 21Gabriel Dos Reis (one of the inventors of constexpr) argued vociferously for a much-expanded definition of constexpr, i.e., any function which appeared "side-effect-less". He was shot down a number of times. The source for Gaby’s notion of constexpr comes from a language called "Spad" which allows read-time (compile-time) evaluation of arbitrary code under the aforementioned constraints.
This comment was originally posted on Reddit
Ms_Gen
04|Jun|2010 22A very interesting article about optimizations at compile time: http://giovanni.bajo.it/2010/05/compile-time-function-execution-in-d/
This comment was originally posted on Twitter
WalterBright
04|Jun|2010 23One of the huge advantages to CTFE in D is the ability to manipulate strings entirely at compile time. You can (and some have done) even build parsers with it to make a DSL. This would be severely difficult in C++ because there is little language recognition of strings.
This comment was originally posted on Reddit
Giovanni Bajo
04|Jun|2010 24@Mitch: fixed, thanks!
andralex
04|Jun|2010 25Nice, but that was expected from a language with a meta-circular evaluator. The interesting thing is that D achieves compile-time evaluation within a traditional compile-link-run framework.
This comment was originally posted on Reddit
you_do_realize
04|Jun|2010 26No `()` after `bar`?
This comment was originally posted on Reddit
spotta
04|Jun|2010 27Is D2 finalized?
This comment was originally posted on Reddit
shellderp
04|Jun|2010 28yay for slower compile times
This comment was originally posted on Reddit
andralex
04|Jun|2010 29The compiler is [5.4 times faster](http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=108831) than Go’s compiler when measured against its own standard library. D’s standard library has unittests that exercise a variety of CTFE paraphernalia. (Unittest run time not included.)
This comment was originally posted on Reddit
Jengu
04|Jun|2010 30Because the static keyword’s meaning wasn’t overloaded enough! ;p Srsly though, this meaning is probably much closer to how people generally use ’static’ today in conversation (e.g. static typing) so I approve ;p
This comment was originally posted on Reddit
Bhelyer
04|Jun|2010 31Parens can be omitted for parameter-less functions. Single parameter functions can be called by assignment: void foo(int i) { } void main() { foo = 1; } In D2 this is intended to be annotated with the attribute `@property`, but this hasn’t happened yet.
This comment was originally posted on Reddit
fionbio
04|Jun|2010 32Of course macros do this too, but read-time evaluation is handly in many cases when you just want to have pre-evaluated value in the compiled (e.g. fasl) file.
This comment was originally posted on Reddit
MaurizioColucci
04|Jun|2010 33Compile-time Function Execution in D http://bit.ly/aE2vzn @giovannibajo
This comment was originally posted on Twitter
p0nce
04|Jun|2010 34I’ve only used const so far to force CTFE… I thought static was just a storage class.
This comment was originally posted on Reddit
axilmar
04|Jun|2010 35Given that compile time execution cannot have side effects and cannot manipulate symbol tables, is it that useful? C++ can do this through templates. The syntax "static int r10 = euler1(10);" is confusing; in C, it introduces a static variable, not a compile time computation. I don’t see why CTFE can’t have side effects. They already built the interpreter that does the computations, why not make it complete? Finally, If I ever need to compute a constant that requires me to write a program, I can simply open a new project, write the program, get the result value and paste it in my code. In other words, CTFE seems kind of redundant functionality for a programming language. I’d much prefer if D was standardized than being a moving target.
This comment was originally posted on Reddit
ntrel
04|Jun|2010 36I don’t think that static is overloaded for any CTFE purpose, just that it’s one way of forcing the compiler to evaluate at compile-time.
This comment was originally posted on Reddit
ntrel
04|Jun|2010 37You can do it through templates, but this is uglier. You can evaluate, copy & paste, but this is not reliable and takes time. The ’static’ modifier here does introduce a static variable, so the compiler has to evaluate the expression to initialize the variable at compile time. There are other ways of forcing CTFE too.
This comment was originally posted on Reddit
G_Morgan
04|Jun|2010 38It is useful if you write in a pure functional style.
This comment was originally posted on Reddit
G_Morgan
04|Jun|2010 39Turn off optimisation during testing. Turn on during release builds. Simple yeah?
This comment was originally posted on Reddit
G_Morgan
04|Jun|2010 40I’m not sure I want to do this via a directive. What we are talking about is compile time evaluation of pure functions when the arguments are constants. Any compiler should do this automatically. There is literally no benefit to not doing it.
This comment was originally posted on Reddit
axilmar
04|Jun|2010 41Can you please give me an example?
This comment was originally posted on Reddit
andralex
04|Jun|2010 42We considered this for D but rejected it for practicality considerations. Not all pure functions in D are computable during compilation (simple example: source is not available), so there is no reliable way for the compiler to assess that a function can be successfully evaluated during compilation. Attempting to do compile-time evaluation against all eligible calls would slow down compilation considerably.
This comment was originally posted on Reddit
pkhuong
04|Jun|2010 43Compilation entails macro-expansion.
This comment was originally posted on Reddit
andralex
04|Jun|2010 44In fact compile-time execution can manipulate symbol tables because it can generate and compile snippets of code. See for example a [library facility](http://www.digitalmars.com/d/2.0/phobos/std_bitmanip.html) that implements the functionality of C bitfields.
This comment was originally posted on Reddit
redalastor
04|Jun|2010 45Still alpha.
This comment was originally posted on Reddit
redalastor
04|Jun|2010 46The benefit of having the keyword is that you are 100% sure it will be done because you’ll get a compile error if it cannot be.
This comment was originally posted on Reddit
axilmar
04|Jun|2010 47Can it manipulate symbols declared outside of templates?
This comment was originally posted on Reddit
Jengu
04|Jun|2010 48The article uses static.
This comment was originally posted on Reddit
naughty
04|Jun|2010 49Is it mainly the memory management side that’s stopping a fuller implementation? By fuller I mean something like [Multi-Stage Programming](http://www.cs.rice.edu/~taha/MSP/) although I could understand it being restricted to two stages, i.e. no run-time compilation.
This comment was originally posted on Reddit
naughty
04|Jun|2010 50Yay for turning interpreters into compilers ;^)
This comment was originally posted on Reddit
zxqdms
04|Jun|2010 51Macro expansion is independent of how the run-time language is implemented. The above code works whether or not the final expanded code is compiled or interpreted. The expansion-time and run-time environments are completely separate, that is the reason that the euler1 function had to be imported twice. The (ct-expr (euler1 1000)) call is evaluated at expansion-time, using the euler1 function from the expansion-time environment. The compiled code is just a constant number. The (euler1 1000) call is evaluated at run-time, using the euler1 function in the run-time environment. In this case, the euler1 function itself would also exist in the compiled code. You may find this paper about macros and compilation interesting: [Composable and Compilable Macros: You Want it When?](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.11.4008)
This comment was originally posted on Reddit
zxqdms
04|Jun|2010 52Macro expansion is independent of how the run-time language is implemented. The above code works whether or not the final expanded code is compiled or interpreted. The expansion-time and run-time environments are completely separate, that is the reason that the euler1 function had to be imported twice. The (ct-expr (euler1 1000)) call is evaluated at expansion-time, using the euler1 function from the expansion-time environment. The compiled code is just a constant number. The (euler1 1000) call is evaluated at run-time, using the euler1 function in the run-time environment. In this case, the euler1 function itself would also exist in the compiled code.
This comment was originally posted on Reddit
andralex
04|Jun|2010 53Thanks for the info and pointer. (Flatt = awesome as ever, and very unassuming and down to earth. We met a while ago and I was very surprised that he’d heard of me.)
This comment was originally posted on Reddit
G_Morgan
04|Jun|2010 54The original post gave an example. Practically all the Euler problems can be evaluated at compile time.
This comment was originally posted on Reddit
andralex
04|Jun|2010 55It’s more a matter of implementation. The plan is to allow creating new objects whenever possible; it’s just not implemented. Things like calling malloc(), I/O routines, or inline assembler will probably stay verboten. There are currently no plans for multi-stage compilation or partial evaluation.
This comment was originally posted on Reddit
giovannibajo
04|Jun|2010 56I’m going to give a more compelling example in a followup post, which is basically evaluating the PLL registers for configuring the clock of an embedded CPU. But being able to evaluate anything (including string algorithms) at compile-time, using the SAME syntax you use at runtime (instead of the archane syntax and idioms templates use), is indeed extremely powerful.
This comment was originally posted on Reddit
fionbio
04|Jun|2010 57Of course it does! But sometimes #. is easier than defmacro or macrolet.
This comment was originally posted on Reddit
speek
04|Jun|2010 58A question about D that’s been bugging me: why aren’t there any D examples at the language shootout site?
This comment was originally posted on Reddit
andralex
04|Jun|2010 59Not sure, I remember people were submitting stuff. I’ll check on the newsgroup.
This comment was originally posted on Reddit
nascent
04|Jun|2010 60Language features are coming to an end. Phobos will be receiving a number of changes, including a container module. So there shouldn’t be any major breaking changes coming.
This comment was originally posted on Reddit
andralex
04|Jun|2010 61OK, it seems the maintainer of the shootout site is not interested. I found these older links: [http://www.functionalfuture.com/d/](http://www.functionalfuture.com/d/) [http://shootout.alioth.debian.org/gp4/benchmark.php?test=all&lang=all](http://shootout.alioth.debian.org/gp4/benchmark.php?test=all&lang=all)
This comment was originally posted on Reddit
nascent
04|Jun|2010 62If you use ‘const’ the value can’t be changed. If you use static it can be.
This comment was originally posted on Reddit
nascent
04|Jun|2010 63Annotating with @property will require that foo be assigned to and not called. I don’t think annotation will become a requirement.
This comment was originally posted on Reddit
andralex
04|Jun|2010 64Only under limited circumstances (the so-called "mixin templates").
This comment was originally posted on Reddit
nascent
04|Jun|2010 65Assuming we are talking about the same [site](http://shootout.alioth.debian.org/), D was removed from the list. But you can find some really old benchmarks here: http://www.functionalfuture.com/d/
This comment was originally posted on Reddit
WalterBright
04|Jun|2010 66Here’s the shoot out maintainer’s (Isaac Gouy) [reason](http://www.digitalmars.com/d/archives/digitalmars/D/No_D_in_Great_Computer_Language_Shootout_103371.html#N103383)
This comment was originally posted on Reddit
spotta
04|Jun|2010 67This was the question I really had, just didn’t know how to ask it. So if I start developing for D2, my code shouldn’t break majorly with the next release of D2?
This comment was originally posted on Reddit
andralex
04|Jun|2010 68It won’t break. D2 was intentionally disruptive in order to get things right (particularly with concurrency), but from here onwards the pace of change will be way slower and priority will be given to backwards compatibility. We believe we are now on a solid foundation that won’t need major changes soon.
This comment was originally posted on Reddit
spotta
05|Jun|2010 69Awesome, thanks a lot. You guys do great work, and I’ve been looking forward to D2 for a while, I just didn’t want to invest time and code into something that wasn’t ready yet. Thanks
This comment was originally posted on Reddit
FeepingCreature
05|Jun|2010 70Nested functions?
This comment was originally posted on Reddit
FeepingCreature
05|Jun|2010 71import std.stdio, tools.ctfe; string foo() { return "writefln(q{Fun times, q{further nested. }p}p); ".litstring_expand(); } void bar() { mixin (foo()); } void main() { bar; } writes ‘Fun times, "further nested. "’
This comment was originally posted on Reddit
FeepingCreature
05|Jun|2010 72Yeah but that’s a bit like the Linux/GNU Linux distinction.
This comment was originally posted on Reddit
speek
05|Jun|2010 73Thanks. Reading that is almost enough to make me want to create a good programming language benchmarking site. Isaac is right about one thing – it’d be a lot of work
I’d be inclined to test on 8-core machines at a minimum, and cloud computing at the high end.
This comment was originally posted on Reddit
speek
05|Jun|2010 74Thanks!
This comment was originally posted on Reddit
igouy
05|Jun|2010 75> almost enough to make me want to create a good programming language benchmarking site Please do! For a handful of (basically similar) language implementations, it would take much less time both to make the measurements and maintain implementations – so the measurements could be over a larger number of data values, and for a larger sample size. The Python measurement scripts are zipped and [ready for download from the Help page](http://shootout.alioth.debian.org/help.php#languagex) (although you should probably check [the requirements and recommendations](https://alioth.debian.org/frs/shownotes.php?release_id=1484) first).
This comment was originally posted on Reddit
igouy
05|Jun|2010 76The puzzle is that there didn’t seem to be anyone in *the D community* who was interested in doing the work.
This comment was originally posted on Reddit
JoeCoder
05|Jun|2010 77>bad ID choice I guess
Stroustrup seems to [feel the same way](http://www.reddit.com/r/programming/comments/c7zip/you_think_you_are_an_expert_coder_just_keep_this/).
This comment was originally posted on Reddit
andralex
05|Jun|2010 78The clear issue there is lack of 64-bit support. That is valiantly being work on and will be a reality later this year.
This comment was originally posted on Reddit
igouy
05|Jun|2010 79Sorry I wasn’t clear enough. There doesn’t seem to be anyone in *the D community* who is interested in doing the work (to measure the performance of D programs, and compare those measurements against the performance of programs written in other languages). Seems like something *the D community* rather than D implementors should be able to do.
This comment was originally posted on Reddit
andralex
05|Jun|2010 80Those are just a bit tricky, but nothing that can’t be done. Feel free to submit a bug report if not already in.
This comment was originally posted on Reddit
andralex
05|Jun|2010 81I understand. As I’ve worked on computationally-intensive problems with D for the past years, I’ve constantly compared its performance against C++ and scripting languages – it was a matter of survival. 50% slower for one inner loop means a day and a half instead of a day! It’s just that I haven’t cared to make the results public or to build a set of consistent benchmarks.
This comment was originally posted on Reddit
igouy
05|Jun|2010 82So you could have said "it seems the maintainer of the shootout site is not interested" *and neither am I*
This comment was originally posted on Reddit
BioTronic
06|Jun|2010 83It will. The Great Andrei Himself moved that it is time, on may 4th.
This comment was originally posted on Reddit
axilmar
06|Jun|2010 84So it can only manipulate symbol tables at compile time in a limited fashion. That’s not exactly "In fact compile-time execution can manipulate symbol tables because it can generate and compile snippets of code" as you said, which implies it can do it for all types of symbols. While it is good that this type of functionality (bitfields) can be done like this, it’s nothing more than a gimmick in real life terms. I’d be more impressed if D could execute code normally at compile time, just like it was at run-time, and be able to manipulate the whole program.
This comment was originally posted on Reddit
axilmar
06|Jun|2010 85> But being able to evaluate anything (including string algorithms) at compile-time It’s not really ‘anything’, according to another poster. The compile-time code cannot manipulate the compiler context (i.e. symbol tables) to produce anything. > using the SAME syntax you use at runtime (instead of the archane syntax and idioms templates use), is indeed extremely powerful. Should I be impressed, given that LISP does that the last 50 years? There is a long list of programming languages able to do that before D.
This comment was originally posted on Reddit
axilmar
06|Jun|2010 86But that doesn’t answer the question.
This comment was originally posted on Reddit
mmalex
08|Jun|2010 87I wish I had this in C, especially for executing hash functions to build case labels at compile time: http://bit.ly/cXRNBe
This comment was originally posted on Twitter
okonomiyonda
09|Jun|2010 88Wow! RT @mmalex: I wish I had this in C, especially for executing hash functions to build case labels at compile time: http://bit.ly/cXRNBe
This comment was originally posted on Twitter