C++ constexpr Function | 'Compile-Time Function' Guide

C++ constexpr Function | 'Compile-Time Function' Guide

이 글의 핵심

constexpr int square(int x) { return x * x; }.

What is constexpr Function?

Function usable at both compile-time and runtime

Below is an implementation example using C++. Understand the role of each part while examining the code.

constexpr int square(int x) {
    return x * x;
}

int main() {
    // Compile-time
    constexpr int a = square(5);  // 25 (calculated at compile time)
    
    // Runtime
    int x = 10;
    int b = square(x);  // Runtime calculation
}

C++11 vs C++14 vs C++17

Here is detailed implementation code using C++. Process data with loops and perform branching with conditionals. Understand the role of each part while examining the code.

// C++11: Single return statement only
constexpr int factorial11(int n) {
    return n <= 1 ? 1 : n * factorial11(n - 1);
}

// C++14: Like regular functions
constexpr int factorial14(int n) {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

// C++17: if constexpr added
template<typename T>
constexpr auto process(T value) {
    if constexpr (std::is_integral_v<T>) {
        return value * 2;
    } else {
        return value;
    }
}

Basic Usage

Below is an implementation example using C++. Understand the role of each part while examining the code.

// Simple calculation
constexpr int add(int a, int b) {
    return a + b;
}

constexpr int multiply(int a, int b) {
    return a * b;
}

// Use as array size
constexpr int SIZE = add(10, 20);
int arr[SIZE];  // Compile-time size

Practical Examples

Example 1: Math Functions

Here is detailed implementation code using C++. Process data with loops and perform branching with conditionals. Understand the role of each part while examining the code.

constexpr int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

constexpr int power(int base, int exp) {
    int result = 1;
    for (int i = 0; i < exp; i++) {
        result *= base;
    }
    return result;
}

constexpr bool isPrime(int n) {
    if (n <= 1) return false;
    if (n == 2) return true;
    if (n % 2 == 0) return false;
    
    for (int i = 3; i * i <= n; i += 2) {
        if (n % i == 0) return false;
    }
    return true;
}

int main() {
    constexpr int fib10 = fibonacci(10);  // 55 (compile-time)
    constexpr int pow = power(2, 10);     // 1024
    constexpr bool prime = isPrime(17);   // true
    
    std::cout << fib10 << std::endl;
    std::cout << pow << std::endl;
    std::cout << prime << std::endl;
}

Example 2: String Processing

Here is detailed implementation code using C++. Process data with loops and perform branching with conditionals. Understand the role of each part while examining the code.

constexpr size_t strlen_constexpr(const char* str) {
    size_t len = 0;
    while (str[len] != '\0') {
        len++;
    }
    return len;
}

constexpr bool startsWith(const char* str, const char* prefix) {
    while (*prefix != '\0') {
        if (*str != *prefix) {
            return false;
        }
        str++;
        prefix++;
    }
    return true;
}

int main() {
    constexpr size_t len = strlen_constexpr("Hello");  // 5
    constexpr bool starts = startsWith("Hello World", "Hello");  // true
    
    char buffer[len + 1];  // Compile-time size
}

Example 3: Hash Function

Here is detailed implementation code using C++. Process data with loops and perform branching with conditionals. Understand the role of each part while examining the code.

constexpr unsigned int hash(const char* str) {
    unsigned int hash = 5381;
    while (*str) {
        hash = ((hash << 5) + hash) + *str;
        str++;
    }
    return hash;
}

int main() {
    // Compile-time hash
    constexpr unsigned int h1 = hash("apple");
    constexpr unsigned int h2 = hash("banana");
    
    // Use in switch statement
    const char* fruit = "apple";
    switch (hash(fruit)) {
        case hash("apple"):
            std::cout << "Apple" << std::endl;
            break;
        case hash("banana"):
            std::cout << "Banana" << std::endl;
            break;
    }
}

Example 4: Array Initialization

Here is detailed implementation code using C++. Process data with loops. Understand the role of each part while examining the code.

constexpr int generateValue(int index) {
    return index * index;
}

template<size_t N>
constexpr std::array<int, N> generateArray() {
    std::array<int, N> arr{};
    for (size_t i = 0; i < N; i++) {
        arr[i] = generateValue(i);
    }
    return arr;
}

int main() {
    constexpr auto arr = generateArray<10>();
    // arr = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
    
    for (int x : arr) {
        std::cout << x << " ";
    }
}

constexpr Classes

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

class Point {
private:
    int x, y;
    
public:
    constexpr Point(int x, int y) : x(x), y(y) {}
    
    constexpr int getX() const { return x; }
    constexpr int getY() const { return y; }
    
    constexpr int distanceSquared() const {
        return x * x + y * y;
    }
};

int main() {
    constexpr Point p(3, 4);
    constexpr int dist = p.distanceSquared();  // 25
    
    int arr[dist];  // Compile-time size
}

Common Issues

Issue 1: Calling Non-constexpr Function

Here is detailed implementation code using C++. Understand the role of each part while examining the code.

int nonConstexpr(int x) {
    return x * 2;
}

// ❌ Call non-constexpr function
constexpr int func(int x) {
    return nonConstexpr(x);  // Error
}

// ✅ Call only constexpr functions
constexpr int constexprFunc(int x) {
    return x * 2;
}

constexpr int func(int x) {
    return constexprFunc(x);  // OK
}

Issue 2: Static Variables

Below is an implementation example using C++. Understand the role of each part while examining the code.

// ❌ Static variable (C++11)
constexpr int func() {
    static int count = 0;  // Error
    return count++;
}

// ✅ Allowed from C++14
constexpr int func14() {
    static int count = 0;  // OK (C++14)
    return count++;
}

Issue 3: Virtual Functions

Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.

class Base {
public:
    // ❌ constexpr virtual function (before C++17)
    virtual constexpr int getValue() const {
        return 42;
    }
};

// ✅ C++20: constexpr virtual allowed
class Base20 {
public:
    virtual constexpr int getValue() const {
        return 42;
    }
};

Summary

Key Points

  1. constexpr function: Usable at compile-time and runtime
  2. C++11: Single return statement only
  3. C++14: Loops and variables allowed
  4. C++17: if constexpr added
  5. C++20: Virtual functions allowed

When to Use

Use constexpr when:

  • Need compile-time constants
  • Want to optimize performance
  • Array size calculation
  • Template metaprogramming

Don’t use when:

  • Function has side effects
  • Calls non-constexpr functions
  • Too complex (readability matters)

Best Practices

  • ✅ Use constexpr for pure functions
  • ✅ Combine with templates
  • ✅ Use for compile-time optimization
  • ❌ Don’t overuse (readability first)
  • ❌ Don’t use for I/O operations

Master compile-time programming with constexpr! 🚀