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