Had D's CTFE pictured wrong for a while. I assumed that once a function is fit for compile time evaluation, the compiler would just do it wherever the arguments are known. It does it only where a compile time value is required.
import std.stdio;
int sumSquares(int n)
{
if (!__ctfe)
writeln("sumSquares ran at run time");
int total = 0;
foreach (i; 0 .. n)
total += i * i;
return total;
}
enum a = sumSquares(4); // interpreted by the compiler
immutable b = sumSquares(4); // module scope, also compile time
void main()
{
auto c = sumSquares(4); // ordinary call, prints the line
writeln(a, " ", b, " ", c);
}
This prints the run time line once, from c, then 14 14 14. The enum and the module level immutable both need their value during compilation, so sumSquares is interpreted there and __ctfe is true. The auto in main is an ordinary call, even though the same function with the same argument was just interpreted twice.
Now replace that auto with an enum during some tidy-up. Nothing at the call changes except one keyword, and the function has quietly taken on a new requirement, that the interpreter can run all of it. Is the declaration keyword really the only marker a reader gets?