constexpr

constexpr Functions

Today, I continue my story about programming at compile time. After template metaprogramming, the type-traits library, today’s topic is constexpr functions in particular.

 

constexpr

You may wonder why I am writing an additional post about constexpr. I have already written a few posts about constexpr in the last few years. Here is my motivation. First, I will show exciting similarities of constexpr functions and templates. Second, I want to write about the improved power of constexpr in C++20. And finally, I also discuss consteval in C++20. When some theory is not detailed enough in my posts, I will refer to previous posts. Let’s start with a short recap before I dive into the new topics.

A Short Recap

constexpr allows you to program at compile time with the typical C++-Syntax. Constant expressions with constexpr can have three forms.

Variables

    • are implicit const.
    • have to be initialized by a constant expression.
           constexpr double pi = 3.14;

Functions

constexpr functions in C++14 are pretty comfortable. They can

  • invoke other constexpr functions.
  • can have variables that have to be initialized by a constant expression.
  • can have conditional expressions or loops.
  • are implicit inline.
  • cannot have static or thread_local data.

User-defined types

  • have to have a constructor, which is a constant expression.
  • cannot have virtual functions.
  • cannot have a virtual base class.

The rules for constexpr functions or methods are pretty simple. In short, I call both functions.

constexpr functions can only depend on functionality which is a constant expression. Being a constexpr function does not mean that the function is executed at compile time. It says that the function has the potential to run at compile time. A constexpr function can also run a run time. It’s often a question of the compiler and the optimization level if a constexpr function runs at compile time or runtime. There are two contexts in which a constexpr function func has to run at compile time.

  1. The constexpr function is executed in a context evaluated at compile time. This can be a static_assert expression, such as with the type-traits library or the initialization of a C-array.
  2. The value of a constexpr function is requested with constexpr: constexpr auto res = func(5);

Here is a small example of the theory. The program constexpr14.cpp calculates the greates common divisor of two numbers.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    // 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 << '\n';
      
      constexpr int i= gcd(11, 121);     // (1)
      
      int a= 11;
      int b= 121;
      int j= gcd(a, b);                  // (2)
    
      std::cout << "gcd(11,121): " << i << '\n';
      std::cout << "gcd(a,b): " << j << '\n';
      
      std::cout << '\n';
     
    }
    

     

    Line (1) calculates the result i at compile time, and line (2) j at run time. The compiler would complain when I declare j as constexpr: constexpr int j = gcd(a, b). The problem would be that int’s a and b are not constant expressions.

    The output of the program should not surprise you.

     constexpr14

    The surprise may start now. Let me show you the magic with the Compiler Explorer.

    godbolt

     

    Line (1) in the program constexpr14.cpp boils down to the constant 11 in the following expression: mov DWORD PTR[rbp-4], 11 (line 33 in the screenshot). In contrast, line (2) is a function call: call gcd(int, int) (line 41 in the screenshot).

    After this recap, let me continue with the similarities of constexpr functions and templates metaprogramming.

    Template Metaprogramming

    constexpr functions have a lot in common with template metaprogramming. If you are not familiar with template metaprogramming, my following three previous posts should give you an idea.

    Here is the big picture comparing constexpr functions with template metaprogramming:

    TemplateMetaprogrammingVersusConstexprOldI want to add a few remarks to my table.

    • A template metaprogram runs at compile, but a constexpr function can run at compile time or runtime.
    • Arguments of a template metaprogram can be types, non-types such as int, or templates.
    • There is no state at compile time and, therefore, no modification. This means template metaprogramming is programming in a pure functional style. Here are the characteristics from the functional style perspective:
      • In template metaprogramming, you return a new value each time instead of modifying a value.
      • Controlling a for a loop by incrementing a variable such as i is not possible at compile-time: for (int i; i <= 10; ++i). Template metaprogramming, therefore, replaces loops with recursion.
      • In template metaprogramming, conditional execution is replaced by template specialization.

    Admittedly, this comparison was relatively concise. A pictural comparison of a metafunction (see Template Metaprogramming – How it Works) and a constexpr function should answer the open questions. Both functions calculate the factorial of a number.

    • The function arguments of a constexpr function correspond to template arguments of a metafunction.

     

    comparison1

     

    •  A constexpr function can have variables and modify them. A metafunction generates a new value.

    comparison2

    •  A metafunction uses recursion to simulate a loop.

    comparison3

    • Instead of an end condition, a metafunction uses a full specialization of a template to end a loop. Additionally, a metafunction uses partial or full specialization to perform conditional execution, such as if statements.

    comparison4

    •  Instead of an updated value res, the metafunction generates in each iteration a new value. comparison5

     

     

    • A metafunction has no return statement. It uses the value as a return value.

    comparison6

     constexpr functions and templates have more in common.

    Template Instantiation

    Once more, when you want to know the details about template instantiation, read my previous post, “Template Instantiation“.  Let me only emphasize the crucial facts.

    A template such as isSmaller is two times syntactically checked:

    template<typename T>
    bool isSmaller(T fir, T sec){
        return fir < sec;
    }
    
    isSmaller(5, 10);       // (1)
    
    std::unordered_set<int> set1;
    std::unordered_set<int> set2;
    isSmaller(set1, set2);  // (2)
    

     

    • First, the syntax of the template definition is checked. This check is not required but allowed and typically done by compilers.
    • Second, the compiler deduces the template arguments from the function arguments. It creates in this process for each template argument a concrete function and checks its syntax. This instantiation process fails in the case of std::unordered_set<int> (2) because the data type does not support the < operator.

    constexpr functions are also two times checked for syntax.

    constexpr auto gcd(int a, int b){
      while (b != 0){
        auto t= b;
        b= a % b;
        a= t;
      }
      return a;
    }
    
    
    constexpr int i= gcd(11, 121);     // (1)
      
    int a= 11;
    int b= 121;
    constexpr int j= gcd(a, b); // (2)

     

    • First, the compiler checks if the function gcd can run at compile time. This means, essentially, that all dependencies of a constexpr function, such as the invoked function, must be constexpr.
    • The compiler has to check for each invocation of gcd that the arguments are constant expressions. Consequentially, the first call (1) is valid, but not the second on (2).

    In the end, templates and constexpr functions are also quite similar regarding the visibility of their definition.

    Visibility

    When you instantiate a template, its definition must be visible. The same holds for constexpr function. When you invoke a constexpr function, its definition must be visible.

    What's Next?

    In the next post, I will write about constexpr functions in C++20 and the C++20 keyword consteval.

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

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