Difference between `constexpr` and `const`

后端 未结 9 1467
无人共我
无人共我 2020-11-22 03:48

What\'s the difference between constexpr and const?

  • When can I use only one of them?
  • When can I use both and how should I
相关标签:
9条回答
  • 2020-11-22 04:10

    A const int var can be dynamically set to a value at runtime and once it is set to that value, it can no longer be changed.

    A constexpr int var cannot be dynamically set at runtime, but rather, at compile time. And once it is set to that value, it can no longer be changed.

    Here is a solid example:

    int main(int argc, char*argv[]) {
        const int p = argc; 
        // p = 69; // cannot change p because it is a const
        // constexpr int q = argc; // cannot be, bcoz argc cannot be computed at compile time 
        constexpr int r = 2^3; // this works!
        // r = 42; // same as const too, it cannot be changed
    }
    

    The snippet above compiles fine and I have commented out those that cause it to error.

    The key notions here to take note of, are the notions of compile time and run time. New innovations have been introduced into C++ intended to as much as possible ** know ** certain things at compilation time to improve performance at runtime.

    Any attempt of explanation which does not involve the two key notions above, is hallucination.

    0 讨论(0)
  • 2020-11-22 04:11

    Basic meaning and syntax

    Both keywords can be used in the declaration of objects as well as functions. The basic difference when applied to objects is this:

    • const declares an object as constant. This implies a guarantee that once initialized, the value of that object won't change, and the compiler can make use of this fact for optimizations. It also helps prevent the programmer from writing code that modifies objects that were not meant to be modified after initialization.

    • constexpr declares an object as fit for use in what the Standard calls constant expressions. But note that constexpr is not the only way to do this.

    When applied to functions the basic difference is this:

    • const can only be used for non-static member functions, not functions in general. It gives a guarantee that the member function does not modify any of the non-static data members (except for mutable data members, which can be modified anyway).

    • constexpr can be used with both member and non-member functions, as well as constructors. It declares the function fit for use in constant expressions. The compiler will only accept it if the function meets certain criteria (7.1.5/3,4), most importantly (†):

      • The function body must be non-virtual and extremely simple: Apart from typedefs and static asserts, only a single return statement is allowed. In the case of a constructor, only an initialization list, typedefs, and static assert are allowed. (= default and = delete are allowed, too, though.)
      • As of C++14, the rules are more relaxed, what is allowed since then inside a constexpr function: asm declaration, a goto statement, a statement with a label other than case and default, try-block, the definition of a variable of non-literal type, definition of a variable of static or thread storage duration, the definition of a variable for which no initialization is performed.
      • The arguments and the return type must be literal types (i.e., generally speaking, very simple types, typically scalars or aggregates)

    Constant expressions

    As said above, constexpr declares both objects as well as functions as fit for use in constant expressions. A constant expression is more than merely constant:

    • It can be used in places that require compile-time evaluation, for example, template parameters and array-size specifiers:

        template<int N>
        class fixed_size_list
        { /*...*/ };
      
        fixed_size_list<X> mylist;  // X must be an integer constant expression
      
        int numbers[X];  // X must be an integer constant expression
      
    • But note:

    • Declaring something as constexpr does not necessarily guarantee that it will be evaluated at compile time. It can be used for such, but it can be used in other places that are evaluated at run-time, as well.

    • An object may be fit for use in constant expressions without being declared constexpr. Example:

           int main()
           {
             const int N = 3;
             int numbers[N] = {1, 2, 3};  // N is constant expression
           }
      

      This is possible because N, being constant and initialized at declaration time with a literal, satisfies the criteria for a constant expression, even if it isn't declared constexpr.

    So when do I actually have to use constexpr?

    • An object like N above can be used as constant expression without being declared constexpr. This is true for all objects that are:
    • const
    • of integral or enumeration type and
    • initialized at declaration time with an expression that is itself a constant expression

    [This is due to §5.19/2: A constant expression must not include a subexpression that involves "an lvalue-to-rvalue modification unless […] a glvalue of integral or enumeration type […]" Thanks to Richard Smith for correcting my earlier claim that this was true for all literal types.]

    • For a function to be fit for use in constant expressions, it must be explicitly declared constexpr; it is not sufficient for it merely to satisfy the criteria for constant-expression functions. Example:

       template<int N>
       class list
       { };
      
       constexpr int sqr1(int arg)
       { return arg * arg; }
      
       int sqr2(int arg)
       { return arg * arg; }
      
       int main()
       {
         const int X = 2;
         list<sqr1(X)> mylist1;  // OK: sqr1 is constexpr
         list<sqr2(X)> mylist2;  // wrong: sqr2 is not constexpr
       }
      

    When can I / should I use both, const and constexpr together?

    A. In object declarations. This is never necessary when both keywords refer to the same object to be declared. constexpr implies const.

    constexpr const int N = 5;
    

    is the same as

    constexpr int N = 5;
    

    However, note that there may be situations when the keywords each refer to different parts of the declaration:

    static constexpr int N = 3;
    
    int main()
    {
      constexpr const int *NP = &N;
    }
    

    Here, NP is declared as an address constant-expression, i.e. a pointer that is itself a constant expression. (This is possible when the address is generated by applying the address operator to a static/global constant expression.) Here, both constexpr and const are required: constexpr always refers to the expression being declared (here NP), while const refers to int (it declares a pointer-to-const). Removing the const would render the expression illegal (because (a) a pointer to a non-const object cannot be a constant expression, and (b) &N is in-fact a pointer-to-constant).

    B. In member function declarations. In C++11, constexpr implies const, while in C++14 and C++17 that is not the case. A member function declared under C++11 as

    constexpr void f();
    

    needs to be declared as

    constexpr void f() const;
    

    under C++14 in order to still be usable as a const function.

    0 讨论(0)
  • 2020-11-22 04:16

    First of all, both are qualifiers in c++. A variable declared const must be initialized and cannot be changed in the future. Hence generally a variable declared as a const will have a value even before compiling.

    But, for constexpr it is a bit different.

    For constexpr, you can give an expression that could be evaluated during the compilation of the program.

    Obviously, the variable declared as constexper cannot be changed in the future just like const.

    0 讨论(0)
  • 2020-11-22 04:17

    I don't think any of the answers really make it clear exactly what side effects it has, or indeed, what it is.

    constexpr and const at namespace/file-scope are identical when initialised with a literal or expression; but with a function, const can be initialised by any function, but constexpr initialised by a non-constexpr (a function that isn't marked with constexpr or a non constexpr expression) will generate a compiler error. Both constexpr and const are implicitly internal linkage for variables (well actually, they don't survive to get to the linking stage if compiling -O1 and stronger, and static doesn't force the compiler to emit an internal (local) linker symbol for const or constexpr when at -O1 or stronger; the only time it does this is if you take the address of the variable. const and constexpr will be an internal symbol unless expressed with extern i.e. extern constexpr/const int i = 3; needs to be used). On a function, constexpr makes the function permanently never reach the linking stage (regardless of extern or inline in the definition or -O0 or -Ofast), whereas const never does, and static and inline only have this effect on -O1 and above. When a const/constexpr variable is initialised by a constexpr function, the load is always optimised out with any optimisation flag, but it is never optimised out if the function is only static or inline, or if the variable is not a const/constexpr.

    Standard compilation (-O0)

    #include<iostream>
    constexpr int multiply (int x, int y)
    {
    
      return x * y;
    }
    
    extern const int val = multiply(10,10);
    int main () {
      std::cout << val;
    } 
    

    compiles to

    val:
            .long   100  //extra external definition supplied due to extern
    
    main:
            push    rbp
            mov     rbp, rsp
            mov     esi, 100 //substituted in as an immediate
            mov     edi, OFFSET FLAT:_ZSt4cout
            call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
            mov     eax, 0
            pop     rbp
            ret
    
    __static_initialization_and_destruction_0(int, int):
            . 
            . 
            . 
    

    However

    #include<iostream>
    const int multiply (int x, int y)
    {
    
      return x * y;
    }
    
    const int val = multiply(10,10); //constexpr is an error
    int main () {
      std::cout << val;
    }
    

    Compiles to

    multiply(int, int):
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR [rbp-4], edi
            mov     DWORD PTR [rbp-8], esi
            mov     eax, DWORD PTR [rbp-4]
            imul    eax, DWORD PTR [rbp-8]
            pop     rbp
            ret
    
    main:
            push    rbp
            mov     rbp, rsp
            mov     eax, DWORD PTR val[rip]
            mov     esi, eax
            mov     edi, OFFSET FLAT:_ZSt4cout
            call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
            mov     eax, 0
            pop     rbp
            ret
    
    __static_initialization_and_destruction_0(int, int):
            . 
            . 
            . 
            mov     esi, 10
            mov     edi, 10
            call    multiply(int, int)
            mov     DWORD PTR val[rip], eax
    

    This clearly shows that constexpr causes the initialisation of the const/constexpr file-scope variable to occur at compile time and produce no global symbol, whereas not using it causes initialisation to occur before main at runtime.

    Compiling using -Ofast

    Even -Ofast doesn't optimise out the load! https://godbolt.org/z/r-mhif, so you need constexpr


    constexpr functions can also be called from inside other constexpr functions for the same result. constexpr on a function also prevents use of anything that can't be done at compile time in the function; for instance, a call to the << operator on std::cout.

    constexpr at block scope behaves the same in that it produces an error if initialised by a non-constexpr function; the value is also substituted in immediately.

    In the end, its main purpose is like C's inline function, but it is only effective when the function is used to initialise file-scope variables (which functions cannot do on C, but they can on C++ because it allows dynamic initialisation of file-scope variables), except the function cannot export a global/local symbol to the linker as well, even using extern/static, which you could with inline on C; block-scope variable assignment functions can be inlined simply using an -O1 optimisation without constexpr on C and C++.

    0 讨论(0)
  • 2020-11-22 04:26

    Both const and constexpr can be applied to variables and functions. Even though they are similar to each other, in fact they are very different concepts.

    Both const and constexpr mean that their values can't be changed after their initialization. So for example:

    const int x1=10;
    constexpr int x2=10;
    
    x1=20; // ERROR. Variable 'x1' can't be changed.
    x2=20; // ERROR. Variable 'x2' can't be changed.
    

    The principal difference between const and constexpr is the time when their initialization values are known (evaluated). While the values of const variables can be evaluated at both compile time and runtime, constexpr are always evaluated at compile time. For example:

    int temp=rand(); // temp is generated by the the random generator at runtime.
    
    const int x1=10; // OK - known at compile time.
    const int x2=temp; // OK - known only at runtime.
    constexpr int x3=10; // OK - known at compile time.
    constexpr int x4=temp; // ERROR. Compiler can't figure out the value of 'temp' variable at compile time so `constexpr` can't be applied here.
    

    The key advantage to know if the value is known at compile time or runtime is the fact that compile time constants can be used whenever compile time constants are needed. For instance, C++ doesn't allow you to specify C-arrays with the variable lengths.

    int temp=rand(); // temp is generated by the the random generator at runtime.
    
    int array1[10]; // OK.
    int array2[temp]; // ERROR.
    

    So it means that:

    const int size1=10; // OK - value known at compile time.
    const int size2=temp; // OK - value known only at runtime.
    constexpr int size3=10; // OK - value known at compile time.
    
    
    int array3[size1]; // OK - size is known at compile time.
    int array4[size2]; // ERROR - size is known only at runtime time.
    int array5[size3]; // OK - size is known at compile time.
    

    So const variables can define both compile time constants like size1 that can be used to specify array sizes and runtime constants like size2 that are known only at runtime and can't be used to define array sizes. On the other hand constexpr always define compile time constants that can specify array sizes.

    Both const and constexpr can be applied to functions too. A const function must be a member function (method, operator) where application of const keyword means that the method can't change the values of their member (non-static) fields. For example.

    class test
    {
       int x;
    
       void function1()
       {
          x=100; // OK.
       }
    
       void function2() const
       {
          x=100; // ERROR. The const methods can't change the values of object fields.
       }
    };
    

    A constexpr is a different concept. It marks a function (member or non-member) as the function that can be evaluated at compile time if compile time constants are passed as their arguments. For example you can write this.

    constexpr int func_constexpr(int X, int Y)
    {
        return(X*Y);
    }
    
    int func(int X, int Y)
    {
        return(X*Y);
    }
    
    int array1[func_constexpr(10,20)]; // OK - func_constexpr() can be evaluated at compile time.
    int array2[func(10,20)]; // ERROR - func() is not a constexpr function.
    
    int array3[func_constexpr(10,rand())]; // ERROR - even though func_constexpr() is the 'constexpr' function, the expression 'constexpr(10,rand())' can't be evaluated at compile time.
    

    By the way the constexpr functions are the regular C++ functions that can be called even if non-constant arguments are passed. But in that case you are getting the non-constexpr values.

    int value1=func_constexpr(10,rand()); // OK. value1 is non-constexpr value that is evaluated in runtime.
    constexpr int value2=func_constexpr(10,rand()); // ERROR. value2 is constexpr and the expression func_constexpr(10,rand()) can't be evaluated at compile time.
    

    The constexpr can be also applied to the member functions (methods), operators and even constructors. For instance.

    class test2
    {
        static constexpr int function(int value)
        {
            return(value+1);
        }
    
        void f()
        {
            int x[function(10)];
    
    
        }
    };
    

    A more 'crazy' sample.

    class test3
    {
        public:
    
        int value;
    
        // constexpr const method - can't chanage the values of object fields and can be evaluated at compile time.
        constexpr int getvalue() const
        {
            return(value);
        }
    
        constexpr test3(int Value)
            : value(Value)
        {
        }
    };
    
    
    constexpr test3 x(100); // OK. Constructor is constexpr.
    
    int array[x.getvalue()]; // OK. x.getvalue() is constexpr and can be evaluated at compile time.
    
    0 讨论(0)
  • 2020-11-22 04:28

    Overview

    • const guarantees that a program does not change an object’s value. However, const does not guarantee which type of initialization the object undergoes.

      Consider:

      const int mx = numeric_limits<int>::max();  // OK: runtime initialization
      

      The function max() merely returns a literal value. However, because the initializer is a function call, mx undergoes runtime initialization. Therefore, you cannot use it as a constant expression:

      int arr[mx];  // error: “constant expression required”
      
    • constexpr is a new C++11 keyword that rids you of the need to create macros and hardcoded literals. It also guarantees, under certain conditions, that objects undergo static initialization. It controls the evaluation time of an expression. By enforcing compile-time evaluation of its expression, constexpr lets you define true constant expressions that are crucial for time-critical applications, system programming, templates, and generally speaking, in any code that relies on compile-time constants.

    Constant-expression functions

    A constant-expression function is a function declared constexpr. Its body must be non-virtual and consist of a single return statement only, apart from typedefs and static asserts. Its arguments and return value must have literal types. It can be used with non-constant-expression arguments, but when that is done the result is not a constant expression.

    A constant-expression function is meant to replace macros and hardcoded literals without sacrificing performance or type safety.

    constexpr int max() { return INT_MAX; }           // OK
    constexpr long long_max() { return 2147483647; }  // OK
    constexpr bool get_val()
    {
        bool res = false;
        return res;
    }  // error: body is not just a return statement
    
    constexpr int square(int x)
    { return x * x; }  // OK: compile-time evaluation only if x is a constant expression
    const int res = square(5);  // OK: compile-time evaluation of square(5)
    int y = getval();
    int n = square(y);          // OK: runtime evaluation of square(y)
    

    Constant-expression objects

    A constant-expression object is an object declared constexpr. It must be initialized with a constant expression or an rvalue constructed by a constant-expression constructor with constant-expression arguments.

    A constant-expression object behaves as if it was declared const, except that it requires initialization before use and its initializer must be a constant expression. Consequently, a constant-expression object can always be used as part of another constant expression.

    struct S
    {
        constexpr int two();      // constant-expression function
    private:
        static constexpr int sz;  // constant-expression object
    };
    constexpr int S::sz = 256;
    enum DataPacket
    {
        Small = S::two(),  // error: S::two() called before it was defined
        Big = 1024
    };
    constexpr int S::two() { return sz*2; }
    constexpr S s;
    int arr[s.two()];  // OK: s.two() called after its definition
    

    Constant-expression constructors

    A constant-expression constructor is a constructor declared constexpr. It can have a member initialization list but its body must be empty, apart from typedefs and static asserts. Its arguments must have literal types.

    A constant-expression constructor allows the compiler to initialize the object at compile-time, provided that the constructor’s arguments are all constant expressions.

    struct complex
    {
        // constant-expression constructor
        constexpr complex(double r, double i) : re(r), im(i) { }  // OK: empty body
        // constant-expression functions
        constexpr double real() { return re; }
        constexpr double imag() { return im; }
    private:
        double re;
        double im;
    };
    constexpr complex COMP(0.0, 1.0);         // creates a literal complex
    double x = 1.0;
    constexpr complex cx1(x, 0);              // error: x is not a constant expression
    const complex cx2(x, 1);                  // OK: runtime initialization
    constexpr double xx = COMP.real();        // OK: compile-time initialization
    constexpr double imaglval = COMP.imag();  // OK: compile-time initialization
    complex cx3(2, 4.6);                      // OK: runtime initialization
    

    Tips from the book Effective Modern C++ by Scott Meyers about constexpr:

    • constexpr objects are const and are initialized with values known during compilation;
    • constexpr functions produce compile-time results when called with arguments whose values are known during compilation;
    • constexpr objects and functions may be used in a wider range of contexts than non-constexpr objects and functions;
    • constexpr is part of an object’s or function’s interface.

    Source: Using constexpr to Improve Security, Performance and Encapsulation in C++.

    0 讨论(0)
提交回复
热议问题