C++

C++ - Constness

constexpr

Posted by Rico's Nerd Cluster on February 1, 2023

constexpr in C++ allows expressions to be evaluated at compile time rather than runtime. This feature helps optimize programs by computing constant values during compilation and enables creating objects with immutable values that the compiler can use in constant expressions. Using constexpr with functions and variables encourages safer and more efficient code by catching errors early and reducing runtime overhead.

Compared to const:

  • const marks data as read-only after initialziation, but its value is not necessarily known during compile time.
  • constexpr provides a stronger compile-time guarantee.
    • constexpr variables are always const
    • constexpr functions and pointers doesn’t make everything const
  • Use constexpr in compile-time evaluations:
    • static_asserts
    • template parameters

constexpr Functions

A constexpr function is NOT a const function, nor does it imply that its parameters or local const are const. It merely guarantees that when provided with constexpr arguments, the function can be evaluated at compile time.

One example is: without constexpr function, static_assert would throw an error:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

// A constexpr function to compute factorial of n
int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

int main() {
    // Compile-time evaluation using static_assert, but this would throw an error
    static_assert(factorial(5) == 120, "factorial(5) should be 120");
    return 0;
}

With constexpr, static_assert is happy. The function can be evaluated during runtime too:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

// A constexpr function to compute factorial of n
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

int main() {
    // Compile-time evaluation using static_assert
    static_assert(factorial(5) == 120, "factorial(5) should be 120");
    // Runtime usage: even though the function is constexpr,
    // it can be used with values not known until runtime.
    int runtime_value = 6;
    int runtime_result = factorial(runtime_value);
    std::cout << "Factorial of " << runtime_value << " is " << runtime_result << "\n";
    return 0;
}

Constexpr Improvements [C++20]

constexpr rules in cpp20 are relaxed. Operations like allocation, resizing, element access on vectors are allowed, as long as those operations themselves meet the requirements for constexpr evaluation

While vectors still can’t be compiled on my C++20 compiler, one can use std::array

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <numeric>
#include <array>


int main() {

    std::cout << std::endl;

    constexpr std::array myArray{1, 2, 3, 4, 5};                                     // (1)
    constexpr auto sum = std::accumulate(myArray.begin(), myArray.end(), 0);         // (2)
}