C++ constexpr functions

I’ve been very excited to see that Visual Studio 2015 supports the constexpr keyword in C++. It was introduced in the C++11 standard, and is being taken further in upcoming revisions.

There are a number of uses for the keyword, but the one which excites me the most is using it for writing functions which can be executed during compilation, potentially saving a lot of runtime overhead. In this post, I’ll show a couple of quick examples.

Maths constants

What do you do when your program needs to use a maths constant, such as pi? One traditional approach is to use a macro like this:

#define PI 3.141592653589793

One of the major problems with a macro is that it isn’t type-safe. There are various ways to solve this, but here’s an approach which uses a constexpr function:

template <typename T_ty>
constexpr T_ty pi()
{
    return T_ty(3.141592653589793);
}

To use this, you’d simply call the pi function with your desired type as the template parameter. For example:

float twoPi = 2.0f * pi<float>();

The nice thing about this is that there’s no runtime overhead from a function call or type conversion. The pi() call is performed during compilation and the expression is effectively replaced by a literal value (i.e. the result of the call).

Adding function parameters

A common maths utility with games programming is converting angles between degrees and radians. We can write a constexpr function to do that too:

template <typename T_ty>
constexpr T_ty degToRad(T_ty angle)
{
    return angle * (pi<T_ty>() / T_ty(180));
}

To use this function, you’d call it something like this:

float angle = degToRad(47.0f);

The compiler can infer the return type from the parameter you pass in, which is why the explicit template parameter isn’t necessary.

Runtime vs. compile time

In the degToRad() example above, it can only be executed at compile time if it’s passed a literal (or other constexpr) value, i.e. something whose value is absolutely fixed at compile-time.

But what happens if you pass a run-time variable to the function? For example:

float deg = 0.0f;
cin >> deg;
float rad = degToRad( deg );

This takes the input angle from stdin at runtime, so there’s absolutely no way its value can be known at compile time. In this case, the compiler automatically invokes the exact same function at runtime instead.

This is a very handy feature of constexpr functions: you only need to write it once, but you get compile-time and runtime versions to play with. The compiler automatically figures out how to call it.

Recursion

Notice that our degToRad() function above is calling our earlier pi() function. The ability to do this means you can offload some surprisingly intensive calculations to the compiler. Obviously this could slow down your build times, but will potentially improve execution times considerably.

A good example of this is a recursive function call. Here’s an example which will calculate pi to any positive integer power, as long as the compiler doesn’t run out of memory:

template <typename T_ty>
constexpr T_ty piRaised(unsigned int power)
{
    return (power == 0) ?
        T_ty(1) : // < Terminal case (anything to 0th power is 1)
        pi<T_ty>() * piRaised<T_ty>(power - 1); // < Recursion
}

As long as it’s called with a literal (or constexpr) value, this function’s entire recursion will happen during compilation.

(In practice, I don’t encounter many situations where I actually need to raise pi to a power greater than 3, but hopefully you get the idea!)

Restrictions

C++11 has some fairly serious restrictions on what can go into a constexpr function. The executable code is limited to a single return statement, and nothing else. You can have static asserts and typedefs, but not much else. If you work with VS2015 you’ll be limited to this for the moment.

C++14 relaxes the restrictions somewhat, allowing multiple statements. I haven’t had an opportunity to try any compilers which support it yet, but it’s definitely something to keep an eye on. More information about the restrictions can be found here.

Conclusion

I’ve shown a couple of really simple examples of what you can do with constexpr functions. Bearing in mind that they can call each other, they really open up a whole world of helpful optimisations if used carefully. What’s more, it’s even possible to have constexpr member functions and constructors, so there’s lots more to explore here beyond what I’ve demonstrated.

It’s unfortunate that several simple standard library functions don’t seem to be getting converted to constexpr functions yet, at least from what I’ve seen. For example, there are many cases where a compile-time version of abs, min, or max would come in extremely handy. I’m sure it’ll happen eventually, and in the meantime they’re not difficult to reimplement in your own code temporarily should the need arise.

Leave a Reply

Your email address will not be published. Required fields are marked *