What's the difference between constexpr and const?
Asked Answered
P

10

862

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 choose one?
Powel answered 2/1, 2013 at 1:42 Comment(6)
constexpr creates a compile-time constant; const simply means that value cannot be changed.Umberto
Also see When should you use constexpr capability in C++11?Unorthodox
May be this article from boost/hana library can enlight some constexpr issues where you can use constexpr and where you can't: boost.org/doc/libs/1_69_0/libs/hana/doc/html/…Arawakan
@0x499602D2 "simply means that value cannot be changed" For a scalar initialized with a literal, a value that cannot be change is also a compile time constant.Hardy
@Hardy Yeah my comment was very oversimplified. Admittedly I was new to constexpr back then too :)Umberto
@0x499602D2 The definition of const was a bit overloaded to serve diff purposes esp. in C++. Had constexpr existed at the time, const might have been more mono-purpose. Maybe. Who knows. We don't have a time machine to travel in alt universe to see.Hardy
H
808

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 and
    • 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.

Horodko answered 2/1, 2013 at 5:10 Comment(29)
IMO the "not necessarily evaluated at compile time" is less helpful than thinking of them as "evaluated at compile time". The constraints on a constant expression mean that it would be relatively easy for a compiler to evaluate it. A compiler must complain if those constraints are not satisfied. Since there are no side effects, you can never tell a difference whether a compiler "evaluated" it or not.Noose
@Noose Sure. My main point there is that if you call a constexpr function on a non-constant expression, e.g. an ordinary variable, this is perfectly legal and the function will be used like any other function. It will not be evaluated at compile time (because it can't). Perhaps you think that's obvious -- but if I stated that a function declared as constexpr will always be evaluated at compile-time, it could be interpreted in the wrong way.Horodko
Yes, I was talking about constexpr objects, not functions. I like to think of constexpr on objects as forcing compile time evaluation of values, and constexpr on functions as allowing the function to be evaluated at compile time or run time as appropriate.Noose
A correction: 'const' is only a restriction that YOU cant change the value of a variable; it does not make any promise that the value wont change (ie, by someone else). It's a write property, not a read property.Goodin
@JaredGrubb What you say is certainly true for const references and pointers to const. I didn't discuss those above, because there are no constexpr alternatives to those (there is no reference-to-constexpr). Perhaps I should make this clearer. But when it comes to actual (non-reference) const objects, their values must be defined at initialization time and cannot ever change.Horodko
You say that "all objects that are: const, literal types and [initialized by a constant expression]" can be used in constant expressions. That's not correct; only objects of integral or enumeration type have this behavior, not arbitrary literal types.Barnebas
@RichardSmith Interesting. I wasn't aware of this. Does the Standard actually say this anywhere? (Also, GCC does not seem to enforce this; I tried with float and with a literal class. Clang enforces it for float but not for the class.)Horodko
@Horodko See 5.19/2: "an lvalue-to-rvalue conversion unless it is applied to [...]". Clang should enforce this correctly; please file bugs if you find otherwise.Barnebas
@RichardSmith Thank you. You are right. I've corrected this, and I will look into bug reports.Horodko
@RichardSmith In case you are interested, I've filed this bug report for GCC: gcc.gnu.org/bugzilla/show_bug.cgi?id=57979 (For Clang it seems you were right; I couldn't reproduce any bug when I tried yesterday).Horodko
"The arguments and the return type must be literal types" - So these can't themselves be constexpr?Finbar
@Finbar I am sorry I couldn't reply earlier. Also I am not sure I understand the question. A function argument cannot be declared as constexpr, because that would imply its value to be defined at declaration time, in which case there is no point in having a function argument at all. Could you give an example of the kind of declaration you mean?Horodko
Never mind. I didn't understand what was meant by a LiteralType. I got my answer from here.Finbar
@Horodko N3337 draft 5.19/3 An address constant expression is a prvalue core constant expression of pointer type that evaluates to the address of an object with static storage duration, to the address of a function, or to a null pointer value, or a prvalue core constant expression of type std::nullptr_t.Brookite
In your answer: "Removing the const would render the expression illegal (because (a) a pointer to a non-const object cannot be a constant expression" seems erroneous; the above quote from the standard only talks about static storage duration and nothing about the object's const-ness; however (b) is true. Also I read in C++ Primer that a constexpr pointer to a non-const object is allowed; lastly GCC agrees too. However, you got my +1 :)Brookite
IMHO, const has always had a double meaning. Not just in runtime, but at compile time too. For example, static const int foo = 100 may never yield an addressable variable at runtime, depending on usage. It would "inline" the literal 100 in places where foo is used. I don't understand why constexpr is even needed for variables, since I don't see what additional rules they enforce. In fact, only functions have different requirements when using const vs constexpr. Need more clarification on usage for variables.Disconformity
"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." ...wut? You're comparing apples and oranges here. You're describing constness of the *this instance, whereas the constexpr applicable to functions is on the return value. One can equally declare any function to return a const value, but that's pretty pointless.Literati
This sentence: It gives a guarantee that the member function does not modify any of the non-static data members. misses one important detail. Members marked as mutable may also be modified by const member functions.Lexi
Now that we have C++14 (and C++17) I confirm that constexpr for member functions no longer implies const.Roberge
@Horodko "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." So when would a constexpr be evaluated at runtime? You provide no examples.Lazaretto
@Nik-Lz When a constexpr function is used like a regular functional function with a normal argument (f.ex. a local variable).Hardy
"guarantee that the member function does not modify any of the non-static data members" There is no guarantee! It's a vague promise that they probably won't be modified, at least not without a cast, via this. They could be modified via other parameters if you do x.f(x) calling X::f(X&) const for example. Or via its own name if you invoke the function on a global object. Or via another pointer...Hardy
@Hardy While it's true it doesn't guarantee no changes, I wouldn't call it a vague promise. Calling a const function lets you know that you're providing read access, and not write access, to its members. If you provide write access by other means (global data, non-const references, etc) then it's not changing its own data as a result of calling a const function, it's accessing the alternate data you provided which is coincidentally the same data.Cismontane
The only exception I'm aware of is that you could const_cast<T*>(this) to modify data, however this would be an inappropriate usage of const_cast, and should not pass any code review. I believe this answer is a nice quick example of one of the very few good usages of const_cast.Cismontane
Lots of words but failed to answer the question. I still don't know what constexpr is.Bathilda
Excitingly, as of C++20, the rules about what can go inside a constexpr constructor have been relaxed even further, and trivial initialisation of members within constructor body is now allowed.Mythological
constexpr can also be used in if() statements for compile time branch selection.Mowery
I'm confused, I might be reading or understanding this the wrong way. The answer states "As of C++14, the rules are more relaxed, what is allowed since then inside a constexpr function: [...]" (emphasis mine), whereas on cppreference.com, it states that "the function body must not contain". In both cases, much the same content is listed (e.g. asm declaration, or thread/static storage variables, non-initialized variable). They seem to contradict each other. Is the answer phrased wrong, or do I misunderstand it?Chadburn
@PaulWicking Thank you for pointing this out. That part of the answer was added by somebody else later, so I am not 100% sure. I suspect they added this content to the answer when some of the details were still in discussion. E.g. asm statements ended up not being allowed in C++14, but are allowed since C++20. This seems to be very much in flux. If somebody were kind enough to trace this through the various C++ standards and make this very clear in the answer, I think that would be great. Personally, I won't have time in the near future.Horodko
D
176

const applies for variables, and prevents them from being modified in your code.

constexpr tells the compiler that this expression results in a compile time constant value, so it can be used in places like array lengths, assigning to const variables, etc. The link given by Oli has a lot of excellent examples.

Basically they are 2 different concepts altogether, and can (and should) be used together.

Dogma answered 2/1, 2013 at 1:44 Comment(4)
const & constexpr usage, ex: en.cppreference.com/w/cpp/container/array/getStagestruck
@ManoharReddyPoreddy i think en.cppreference.com/w/cpp/container/array/begin is a better example, it has signatures with constexpr T f(x) const, where both apply to the function, while in constexpr const T f(x) (i.e. the array::get signature) the const is part of the return type rather than the function property (not sure of the name for this in standardese). Although this awnser fails to acknowledge the use of const on member functions.Those
@Those I changed my programming language from C++ to javascript for some time now, so I barely even remember that posted above :), and so unable to comment for the same reason.Stagestruck
const doesn't prevent a variable from being modified. It prevents the programmer from modifying the variable without explicitly saying they want to do so (via a cast).Mallissa
A
96

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++.

Advanced answered 1/1, 2016 at 7:45 Comment(3)
Thanks for the great example code showing the different situations. As great as some of the other explanations are, I found the seeing the code in action much more useful and understandable. It really helped solidify my understanding of what is going on.Ignorance
Thanks, this helped me better understand than the selected answer.Tremain
numeric_limits<int>::max() is a constant expression though. (since it's constexpr)Buckbuckaroo
G
60

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.
Goodfellowship answered 5/8, 2018 at 16:43 Comment(1)
Also, in C, constexpr int exists but it's spelled const intHardy
M
45

According to book of "The C++ Programming Language 4th Editon" by Bjarne Stroustrup
const: meaning roughly ‘‘I promise not to change this value’’ (§7.5). This is used primarily to specify interfaces, so that data can be passed to functions without fear of it being modified.
The compiler enforces the promise made by const.
constexpr: meaning roughly ‘‘to be evaluated at compile time’’ (§10.4). This is used primarily to specify constants, to allow
For example:

const int dmv = 17; // dmv is a named constant
int var = 17; // var is not a constant
constexpr double max1 = 1.4*square(dmv); // OK if square(17) is a constant expression
constexpr double max2 = 1.4∗square(var); // error : var is not a constant expression
const double max3 = 1.4∗square(var); //OK, may be evaluated at run time
double sum(const vector<double>&); // sum will not modify its argument (§2.2.5)
vector<double> v {1.2, 3.4, 4.5}; // v is not a constant
const double s1 = sum(v); // OK: evaluated at run time
constexpr double s2 = sum(v); // error : sum(v) not constant expression

For a function to be usable in a constant expression, that is, in an expression that will be evaluated by the compiler, it must be defined constexpr.
For example:

constexpr double square(double x) { return x∗x; }


To be constexpr, a function must be rather simple: just a return-statement computing a value. A constexpr function can be used for non-constant arguments, but when that is done the result is not a constant expression. We allow a constexpr function to be called with non-constant-expression arguments in contexts that do not require constant expressions, so that we don’t hav e to define essentially the same function twice: once for constant expressions and once for variables.
In a few places, constant expressions are required by language rules (e.g., array bounds (§2.2.5, §7.3), case labels (§2.2.4, §9.4.2), some template arguments (§25.2), and constants declared using constexpr). In other cases, compile-time evaluation is important for performance. Independently of performance issues, the notion of immutability (of an object with an unchangeable state) is an important design concern (§10.4).

Moulding answered 2/10, 2013 at 12:58 Comment(1)
there are still performance issues. Seems that constexpr function if evaluated at runtime may be slower than non-constexpr version of function. Also if we have a constant value should we prefer "const" or "constexpr"? (more a style question generated assembly looks the same)Baluster
N
18

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.

Neron answered 6/7, 2018 at 5:10 Comment(0)
C
8

As @0x499602d2 already pointed out, const only ensures that a value cannot be changed after initialization where as constexpr (introduced in C++11) guarantees the variable is a compile time constant.
Consider the following example(from LearnCpp.com):

cout << "Enter your age: ";
int age;
cin >> age;

const int myAge{age};        // works
constexpr int someAge{age};  // error: age can only be resolved at runtime
Claviform answered 5/6, 2016 at 10:33 Comment(0)
G
7

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++.

Grapery answered 10/4, 2020 at 0:34 Comment(2)
Nice point on the linker. Could it be considered more secure in general to use constexpr as it results in fewer symbol leaks?Vivanvivarium
@NeilMcGill not really because inline and static will cause the compiler to not emit a local symbol for multiply if compiling using -O1 or stronger. Constexpr is the only one that optimises out the load for val, but other than that it's identical to putting static or inline before the function. I forgot something else as well. Constexpr is the only keyword that does not emit a symbol for the function on -O0, static and inline doGrapery
D
5

An overview of the const and constexpr keywords

In C ++, if a const object is initialized with a constant expression, we can use our const object wherever a constant expression is required.

const int x = 10;
int a[x] = {0};

For example, we can make a case statement in switch.

constexpr can be used with arrays.

constexpr is not a type.

The constexpr keyword can be used in conjunction with the auto keyword.

constexpr auto x = 10;

struct Data {   // We can make a bit field element of struct.   
    int a:x;
 };

If we initialize a const object with a constant expression, the expression generated by that const object is now a constant expression as well.

Constant Expression : An expression whose value can be calculated at compile time.

x*5-4 // This is a constant expression. For the compiler, there is no difference between typing this expression and typing 46 directly.

Initialize is mandatory. It can be used for reading purposes only. It cannot be changed. Up to this point, there is no difference between the "const" and "constexpr" keywords.

NOTE: We can use constexpr and const in the same declaration.

constexpr const int* p;

Constexpr Functions

Normally, the return value of a function is obtained at runtime. But calls to constexpr functions will be obtained as a constant in compile time when certain conditions are met.

NOTE : Arguments sent to the parameter variable of the function in function calls or to all parameter variables if there is more than one parameter, if C.E the return value of the function will be calculated in compile time. !!!

constexpr int square (int a){
return a*a;
}

constexpr int a = 3;
constexpr int b = 5;

int arr[square(a*b+20)] = {0}; //This expression is equal to int arr[35] = {0};

In order for a function to be a constexpr function, the return value type of the function and the type of the function's parameters must be in the type category called "literal type".

The constexpr functions are implicitly inline functions.

An important point :

None of the constexpr functions need to be called with a constant expression.It is not mandatory. If this happens, the computation will not be done at compile time. It will be treated like a normal function call. Therefore, where the constant expression is required, we will no longer be able to use this expression.

The conditions required to be a constexpr function are shown below;

1 ) The types used in the parameters of the function and the type of the return value of the function must be literal type.

2 ) A local variable with static life time should not be used inside the function.

3 ) If the function is legal, when we call this function with a constant expression in compile time, the compiler calculates the return value of the function in compile time.

4 ) The compiler needs to see the code of the function, so constexpr functions will almost always be in the header files.

5 ) In order for the function we created to be a constexpr function, the definition of the function must be in the header file.Thus, whichever source file includes that header file will see the function definition.

Bonus

Normally with Default Member Initialization, static data members with const and integral types can be initialized within the class. However, in order to do this, there must be both "const" and "integral types".

If we use static constexpr then it doesn't have to be an integral type to initialize it inside the class. As long as I initialize it with a constant expression, there is no problem.

class Myclass  {
         const static int sx = 15;         // OK
         constexpr static int sy = 15;     // OK
         const static double sd = 1.5;     // ERROR
         constexpr static double sd = 1.5; // OK
 };
Dibru answered 22/2, 2021 at 12:0 Comment(2)
"if a const object is initialized with a constant expression, we can use our const object wherever a constant expression is required." Only true for constants of integral types.Hollo
Is the comment correct? I get square(a*b+20) -> square(35) -> 1225...Gavingavini
C
1

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.

Chalet answered 13/6, 2019 at 12:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.