www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Enum and CTFE function call

reply ixid <nuaccount gmail.com> writes:
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
next sibling parent reply ixid <nuaccount gmail.com> writes:
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
next sibling parent reply Michael <michael toohuman.io> writes:
On Tuesday, 14 August 2018 at 09:17:41 UTC, ixid wrote:
 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.
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.
Aug 14 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
parent Michael <michael toohuman.io> writes:
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:
 [...]
CTFE is triggered when a value must be known at compile-time. So, if you have something like [...]
That is much clearer now, thanks for clarifying.
Aug 14 2018
prev sibling parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 14 August 2018 at 09:17:41 UTC, ixid wrote:
 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.
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 -- Simen
Aug 14 2018
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
prev sibling parent reply Everlast <Everlast For.Ever> writes:
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
parent reply ixid <adamsibson gmail.com> writes:
On Tuesday, 14 August 2018 at 13:38:16 UTC, Everlast wrote:
 etc
Thanks 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
parent Everlast <Everlast For.Ever> writes:
On Tuesday, 14 August 2018 at 16:54:09 UTC, ixid wrote:
 On Tuesday, 14 August 2018 at 13:38:16 UTC, Everlast wrote:
 etc
Thanks all for the comprehensive responses. I was not clearly separating CTFE and the compilation of the function in thinking about it. Much clearer now.
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?).
Aug 14 2018