Calculation at compile-time by help of constant expression
Some time ago I have posted article about calculation at compile-time by help of templates. I must say that such method is huge, but helpful. Everything what I said is based on C++98 standard, but I think you have already heard about C++11 standard. At least I have mentioned it a couple of times in my previous articles.
This standard has some good stuff for you which helps to write compile-performed code without templates. It has name constexpr and is a shortening of constant expression.
Today I will use this one from C++11 and write similar code as before, but much smaller. Yep, I will re-implement calculation of my mathematics series at compile-time in a more elegant way. Let's see.
New keyword constexpr
or mark for compiler
As in previous time I'm beginning with an example of factorial calculation. Now it resembles a regular recursive implementation of factorial. Take a look at the following code:
Last time it took approximately 18 lines. Currently you see 4 lines (yes, I counted brackets) and it has only one new thing for you: a constexpr keyword. This keyword is a hint for compiler that result of this function can be evaluated at compile-time. It has some usage restrictions, but will'll discuss them later. Let's see how we can use it at first.
Probably some of you will write the following code:
and will expect assignment of result of calculation a factorial to value variable at compile-time. I want to look under the hood into an assembler code of such function. Don't worry it won't hurt you. I will add some comments for better understanding. My compiler generates the following assembler instructions for SomeFunc:
Wait a minute... We were expecting that everything would be calculated at compile-time and to see just the result generated assembly, but we see that calculation will be performed at run-time. If you try to debug this code, you will be able to enter in FactorialExpr() function and see calculation. Do you think I lied when said about calculation at compile-time?
Of course no. Check out the correct code:
and assembly code now looks like the following:
Do you see this? There is no call of FactorialExpr function. Here is just putting ready result to the variable. Really factorial of 4 is 24, which we see in the assembly code. We got this when we added constexpr keyword for the variable.
So at the moment we know that constexpr can be used for functions and variables and they interact. It is time to say about rules of this keyword.
Rules of constexpr
Let's begin with the simplest - using constexpr for variables. It has the following rules:
the variable must be immediately constructed or assigned a value;
the constructor parameters or the value to be assigned must contain constexpr variables;
the constructor parameters or the value to be assigned must contain constexpr functions;
the constructor used to construct the object must satisfy the requirements of constexpr constructor (I will write about this further).
Before describing of constexpr functions I need to give definition of literal type. It can be:
scalar type (bool, short, int, double, pointers, etc.);
an array of literal type;
class type that has all of the following properties:
has a trivial destructor (destructor that performs no action);
is either an aggregate type or a type with at least one constexpr constructor that is not a copy or move constructor;
all non-static data members and base classes are of non-volatile literal types.
OK, now you are ready to see rules for functions\methods with constexpr keyword:
it must not be a virtual method;
its return type must be a literal type;
each of its parameters must be a literal type;
the function body can contain:
typedef declarations and alias declarations that do not define classes or enumerations;
using declarations or directives;
exactly one return statement that contains only literal values, constexpr variables and functions.
There is harder story with constexpr constructors. Constructor of class can be declared with constexpr like function as well, but with following additional rules:
body of the constructor can have only: null statements, static_assert declarations, typedef declarations, using declarations and directives;
every base class and every non-static member must be initialized, either in the constructor's initialization list or in place of declaration.
As you see constexpr can be used for simple situations needed for evaluating code at compile-time. C++14 standard expands possible actions with constexpr, but for me it is enough to rewrite calculation of mathematics series from my previous article with C++11.
Re-implementing calculation of mathematics series
We already have implementation of factorial, now it is turn of exponentiation. As with factorial thanks to constexpr it is very easy:
As in previously I have written a wrapper for hiding start state of variable that keeps serial multiplying base on its value "degree" times. I don't think that there is something to review more, because there is elementary realization by recursive calls of PowerExprImpl function. Now we can write code for calculation of single series:
There is one unusual thing – the first argument of the function. It is a pointer to function and it can be used in constexpr function because pointer is a scalar type. If you don't know syntax of such pointers in C++, you can read about this here.
Now we have everything what we need to finish implementation of the calculation. Get into this:
You see? We get same results as we did it with help of templates in previous article[link here], but this time our implementation is more understandable for programmer. Thanks to new constexpr keyword from C++11 standard.
Calculation at compile-time by usage of templates is great, but in such case programmer writes code which is hard to understand for other programmers. Such implementation can be huge and bulky. C++11 standard has pretty cool instrument for avoiding this. It has name constexpr, which means constant expression. In this article I have described rules of usage this keyword and abused my previous implementation for calculation of mathematics series by writing small and clear code with constexpr. So if your compiler supports C++11, it is an awesome practice to use new constexpr keyword with the Force.
As usual examples of code from the article you can find in my Github repository.
My previous articles from “.CPP files” series are: