Enum and CTFE function call

Everlast Everlast at For.Ever
Tue Aug 14 13:38:16 UTC 2018


On Tuesday, 14 August 2018 at 09:12:30 UTC, ixid wrote:
> This will not compile as it says n is not known at compile time:
>
> auto fun(int n) {
> 	static foreach(i;0..n)
> 		mixin(i.to!string ~ ".writeln;");
> 	return;	
> }
>
> enum value = 2;
>
>
> void main() {
> 	fun(value);
> }
>
> But making it a template parameter fun(int n)() and fun!value 
> will obviously work as will replacing n in the foreach 
> statement with the enum 'value'. It seems like a missed 
> opportunity that the enum nature of 'value' does not propagate 
> through the function call letting the compiler know that 
> 'value'and therefore n are known at compile time.


You are not understand what is going on:

Your fun is a runtime function in n. That is, it cannot, under 
any circumstances, treat n as a compile time variable! n IS NOT a 
compile time variable... it doesn't matter how you use fun, it is 
irrelevant.

static foreach is a compile time loop(that is computed at compile 
time and the compiler can compute things about it(such as 
unrolling it).

CTFE means compile time FUNCTION EXECUTION!

Essentially it takes the runtime function and uses compiles it to 
a mini program that just contains that function, then the 
compiler calls the function with the *known* values. Since the 
function is known(defined) at compile time(obvious, since virtual 
all functions are in programming except self modifying functions, 
which are almost never used), the compiler can then evaluate the 
result at compile time and use that instead.


So, CTFE is no different than thinking about functions in a 
language without CTFE except you can treat them as also being 
sort of compile time functions when your inputs are known at 
compile time. CTFE functions will do what they behave just as if 
they did them at run time(although you can create different 
versions for compile time or run time execution if you need to 
have different behavior(doesn't change the conceptualization 
though)).

So, when I look at fun,

I see n is a run-time parameter. I then see a static foreach, 
which can only work over compile time(info known at compile time) 
using n... which states something is invalid.

The proper way:

auto fun(int n) {
  	[static] foreach(i;0..n)
  		[mixin(i.to!string ~ ".writeln;");]
  		i.to!string.writeln;
  	return;	
  }


and you might say! "Well, that is how I would do it at 
runtime!"(old school, so to speak).... YES!

CTFE is runtime! it is not compile time!

When you call

fun(3);

The compiler realizes the input, n, is actually known at compile 
time so it tries to do this "optimization"(which, basically, is 
all CTFE is)... and it will effectively compile fun and use it 
internally to figure out what fun(3) is.

If you want, you can think of the compiler magically rewriting 
fun as

auto funCTFE(int n)() {
	static foreach(i;0..n)
		mixin(i.to!string ~ ".writeln;");
	return;	
}


and then it calls not fun(3) but funCTFE(3).

or, we could see that the compiler "wraps" all functions as

auto funRT(int n) {
	foreach(i;0..n)
  		i.to!string.writeln; 		
  	return;	
  }

auto funCT(int n)() {
	static foreach(i;0..n)
		mixin(i.to!string ~ ".writeln;");
	return;	
}

auto fun(int n1)(int n2)
{
     if (__ctfe)
           return funCT!n1;
     else
           return funRT(n2);
}

and then it replaces the "fun(3)" with fun!3(3). I'm not saying 
this is what it does internally(it could, more or less) but this 
is what you think about it to understand it.

When I see a function, it doesn't phase me one bit if it is CTFE 
or not. CTFE really has nothing to do with meta programming and 
it is just an optimization(pre-computes the value of something at 
compile time if it can so it doesn't have to be done at runtime 
every time it is used). It just happens that CTFE and meta 
programming work well together and hence they are used together 
often.

What you are failing at is probably making that realization(CTFE 
is not meta programming!! One can just use them to do really 
effective meta programming because it allows one to think of meta 
functions as normal runtime functions).

Meta programming, OTH, is realizing that if a value is defined or 
will be defined at compile time then you can programming 
against(or rather with) that to create polymorphic(different) 
compile time behaviors.

Templates are simply "compile time" functions(which is not the 
same as compile time function execution!).

Runtime function - a function that has at least one input that 
can only be determined when the application is ran(and not at 
compile time). These functions cannot be used by the compiler 
since the input is not known. Of course, CTFE can use them 
because even though the inputs are not known, from the functions 
perspective, they are actually known to the program and so CTFE 
is used to evaluate them.

Compile time function s - a function who's inputs are all known 
at compile time. The compiler can then evaluate these functions 
completely.


Now, real functions are a combination of these two categories. 
Old school programming only used runtime functions. The new way 
is to use compile time(templates, meta programming, etc) and 
CTFE(evaluation of runtime/compile time functions that have known 
inputs at compile time... which, for compile time functions, is 
always the case).


It's really not complex, you just have to think of the compiler 
working on two levels.

As it compiles a function it can figure out if it is a template 
function or not determine if it can "optimize"(pre-compute) the 
function at compile time. If it can, it uses the pre-computed 
result resulting in a faster program(of course, slower 
compilation times). If it can't, it then checks if it can use 
CTFE on the function(are the inputs known), and if it can, it 
optimizes the result(as I've shown, CTFE is effectively meta 
programming but one doesn't want to think about that inside the 
function).

If all else fails it simply resorts to using the function as if 
it were purely run-time and does what all compilers have done in 
the past.

So,

1. When you are writing a D function and you know for a fact that 
it will only consist of run-time behavior(you don't want to 
precompute stuff, or have it know anything about compile time), 
then you just write your function as normal.

This applies to all runtime parameters. Even if you will use them 
in CTFE, you don't think about that while "inside" the function. 
CTFE will happen automatically by the compiler when it realizes 
it can carry out the CTFE process.

Remember, any CTFE function is a runtime function(although, with 
__ctfe, it complicates things a little, but not really).

2. If you are writing a compile time function, which is a 
function that will only do what it does for compile time 
purposes, then you can use meta programming(which is programming 
but using functions that also work with meta programming 
functionality(such as traits, static stuff, etc))... which is 
just programming(same logic but you know the inputs are known at 
compile time(or should be)).

There is a different "api" though so it looks a little bit 
different. Lots of usage of traits and compile time 
introspection/reflection/etc.


3. Actual D functions are a mixture of the two concepts above. 
Template parameters are "compile time"(known at compile time) 
while function arguments are known at runtime(even if they might 
be known at compile time = CTFE). A D function can have both.


Once you get the hang of it, it's actually pretty simple. You 
just have two "modes" of thought that overlap greatly but have 
slight differences(they may seem vastly different but they are 
not). As long as you do not confuse the two modes, you will be 
good. One might switch between the different modes constantly and 
quickly.

e.g.,

ReturnType!A foo(F, alias A, int c)(int x, string s)
{
     static if (is(F == string))
         return F;
     else
         foreach(k; 0..x)
         if (A(k) == 0) return s;
     static foreach(i; 0..c)
         writeln(s);
     return "hey!";
}


So, ReturnType is a meta-programming concept. It's pretty 
obvious, it takes a function and returns it's return type(but it 
is a meta type, a "concept").

If the return type of A is an string then it gives int, if it is 
string, it gives string, etc. foo then has 3 template parameters 
and 2 runtime parameters.

The static if is a compile time if(that is done while the 
compiler is figuring out stuff). It checks a compile time boolean 
and if it is true(which, because all compile time inputs are 
known it can do this) it evaluates one branch, else the other.

The foreach is a runtime concept. It's inputs are assumed to only 
be known at runtime.

Now, add on top of that the CTFE side, which sort of turns 
runtime parameters in to compile time, and you hae a pretty 
powerful system.

The above was not necessarily showing correct or valid meta 
programming but that one has to think of several levels at the 
same time. It is not hard. One just has to keep track of all the 
inputs categories(are they RT or CT). Usually one the lines are 
clearly defined(and they actually must be for the compiler to 
work).


So, it's not hard, just get practicing and write some actual 
code. Note that to use this stuff it is very helpful to actually 
know what you want to achieve rather than just blinding search 
for things that work. e.g., suppose you wanted to write a compile 
time math library! The idea is that all functions can precompute 
their values at compile time(the inputs have to be known of 
course):

e.g.,

sinCT(3.42), tanCT(-42.555534), etc.

Well, guess what! You can just write a runtime library and 
because of CTFE, the above will work and be precomputed at 
compile time!

That's quite powerful! The bonus is that you have a runtime and 
compile time math library!

Hence, CTFE alleviates a lot of the trouble of having to versions 
of functions that are essentially identical. It's a very powerful 
optimization.


If you wanted to write, say, some type of parser that would take 
code at compile time and generate valid D code that then the 
compiler compiles, you would be doing CTFE and meta programming. 
D can read files at compile time and it can also create code at 
compile time that creates code that creates code at compile time!

One day everyone will know D! But by then it will be called 
something else!











More information about the Digitalmars-d-learn mailing list