Compile-time function execution
In computing, compile-time function execution is the ability of a compiler, that would normally compile a function to machine code and execute it at run time, to execute the function at compile time. This is possible if the arguments to the function are known at compile time, and the function does not make any reference to or attempt to modify any global state.
If the value of only some of the arguments are known, the compiler may still be able to perform some level of compile-time function execution, possibly producing more optimized code than if no arguments were known.
Examples
Lisp
The Lisp macro system is an early example of the use of compile-time evaluation of user-defined functions in the same language.C++
The Metacode extension to C++ was an early experimental system to allow compile-time function evaluation and code injection as an improved syntax for C++ template metaprogramming.In earlier versions of C++, template metaprogramming is often used to compute values at compile time, such as:
template
struct Factorial ;
template <>
struct Factorial<0> ;
// Factorial<4>::value 24
// Factorial<0>::value 1
void foo
Using compile-time function evaluation, code used to compute the factorial would be similar to what one would write for run-time evaluation e.g. using C++11 constexpr.
import std;
constexpr int factorial
constexpr int f10 = factorial;
int main
In C++11, this technique is known as generalized constant expressions. C++14 relaxes the constraints on constexpr – allowing local declarations and use of conditionals and loops.
Here's an example of compile-time function evaluation in C++14:
// Iterative factorial at compile time.
constexpr int factorial
int main
Immediate functions (C++)
In C++20, immediate functions were introduced, and compile-time function execution was made more accessible and flexible with relaxedconstexpr restrictions.// Iterative factorial at compile time.
consteval int factorial
int main
Since function
Factorial is marked consteval, it is guaranteed to invoke at compile-time without being forced in another manifestly constant-evaluated context. Hence, the usage of immediate functions offers wide uses in metaprogramming, and compile-time checking.Here's an example of using immediate functions in compile-time function execution:
void assertionFailed
consteval void testAssert
consteval void test
int main
In this example, the compilation fails because the immediate function invoked function which is not usable in constant expressions. In other words, the compilation stops after failed assertion.
The typical compilation error message would display:
In function 'int main':
in 'constexpr' expansion of 'test'
in 'constexpr' expansion of 'testAssert'
error: call to non-'constexpr' function 'assertionFailed'
assertionFailed;
~~~~~~~~~~~~~~~^~
Here's another example of using immediate functions as constructors which enables compile-time argument checking:
import std;
using std::string_view;
void assertionFailed
struct CheckedMessage ;
void sendCalmMessage
int main
The compilation fails here with the message:
In function 'int main':
in 'constexpr' expansion of 'CheckedMessage)'
error: call to non-'constexpr' function 'void assertionFailed'
assertionFailed;
~~~~~~~~~~~~~~~^~
D
Here's an example of compile-time function evaluation in the D programming language:int factorial
// computed at compile time
enum y = factorial; // 1
enum x = factorial; // 24
This example specifies a valid D function called "factorial" which would typically be evaluated at run time. The use of
enum tells the compiler that the initializer for the variables must be computed at compile time. Note that the arguments to the function must be able to be resolved at compile time as well.CTFE can be used to populate data structures at compile-time in a simple way :
int genFactorials
enum factorials = genFactorials;
void main
// 'factorials' contains at compile-time:
//
CTFE can be used to generate strings which are then parsed and compiled as D code in D.
Zig
Here's an example of compile-time function evaluation in the Zig programming language:pub fn factorial usize
pub fn main void
This example specifies a valid Zig function called "factorial" which would typically be evaluated at run time. The use of
comptime tells the compiler that the initializer for the variables must be computed at compile time. Note that the arguments to the function must be able to be resolved at compile time as well.Zig also support Compile-Time Parameters.
pub fn factorial usize
pub fn main void
CTFE can be used to create generic data structures at compile-time:
fn List type
// The generic List data structure can be instantiated by passing in a type:
var buffer: i32 = undefined;
var list = List;