constexpr Functions

Contents[Show]

constexpr functions are functions that can be executed at compile time. Sounds not so thrilling. But it is. Trust me. You can perform with constexpr functions a lot of calculations at compile time. Therefore, the result of the calculation is at runtime as a constant in ROM available. In addition, constexpr functions are implicit inline.

 

The syntax of constexpr functions was massively improved with the change from C++11 to C++14. The positive list form C++11 is with C++14 a negative list. In C++11 you had to keep in mind which feature you can use in a constexpr functions. With C++14 you only have to keep in mind which feature you can't use in a constexpr function. 

C++11

For constexpr functions there a few restrictions:

The function

The restriction goes on with the function body. The key points are that

  • it's has to be defined with the keyword default or delete or
  • can only have one return statement.

Is it possible to define a meaningful function with such restrictions? Because constexpr functions can not have a conditional like if or a loop. Yes. Have a look at the constexpr functions in the post Constant expressions with constexpr. Only the functions getAverageDistance requires the C++14 standard. 

Fortunately, we have the ternary operator and recursion in C++. Therefore, I can implement the gcd algorithm as a constexpr function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// constexpr11.cpp

#include <iostream>

constexpr int gcd(int a, int b){
  return (b== 0) ? a : gcd(b, a % b);
}

int main(){
  
  std::cout << std::endl;
  
  constexpr int i= gcd(11, 121);
  
  int a= 11;
  int b= 121;
  int j= gcd(a,b);

  std::cout << "gcd(11,121): " << i << std::endl;
  std::cout << "gcd(a,b): " << j << std::endl;
  
  std::cout << std::endl;
 
}

 

I implemented in line 6 the Euclidean algorithm with the help of the ternary operator and recursion. Comprehensible code looks different. Of course, I can invoke the gcd function with arguments that are non-constant expressions (line 15 and 16). Therefore, the result will be calculated at runtime and can only be taken by a non-constant expression (line 17). 

The result is a little bit boring. But wait a second.

 constexpr11

A glimpse on the assembler instructions is quite enlightening. 

 constexpr11Objectdump

The call of the constexpr function in line 13 causes that the result of gcd(11, 121) is available in the object file. This is in opposite to the function call in line 17. At first, the processor has to push the variables on the stack (Instructions 400939 - 400941 in the object dump). At second, the processor has to invoke (callq) the function and store the result in the variable j (400948). What a difference!

Of course, you can define more powerful constexpr functions by calling ternary operators from ternary operators and so on. This technique is Turing complete. So you can compute all, what is computable. Fortunately, you have not to do that. You can use the C++14 constexpr functions, which behave almost like ordinary functions.  

C++14

constexpr can have

  • conditional jump instructions or loop instructions.
  • more than one instruction.
  • constexpr functions.
  • fundamental data types that have to be initialized with a constant expression.

The difference of an ordinary functions to constexpr functions in C++14 is minimal. constexpr functions can not have static or thread_local data. Either can they have a try block nor a goto instruction. Therefore, it's quite easy to implement the gcd algorithm in C++14 as a constexpr function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// constexpr14.cpp

#include <iostream>

constexpr auto gcd(int a, int b){
  while (b != 0){
    auto t= b;
    b= a % b;
    a= t;
  }
  return a;
}

int main(){
  
 std::cout << std::endl;
  
  constexpr int i= gcd(11,121);
  
  int a= 11;
  int b= 121;
  int j= gcd(a,b);

  std::cout << "gcd(11,121): " << i << std::endl;
  std::cout << "gcd(a,b): " << j << std::endl;
  
  std::cout << std::endl;
 
}

 

I skip the output of the program because it's identical to the output of the C++11 variant.

Thanks to a discussion with Odin Holmes in the facebook group of this blog Modernes C++ I want to present a very interesting use case for constexpr functions.

Pure functions

You can execute constexpr functions at runtime. If you take the return value of a constexpr function by a constant expression, the compiler will perform the function at compile time. The question is. Is there a reason to perform a constexpr function at runtime? Of course, you have the constexpr function so can use it at runtime. But there is a much more convincing reason.

A constexpr function can be potential performed at compile time. There is no state at compile time. At compile time we are in a pure functional sub language of the imperative programming language C++. In particular, that means that at compile time executed functions have to be pure functions. When you use this constexpr function at runtime the function keeps pure. Pure functions are functions that return always the same result when given the same arguments. Pure functions are like infinitely large tables from which you get your value. The guarantee that an expression returns always the same result when given the same arguments is called referential transparency.

Pure functions have a lot of advantages:

  • The function call can be replaced by the result.
  • The execution of pure functions can automatically distributed to other threads.
  • Function call can be reordered.
  • They can easily be refactored.

The last three points hold because pure functions have no state and have therefore no dependency to the environment. Pure functions are often called mathematical functions.

There are a lot of good reasons to use constexpr functions. The table shows the key points of pure and impure functions.

PureVersusImpure

I will write in a few weeks about functional programming in C++. Then I will explain the details of pure functions and pure functional programming.

Pure and more pure

I want to stress one point. constexpr functions are not per se pure. Thanks to Marcel Wid, who made me aware of this. constexpr functions are a lot more pure than ordinary functions. E.g. you can only invoke a constexpr function in a constexpr function. Who wants only to use pure functions should study Haskell. The very well written introduction to Haskell  Learn You a Haskell For Great Good from Miran Lipovaca is online available.

What's next?

In the post More and more save I successively optimized the gcd algorithm. Thanks to the type-traits library and static_assert this was quite impressing. But that was not the whole story about the type-traits. By analysing the type system of your code you can write self optimizing programs. How? Read the next post.

 

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

Tags: constexpr

Add comment


Support my blog by buying my E-book

Latest comments

Modernes C++

Subscribe to the newsletter

Including two chapters of my e-book
Introduction and Multithreading

Blog archive

Source Code

Visitors

Today 2115

All 228129

Currently are 115 guests and no members online