Posts C++ notes on constexpr
Post
Cancel

C++ notes on constexpr

Intro to constexpr

Constant expressions allows us to evaluate expressions at compile time. Kind of like template meta-programming, except that it uses a familiar C++ syntax, so its easier to maintain. constexpr objects can not change at run time.

Why do we care about this? Whats useful about doing thins at compile time? Well there are quite a number of advantages such as:

  • No run time cost
    • No execution time
    • Minimal footprint
  • Errors found at compile or link time
  • No sync concerns

constexpr values

constexpr can be applied to a value or object definition:

1
2
constexpr int const_3 = 3;
constexpr char tile[] = "grout";

A constexpr value can be:

  • a floating point type
  • a character literal
  • a pointer literal
  • a literal object

This requires no storage declaration. A note is that we can not declare constexpr parameters. So where do we use these?

  • Non-type template parameters
  • Array dimensions
  • Enum initialization
  • Standard runtime code

Casting away const is undefined behavior.

constexpr computations

constexpr declarations are allows on free functions, member functions and constructors. Once we declare something as constexpr, the code we are allowed to put inside becomes more constrained, we can’t put just anything in a constexpr body (strict in C++11 and somewhat relaxed in C++14). constexpr constructors allow user-defined literal types.

constexpr code can run both at compile and run time:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
constexpr double half_of(double x) {
    return x / 2;
}

void foo() {
    // evaluates at compile time
    constexpr double half = half_of(1.0);
    static_assert((half < 0.51) && (half > 0.49), "Good");

    // evaluates at run time
    char c;
    std::cin >> c;
    const double run_half = half_of(c);
    assert(run == (c * 0.5));
}

To force constexpr evaluation to be during translation we can declare the object or value as constexpr and the other is to use it where a literal is required by the compiler:

1
int dummy_array[half_of(2.0)] {};

constexpr is part of the definition. The following will not compile:

1
2
3
4
5
int const_5(); // forward declaration

constexpr int const_5() { // error
    return 5;
}

Different definitions in different translation units violate One Definition Rule. Don’t do this. These declarations are implicitly inlined, the definition must be visible to the compiler before the first invocation.

constexpr and floating point

Compile-time floating point calculations might not have the same results as runtime calculations. Looking inside the implementation of a floating point number is not allowed.

constexpr in C++11

The constraints on a constexpr function in C++11 are:

  • Not virtual
  • Must return literal type or a reference to a literal type
  • Parameters must be literal types or references to literal types
  • Body is a compound statement
  • Unevaluated sub-expressions are ignored

So, just one statement, function calls are allowed, compound statements are allowed and ternary operations are allowed. So, we’ll use recursion!

This also points to the throw idiom for constexprerrors. If we throw from constexpr code then if it is evaluated during compilation we will get a compile error and if it is a legitimate throw, we will use it at run time.

A C++11 constexpr constructor has:

  • params are literals or references to literals
  • no function-try-block
  • an empty body
  • non static data member and base class sub-objects must be init-ed
  • all invoked constructors must be constexpr
  • Every assignment in initializer list must be a constant expression

So in C++11, constexpr is highly constrained.

constexpr in C++14

C++11 constexpr gives us rules for what we can do, in C++14 we are given rules on what we can’t do. C++14 says we can not use constexpr with :

  • most examinations of this
  • calling non-constexpr functions
  • operations with undefined behavior
  • lambda expressions
  • most l-value to r-value conversions
  • referencing uninitialized data
  • conversions from void* to object*
  • modifications of non-local objects

And quite a few other smaller rules.

This post is licensed under CC BY 4.0 by the author.

Recent Update

    Contents