TimelineCpp20

Two new Keywords in C++20: consteval and constinit

With C++20, we get two new keywords: consteval and constinit. consteval produces a function executed at compile-time, and constinit guarantees that a variable is initialized at compile-time.

 TimelineCpp20

When you read my previous short description of consteval and constinit you may have the impression that both specifiers are pretty similar to constexpr. To make it short, you are right. Before I compare the keywords consteval, constinit, constexpr, and good old const, I have to introduce the new specifiers consteval and constinit.

consteval

consteval int sqr(int n) {
    return n * n;
}

 

consteval creates a so-called immediate function. Each invocation of an immediate function creates a compile-time constant. To say it more directly. A consteval (immediate) function is executed at compile-time.

consteval cannot be applied to destructors or functions which allocate or deallocate. You can only use at most one of consteval, constexpr, or constinit specifiers in a declaration. An immediate function (consteval) is implicit inline and must fulfill a constexpr function’s requirements.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    The requirements for a constexpr function in C++14 and, therefore, a consteval function is:

    A constexpr function can

    • have conditional jump instructions or loop instructions.
    • have more than one instruction.
    • invoke constexp functions. A consteval function can only invoke a constexpr function but not the other way around.
    • have fundamental data types that have to be initialized with a constant expression.

    constexpr functions can not have static or thread_local data. Neither can they have a try block nor a goto instruction.

    The program constevalSqr.cpp applies the consteval function sqr.

    // constevalSqr.cpp
    
    #include <iostream>
    
    consteval int sqr(int n) {
        return n * n;
    }
    
    int main() {
        
        std::cout << "sqr(5): " << sqr(5) << std::endl;     // (1)
        
        const int a = 5;                                    // (2)
        std::cout << "sqr(a): " << sqr(a) << std::endl;     
    
        int b = 5;                                          // (3)
        // std::cout << "sqr(b): " << sqr(b) << std::endl; ERROR
    
    }
    

     

    5 is a constant expression and can be used as an argument for the function sqr (1).

    The same holds for variable a (2). A constant variable such as a is usable in a constant expression when it is initialized with a constant expression.

    b (3) is not a constant expression. Consequently, the invocation of sqr(b) is not valid.

    Thanks to the brand-new GCC11 and the Compiler Explorer, here is the program’s output.

    constevalSqr

    constinit

    constinit can be applied to variables with static or thread storage duration.

    • Global (namespace) variables, static variables, or static class members have static storage duration. These objects are allocated when the program starts and deallocated when the program ends.
    • thread_local variables have thread storage duration. Thread local data is created for each thread that uses this data. thread_local data exclusively belongs to the thread. They are created at their first usage, and its lifetime is bound to the lifetime of the thread it belongs to. Often thread-local data is called thread-local storage.

    constinit ensures that this kind of variable (static storage duration or thread storage duration) is initialized at compile-time.

     

    // constinitSqr.cpp
    
    #include <iostream>
    
    consteval int sqr(int n) {
        return n * n;
    }
    
        constexpr auto res1 = sqr(5);                  
        constinit auto res2 = sqr(5);                 
    
    int main() {
    
        std::cout << "sqr(5): " << res1 << std::endl;
        std::cout << "sqr(5): " << res2 << std::endl;
       
        constinit thread_local auto res3 = sqr(5);     
        std::cout << "sqr(5): " << res3 << std::endl;
    
    }
    

     

    res1 and res2 have static storage duration. res3  has thread storage duration.

    constinitSqr

    Now it’s time to write about the differences between const, constexpr, consteval, and constinit. Let me first write about function execution and then about variable initialization.

    Function Execution

     The following program consteval.cpp has three versions of a square function.

    // consteval.cpp
    
    #include <iostream>
    
    int sqrRunTime(int n) {
        return n * n;
    }
    
    consteval int sqrCompileTime(int n) {
        return n * n;
    }
    
    constexpr int sqrRunOrCompileTime(int n) {
        return n * n;
    }
    
    int main() {
    
        // constexpr int prod1 = sqrRunTime(100); ERROR (1)
        constexpr int prod2 = sqrCompileTime(100);
        constexpr int prod3 = sqrRunOrCompileTime(100);
        
        int x = 100;
        
        int prod4 = sqrRunTime(x); 
        // int prod5 = sqrCompileTime(x); ERROR (2)
        int prod6 = sqrRunOrCompileTime(x);
    
    }
    

     

    As the name suggests it. The ordinary function sqrRunTime runs at run-time; the consteval function sqrCompileTime runs at compile-time; the constexpr function sqrRunOrCompileTime can run at compile-time or run-time. Consequently, asking for the result at compile-time with sqrRunTime (1) is an error, or using a non-constant expression as an argument for sqrCompileTime (2) is an error.

    The difference between the constexpr function sqrRunOrCompileTime and the consteval function sqrCompileTime is that sqrRunOrCompileTime has only to run at compile-time when the context requires compile-time evaluation.

    static_assert(sqrRunOrCompileTime(10) == 100);                    // compile-time (1)
    int arrayNewWithConstExpressioFunction[sqrRunOrCompileTime(100)]; // compile-time (1)
    constexpr int prod = sqrRunOrCompileTime(100);                    // compile-time (1)
    
    int a = 100;
    int runTime = sqrRunOrCompileTime(a);                 // run-time (2)
    
    int runTimeOrCompiletime = sqrRunOrCompileTime(100);  // run-time or compile-time (3)
    
    int allwaysCompileTime = sqrCompileTime(100);         // compile-time (4)
    

     

    The first three lines (1) require compile-time evaluation. Line (2) can only be evaluated at run-time because a is not a constant expression. The critical line is (3). The function can be executed at compile-time or run-time. If it is executed at compile-time or run-time may depend on the compiler or the optimization level. This observation does not hold for line (4). A consteval function is always executed at compile-time.

    Variable Initialization

     In the following program constexprConstinit.cpp, I compare const, constexpr, and constint.

    // constexprConstinit.cpp
    
    #include <iostream>
    
    constexpr int constexprVal = 1000;
    constinit int constinitVal = 1000;
    
    int incrementMe(int val){ return ++val;}
    
    int main() {
    
        auto val = 1000;
        const auto res = incrementMe(val);                                      // (1)                         
        std::cout << "res: " << res << std::endl;
        
    // std::cout << "res: " << ++res << std::endl; ERROR (2) // std::cout << "++constexprVal++: " << ++constexprVal << std::endl; ERROR (2) std::cout << "++constinitVal++: " << ++constinitVal << std::endl; // (3) constexpr auto localConstexpr = 1000; // (4) // constinit auto localConstinit = 1000; ERROR }

     

    Only the const variable  (1) is initialized at run-time. constexpr and constinit variables are initialized at compile-time.

    constinit (3) does not imply constness such as const (2) or  constexpr(2).  A  constexpr (4) or const (1) declared variable can be created as a local, but a constinit declared variable not.

    constexprConstinit

    What’s next?

    Initialization of static variables in different translation units has a severe issue: If the initialization of one static depends on another static, it is not defined in which sequence they are initialized. To make it short, my next post is about the Static Initialization Order Fiasco and how you can solve it with constinit.

     

     

    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, Kris Kafka, 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, Dmitry Farberov, 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, moon, Philipp Lenk, Hobsbawm, and Charles-Jianye Chen.

    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

    Seminars

    I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

    Standard Seminars (English/German)

    Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

    • C++ – The Core Language
    • C++ – The Standard Library
    • C++ – Compact
    • C++11 and C++14
    • Concurrency with Modern C++
    • Design Pattern and Architectural Pattern with C++
    • Embedded Programming with Modern C++
    • Generic Programming (Templates) with C++
    • Clean Code with Modern C++
    • C++20

    Online Seminars (German)

    Contact Me

    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 *