digitalmars.D.learn - Enum and CTFE function call
- ixid (16/16) Aug 14 2018 This will not compile as it says n is not known at compile time:
- ixid (4/6) Aug 14 2018 This does work if 'value' is changed to immutable and fun to
- Michael (11/17) Aug 14 2018 From the examples on this page:
- Jonathan M Davis (24/25) Aug 14 2018 CTFE is triggered when a value must be known at compile-time. So, if you
- Michael (3/9) Aug 14 2018 That is much clearer now, thanks for clarifying.
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (18/24) Aug 14 2018 If 'value' is a global immutable, its value is known at
- Jonathan M Davis (19/35) Aug 14 2018 The fact that value is an enum is irrelevant. fun(value) is a call at
- Everlast (209/225) Aug 14 2018 You are not understand what is going on:
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.
Aug 14 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...This does work if 'value' is changed to immutable and fun to accept it. So it still seems like a missed opportunity as enum shouldn't be able to change either.
Aug 14 2018
On Tuesday, 14 August 2018 at 09:17:41 UTC, ixid wrote:On Tuesday, 14 August 2018 at 09:12:30 UTC, ixid wrote:From the examples on this page: https://tour.dlang.org/tour/en/gems/compile-time-function-evaluation-ctfe It looks to me like it might be a slight issue with the function being void, in that you need to explicitly ensure that n is immutable. For the example on the page above, they assign the result of the function to a static variable, and so the function is evaluated at compile-time, but without assigning the value to a static variable, it is evaluated at runtime. I guess in this case the compiler may just be being cautious? The page does state that enums should trigger CTFE though.This will not compile as it says n is not known at compile time...This does work if 'value' is changed to immutable and fun to accept it. So it still seems like a missed opportunity as enum shouldn't be able to change either.
Aug 14 2018
On Tuesday, August 14, 2018 4:03:11 AM MDT Michael via Digitalmars-d-learn wrote:The page does state that enums should trigger CTFE though.CTFE is triggered when a value must be known at compile-time. So, if you have something like enum a = foo(); foo gets called at compile-time, because an enum's value must be known at compile-time. So, in that sense, enums do trigger CTFE. However, using an enum does not necessarily require CTFE. e.g. void main() { enum a = 42; auto result = foo(a); } uses an enum, but the enum is used in a runtime context. foo's result is given to a local variable, which does not need to have its value known at compile-time, so foo is not called at compile-time. The fact that the argument to foo is known at compile-time is irrelevant. CTFE is all about evaluating functions at compile-time when the result is used in a context that must be known at compile-time. It isn't used in an attempt to optimize code or attempted just because it might work. It's used because a function is called in a context where the result is explicitly used at compile-time. The most common situations would be template arguments and initializing enums, static variables, and member variables. - Jonathan M Davis
Aug 14 2018
On Tuesday, 14 August 2018 at 11:25:06 UTC, Jonathan M Davis wrote:On Tuesday, August 14, 2018 4:03:11 AM MDT Michael via Digitalmars-d-learn wrote:That is much clearer now, thanks for clarifying.[...]CTFE is triggered when a value must be known at compile-time. So, if you have something like [...]
Aug 14 2018
On Tuesday, 14 August 2018 at 09:17:41 UTC, ixid wrote:On Tuesday, 14 August 2018 at 09:12:30 UTC, ixid wrote:If 'value' is a global immutable, its value is known at compile-time, and is equivalent to an enum except you can get its address. If it's passed in a compile-time-compatible way (generally, as a template parameter), it can thus be used as a compile-time constant. Now, as for your initial post: CTFE is run-time code, run at compile-time. In other words, for a function to be CTFE-able, it must be possible to run at run-time, and any compile-time arguments must be passed in a compile-time-compatible way. Your function 'fun' will not compile, because for it to run at run-time, 'n' will need to be known. H. S. Teoh has a great article on the wiki explaining the different stages of compilation in D, which I think would be a great help in your understanding of the differences: https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time -- SimenThis will not compile as it says n is not known at compile time...This does work if 'value' is changed to immutable and fun to accept it. So it still seems like a missed opportunity as enum shouldn't be able to change either.
Aug 14 2018
On Tuesday, August 14, 2018 3:12:30 AM MDT ixid via Digitalmars-d-learn
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.
The fact that value is an enum is irrelevant. fun(value) is a call at
runtime. CTFE is only triggered if the result must be known at compile time.
So, no matter what you pass to fun, fun is not going to be called at
compile-time unless the result is required at compile-time - e.g. if it's
used to initialize an enum, static variable, or member variable, or if it's
used as a template argument.
Inside of fun, n is used as in static foreach, which won't work regardless
of whether fun is called at compile-time or not, because n is a function
argument, not a template argument and is thus a "runtime" value even if the
functions is called at compile-time.
If you want n to be usable in the static foreach, then it cannot be a
function parameter. If it's an argument to the function, it must be a
template argument.
I suggest that you read this article:
https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time
It's a work in progress, but it should give you some insight into CTFE.
- Jonathan M Davis
Aug 14 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!
Aug 14 2018
On Tuesday, 14 August 2018 at 13:38:16 UTC, Everlast wrote:etcThanks all for the comprehensive responses. I was not clearly separating CTFE and the compilation of the function in thinking about it. Much clearer now.
Aug 14 2018
On Tuesday, 14 August 2018 at 16:54:09 UTC, ixid wrote:On Tuesday, 14 August 2018 at 13:38:16 UTC, Everlast wrote:Yeah, if you don't think's can get pretty confusing because the language really does not cleanly separate it... which is intention because they are virtually identical things but used slightly differently(technically RT and CT are not separate things, we could assume the OS is a compiler of some sorts). I will say that once you do some meta programming in D(say 10k lines of code, even if basically junk but in some higher context) it will just sorta of magically start to separate pretty quickly. Since they are so similar you really are not learning anything new if you already know how to program well(have a good conceptual basis of what programming is and how it works, say about ~5 years of decent programming experience). Just remember that it won't all suddenly be crystal clear, the more you program this stuff the more clear the divisions become(And everything D in general). Initially when I started messing with D the concepts were not obvious but once I recognized the power unadulterated power that they posses(which was quick since I love meta programming in general) and the ease of use(I just had to think in basically those 3 categories without changing much actually programming "logic") I was hooked. Of course, it's not perfect, but nothing out there like it that is like C++(which was my background). I'm not much in to Haskell, which is effectively even a more powerful logic device, but just unlike the rigor that is required to use it. (When I want to be risky I want to be risque!). D provides that balance, as a language, for me... all though it isn't a perfect marriage by any means(are any?).etcThanks all for the comprehensive responses. I was not clearly separating CTFE and the compilation of the function in thinking about it. Much clearer now.
Aug 14 2018









Michael <michael toohuman.io> 