close
Jump to content

Compile-time function execution

From Wikipedia, the free encyclopedia

In computing, compile-time function execution (or compile-time function evaluation, or general constant expressions) 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 (i.e. it is a pure function).

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 (partial evaluation), possibly producing more optimized code than if no arguments were known.

Examples

[edit]

Lisp

[edit]

The Lisp macro system is an early example of the use of compile-time evaluation of user-defined functions in the same language.

C++

[edit]

The Metacode extension to C++ (Vandevoorde 2003)[1] was an early experimental system to allow compile-time function evaluation (CTFE) 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 the following. In C and C++, enums were historically used to create compile-time named integral constants (without preprocessor macros), before the addition of constexpr in C++11.

template <unsigned int N>
struct Factorial {
    enum { 
        VALUE = N * Factorial<N - 1>::VALUE 
    };
};

template <>
struct Factorial<0> {
    enum { 
        VALUE = 1 
    };
};

void foo() {
    int x = Factorial<0>::VALUE; // == 1
    int y = Factorial<4>::VALUE; // == 24
}

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 constexpr.

import std;

constexpr unsigned int factorial(unsigned int n) noexcept { 
    return n ? (n * factorial(n - 1)) : 1; 
}

constexpr unsigned int f10 = factorial(10);

int main() {
    std::println("{}", f10);
    return 0;
}

In C++11, this technique is known as generalized constant expressions (constexpr).[2] C++14 relaxes the constraints on constexpr, allowing local declarations and use of conditionals and loops (the general restriction that all data required for the execution be available at compile-time remains).

// Iterative factorial at compile time.
constexpr unsigned int factorial(unsigned int n) noexcept {
    unsigned int result = 1;
    while (n > 1) {
        result *= n--;
    }
    return result;
}

int main() {
    constexpr int f4 = factorial(4); // f4 == 24
}

In C++20, immediate functions were introduced with the consteval keyword, and compile-time function execution was made more accessible and flexible with relaxed constexpr restrictions.

// Iterative factorial at compile time.
consteval unsigned int factorial(unsigned int n) noexcept {
    unsigned int result = 1;
    while (n > 1) {
        result *= n--;
    }
    return result;
}

int main() {
    int f4 = factorial(4); // f4 == 24
}

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. For instance, this is used in std::format().[3]

void assertionFailed() {}

consteval void testAssert(bool cond) {
    if (!cond) {
        assertionFailed();
    }
}

consteval void test() {
    int x = 10;
    testAssert(x == 10); // ok
    x++;
    testAssert(x == 11); // ok
    x--;
    testAssert(x == 12); // fails here
}

int main() { 
    test(); 
}

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(x == 12)'
error: call to non-'constexpr' function 'assertionFailed()'
              assertionFailed();
              ~~~~~~~~~~~~~~~^~
  [ ... ]

This is an example of using immediate functions as constructors, for compile-time argument checking:

import std;

using std::string_view;

void assertionFailed() {}

struct CheckedMessage {
    string_view msg;

    consteval CheckedMessage(const char msg[]): 
        msg{msg} {
        if (msg.ends_with('!')) {
            assertionFailed();
        }
    }
};

void sendCalmMessage(const CheckedMessage& arg) {
    std::println("{}", arg.msg);
}

int main() {
    sendCalmMessage("Hello, world");
    sendCalmMessage("Hello, world!");
}

The compilation fails here with the message:

In function 'int main()':
  in 'constexpr' expansion of 'CheckedMessage("Hello, world!")'
error: call to non-'constexpr' function 'void assertionFailed()'
                    assertionFailed();
                    ~~~~~~~~~~~~~~~^~
 [ ... ]

D

[edit]

This is an example of compile-time function evaluation in the D programming language:[4]

uint factorial(uint n) {
    if (n == 0) {
        return 1;
    }
    return n * factorial(n - 1);
}

// computed at compile time
enum y = factorial(0); // == 1
enum x = factorial(4); // == 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.[5]

CTFE can be used to populate data structures at compile-time in a simple way (D version 2):

uint[] genFactorials(uint n) {
    uint[] result = new uint[n];
    result[0] = 1;
    foreach (i; 1 .. n) {
        result[i] = result[i - 1] * i;
    }
    return result;
}

enum factorials = genFactorials(13);

void main() {}

// 'factorials' contains at compile-time:
// [1, 1, 2, 6, 24, 120, 720, 5_040, 40_320, 362_880, 3_628_800,
//  39_916_800, 479_001_600]

CTFE can be used to generate strings which are then parsed and compiled as D code in D.

Rust

[edit]

This is an example of compile-time function evaluation in Rust. This is done using const fn.[6]

const fn add(a: i32, b: i32) -> i32 {
    a + b
}

const RESULT: i32 = add(10, 5);

fn main() {
    // The value 15 is already baked into the binary here
    println!("The result is: {}", RESULT);
}

Zig

[edit]

This is an example of compile-time function evaluation in Zig.[7]

pub fn factorial(n: usize) usize {
    var result = 1;
    for (1..(n + 1)) |i| {
        result *= i;
    }
    return result;
}

pub fn main() void {
    const x = comptime factorial(0); // == 0
    const y = comptime factorial(4); // == 24
}

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.[8]

pub fn factorial(comptime n: usize) usize {
    var result: usize = 1;
    for (1..(n + 1)) |i| {
        result *= i;
    }
    return result;
}

pub fn main() void {
    const x = factorial(0); // == 0
    const y = factorial(4); // == 24
}

CTFE can be used to create generic data structures at compile-time:

fn List(comptime T: type) type {
    return struct {
        items: []T,
        len: usize,
    };
}

// The generic List data structure can be instantiated by passing in a type:
var buffer: [10]i32 = undefined;
var list = List(i32){
    .items = &buffer,
    .len = 0,
};

References

[edit]
  1. ^ Daveed Vandevoorde, Edison Design Group (April 18, 2003). "Reflective Metaprogramming in C++" (PDF). Retrieved July 19, 2015.
  2. ^ Gabriel Dos Reis and Bjarne Stroustrup (March 2010). "General Constant Expressions for System Programming Languages. SAC-2010. The 25th ACM Symposium On Applied Computing" (PDF).
  3. ^ cppreference.com (10 May 2026). "std::format". cppreference.com. cppreference.com.
  4. ^ D 2.0 language specification: Functions
  5. ^ D 2.0 language specification: Attributes
  6. ^ The Rust Team (16 April 2026). "Constant evaluation". docs.rust-lang.org. The Rust Team.
  7. ^ Zig 0.11.0 Language Reference: Compile-Time Expressions
  8. ^ Zig 0.11.0 Language Reference: Compile-Time Parameters
[edit]