Use constexpr wherever possible
constexpr
Variables haveconst
properties and must be initialized with values known to the compiler.constexpr
When the function is called, if the actual parameter values passed in are known at compile time, the returned result is alsoconstexpr
a variable, otherwise it returns a non-constexpr
variable- Functions or variables can be used in a wider context than non-
constexpr
variables orconstexpr
functionsconstexpr
constexpr
constexpr
Is part of the object and function interface.
Before looking at this statute, let's look at the following code. These three are macro, const
modification, and constexpr
modification. What is the difference and what is the meaning? This article will explain them one by one.
// 预处理
#define MEANING_OF_LIFE 42
// 常量:
const int MeaningOfLife = 42;
// constexpr-函数:
constexpr int MeaningOfLife () {
return 42; }
Macros --> Avoid using macros
To be honest: macros are
C
theC++
most blunt tools in the abstract facilities of the language. They are hungry wolves in the coat of functions, which are difficult to tame, and they will wander everywhere in their own way. Avoid macros.
Macros are touted as a text replacement facility, the effect of which occurs during the preprocessing stage, before
c++
syntactic and semantic rules have yet come into play.
c++
Macros are almost never needed in . You can useconst
orenum
define well-understood constants, toinline
avoid the overhead of function calls, totemplate
specify families of functions and types, tonamespace
avoid name conflicts. —stroustrup
The number one rule about macros is: don't use them unless you have to. Almost every macro indicates a flaw in the programming language, the program, or the programmer. —stroustrup
A macro ignores scope, ignores the type system, ignores all other language features and rules, and hijacks the #define
symbols it defines ( ) for the rest of the file. Macros look like symbols or function calls, but they are not. It unfolds into something compelling and surprising depending on the context in which it is used.
Errors in macros may only be reported after macro expansion, not at definition time.
Exceptions:
#include guard
Add guards to headers (files): Use include guards ( ) with unique names in all header files to prevent unintentional multiple inclusions .- Assertions generally only generate code in debug mode (
NDEBUG
when the macro is not defined), so they don't exist in release builds. #ifdef
and in conditional compilationif defined
. In conditional compilation (such as system-dependent parts), avoid messing with insertions here and there in the code#ifdef
. Instead, organize your code to use macros to drive multiple implementations of a common interface, and then use that interface consistently.
constant expression
Defines an expression that can be evaluated at compile time.
The so-called constant expression refers to
≥1
an expression composed of multiple ( ) constants. In other words, an expression is a constant expression if its members are all constants. This also means that once a constant expression is determined, its value cannot be modified.
int n = 1;
std::array<int, n> a1; // 错误:n 不是常量表达式
const int cn = 2;
std::array<int, cn> a2; // OK:cn 是常量表达式
C++
The execution process of the program generally goes through three stages: compiling, linking, and running. It is worth mentioning that the calculation timing of constant expressions and non-constant expressions is different. The results of non-constant expressions can only be calculated at the program running stage; while the calculation of constant expressions often occurs at the program compilation stage, which can be extremely The execution efficiency of the program is greatly improved, because the expression only needs to be calculated once in the compilation stage, which saves the time needed to be calculated every time the program runs.
#include <array>
#include <iostream>
#include <array>
using namespace std;
void dis_1(const int x) {
//error: 'x' is not a constant expression
array<int, x> myarr{
1, 2, 3, 4, 5};
cout << myarr[1] << endl;
}
void dis_2() {
const int x = 5;
array<int, x> myarr{
1, 2, 3, 4, 5};
cout << myarr[1] << endl;
}
int main() {
dis_1(5);
dis_2();
}
const
for what
- Modified variable, indicating that the variable cannot be changed;
- Modified pointers are divided into pointers to constants and pointer constants;
- Constant references are often used for formal parameter types, which avoids copying and modification of values by functions;
- Modified member function, indicating that member variables cannot be modified within the member function.
// 类
class A {
private:
const int a; // 常对象成员,只能在初始化列表赋值
public:
// 构造函数
A() : a(0){
};
A(int x) : a(x){
}; // 初始化列表
// const可用于对重载函数的区分
int getValue(); // 普通成员函数
int getValue() const; // 常成员函数,不得修改类中的任何数据成员的值
};
void function() {
// 对象
A b; // 普通对象,可以调用全部成员函数
const A a; // 常对象,只能调用常成员函数、更新常成员变量
const A* p = &a; // 常指针
const A& q = a; // 常引用
// 指针
char greeting[] = "Hello";
char* p1 = greeting; // 指针变量,指向字符数组变量
const char* p2 = greeting; // 指针变量,指向字符数组常量
char* const p3 = greeting; // 常指针,指向字符数组变量
const char* const p4 = greeting; // 常指针,指向字符数组常量
}
// 函数
void function1(const int Var); // 传递过来的参数在函数内不可变
void function2(const char* Var); // 参数指针所指内容为常量
void function3(char* const Var); // 参数指针为常指针
void function4(const int& Var); // 引用参数在函数内为常量
// 函数返回值
const int function5(); // 返回一个常数
const int*
function6(); // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const
function7(); // 返回一个指向变量的常指针,使用:int* const p = function7();
const
are our friends: constant values are easier to understand, track, and analyze, so constants should be used instead of variables whenever possible , and should be const
the default option when defining values; constants are safe and will be checked at compile time Check, and it's c++
seamlessly integrated with the type system of . Do not cast const
the type unless you are calling a function with incorrect const.
common practice
const double gravity {
9.8 }; // 首选在类型前使用const
int const sidesInSquare {
4 }; // "east const" 风格, ok但是不推荐
const variables must be initialized
const
Variables must be initialized when they are defined, and then their value cannot be changed by assignment:
int main()
{
const double gravity; // error: const variables must be initialized
gravity = 9.9; // error: const variables can not be changed
return 0;
}
Compile as follows:
<source>: In function 'int main()':
<source>:7:18: error: uninitialized 'const gravity' [-fpermissive]
7 | const double gravity; // error: const variables must be initialized
| ^~~~~~~
<source>:8:13: error: assignment of read-only variable 'gravity'
8 | gravity = 9.9; // error: const variables can not be changed
Note that constant variables can be initialized from other variables (including non-const variables):
#include <iostream>
int main() {
int age{
};
age = 11;
const int constAge{
age}; // 使用非const值初始化const变量
age = 5; // ok: age 是非常数的,所以我们可以改变它的值
// constAge = 6; // error: constAge 是const,我们不能更改其值
return 0;
}
pass value and return value
- Don't use const when passing by value
- Don't use const when returning by value
inline function
use
- It is equivalent to writing the content in the inline function at the place where the inline function is called;
- It is equivalent to directly execute the function body without executing the step of entering the function;
- It is equivalent to a macro, but it has more type checks than a macro, and it really has functional characteristics;
- Complex operations such as loops, recursion, and switches cannot be included;
- Functions defined in the class declaration, except for virtual functions, are automatically and implicitly regarded as inline functions.
// 声明1(加 inline,建议使用)
inline int functionName(int first, int secend,...);
// 声明2(不加 inline)
int functionName(int first, int secend,...);
// 定义
inline int functionName(int first, int secend,...) {
/****/};
// 类内定义,隐式内联
class A {
int doA() {
return 0; } // 隐式内联
}
// 类外定义,需要显式内联
class A {
int doA();
}
inline int A::doA() {
return 0; } // 需要显式内联
Advantages and disadvantages
advantage
- Inline functions, like macro functions, will perform code expansion at the called place, eliminating the need for parameter stacking, stack frame development and recycling, and result return, thereby improving the program's running speed .
- Compared with macro functions, inline functions will perform security checks or automatic type conversions (same as ordinary functions) when the code is expanded , while macro definitions will not.
- The member functions that are declared and defined in the class are automatically converted into inline functions, so inline functions can access the member variables of the class , but macro definitions cannot.
- Inline functions can be debugged at runtime , while macro definitions cannot.
shortcoming
- ** Code bloat. **Inlining is at the cost of code expansion (copying), eliminating the overhead of function calls. If the execution time of the code in the function body is relatively large compared to the overhead of the function call, then the efficiency gains will be small. On the other hand, every inline function call needs to copy the code, which will increase the total code size of the program and consume more memory space.
- Inline functions cannot be upgraded with library upgrades. Changes to inline functions require recompilation , unlike non-inline functions that can be directly linked.
- Whether it is inlined or not, the programmer cannot control it . The inline function is just a suggestion to the compiler, and it is up to the compiler to decide whether to inline the function.
Can virtual functions be inlined?
- A virtual function can be an inline function , and inlining can modify a virtual function, but when a virtual function exhibits polymorphism, it cannot be inlined .
- Inlining means that the compiler recommends the compiler to inline, and the polymorphism of virtual functions is at runtime, and the compiler cannot know which code to call at runtime, so virtual functions cannot be inlined when they appear to be polymorphic (runtime).
inline virtual
The only time it can be inlined is when: the compiler knows which class (as in) the object being called isBase::who()
, which only happens when the compiler has the actual object and not a pointer or reference to the object.
#include <iostream>
using namespace std;
class Base
{
public:
inline virtual void who(){
cout << "I am Base\n";
}
virtual ~Base() {
}
};
class Derived : public Base{
public:
inline void who() {
// 不写inline时隐式内联
cout << "I am Derived\n";
}
};
int main(){
// 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。
Base b;
b.who();
// 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。
Base *ptr = new Derived();
ptr->who();
// 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。
delete ptr;
ptr = nullptr;
system("pause");
return 0;
}
concept | inline function |
---|---|
It removes function calls when evaluating code/expressions at compile time. | It removes hardly any function calls, since it operates on expressions at runtime. |
The value of a variable or function can be evaluated at compile time. | The value of a function or variable cannot be evaluated at compile time. |
it does not imply external links | It means external linkage. |
constexpr
- Generalized constant expressions
constexpr
mechanism
- Provides more general constant expressions
- Allow constant expressions containing user-defined types
- Provides a way to guarantee that initialization is done at compile time
constexpr
A specifier declares that a function or variable can be evaluated at compile time. These variables and functions (given appropriate function arguments) can be used where compile-time constant expressions are required.
- Using the specifier when declaring an object or a non-static member function (
C++14
before)constexpr
implies bothconst
.- Use of specifiers when declaring a function or static member variable(
C++17
s)constexpr
implies at the same timeinline
. If a declaration of a function or function template hasconstexpr
a specifier, all of its declarations must have that specifier.
Example:
#include <array>
#include <iostream>
using namespace std;
constexpr int ximen = 12; //蕴含const
//若无constexpr则会报错
//error: redeclaration 'int fun()' differs in 'constexpr' from previous declaration
constexpr int fun();
constexpr auto square(int x) -> int {
return x * x; } //蕴含inline
constexpr auto twice_square(int x) -> int {
return square(x); } //蕴含inline
constexpr int fun() {
return 12;}
int main() {
return 0; }
The content generated by compilation is as follows:
#include <array>
#include <iostream>
using namespace std;
constexpr const int ximen = 12;
inline constexpr int fun();
inline constexpr int square(int x)
{
return x * x;
}
inline constexpr int twice_square(int x)
{
return square(x);
}
//若无constexpr则会报错
//error: redeclaration 'int fun()' differs in 'constexpr' from previous declaration
inline constexpr int fun()
{
return 12;
}
int main()
{
return 0;
}
Modify ordinary variables
constexpr
Variables must meet the following requirements:
- Its type must be a literal type (
LiteralType
). - It must be initialized immediately.
- Its initialization, including all implicit conversions, constructor calls, etc., must be a constant expression (when these two conditions are met, the reference can be declared as: the referenced object is initialized by the constant expression, during
constexpr
initialization Any implicit conversions invoked are also constant expressions) - It must have constant destructor (
C++20
s) , ie:- it is not a class type or a (possibly multidimensional) array of it, or
- It is a class type or its (possibly multidimensional) array, and the class type has a
constexpr
destructor; for a fake expression whose effect is only to destroy the objecte
, if the object is related to itsmutable
non- subobjects (but not itsmutable
child object) whose lifetime beginse
within, thene
is a core constant expression.
If constexpr
a variable is not translation-unit-local, it shall not be initialized to point to or refer to a translation-unit-local entity that can be used in constant expressions, or to have (possibly recursive) subobjects that point to or refer to such entities. Such initialization is prohibited in a module interface unit (outside of its private module section, if present) or module division, and is deprecated inC++20
any other context .
Example:
#include <math.h>
#include <iostream>
using namespace std;
int main() {
constexpr float x = 42.0;
constexpr float y{
108};
constexpr float z = exp(5);
constexpr int i; // error: uninitialized 'const i' [-fpermissive]
//可以使用constexpr运行时的值,但不能在编译时使用运行时变量。
int j = 0;
constexpr int k = j + 1; // error: the value of 'j' is not usable in a constant expression
int kk = x; // ok
x = 100.00002; //error: assignment of read-only variable 'x'
}
modifier function
-
Function must have non-
void
return type -
If any declarations of a function or function template are
constexpr
specified viaconstexpr
constexpr int f1(); // OK, function declaration
int f1() {
// Error, the constexpr specifier is missing
return 55;
}
constexpr
does not guarantee compile-time evaluation; it only guarantees that constant expression arguments can be evaluated at compile-time if the programmer wants to or if the compiler decides to do so for optimization. Ifconstexpr
a function or constructor is called with an argument that is not a constant expression, the call behaves as if the function were notconstexpr
an r function, and the resulting value is not a constant expression.constexpr
Likewise, if an expression in a function return statement does not evaluate to a constant expression in a given call , the result is not a constant expression.
#include <iostream>
using namespace std;
constexpr int min(int x, int y) {
return x < y ? x : y; }
void test(int v) {
int m1 = min(-1, 2); // 可能是编译时评估
constexpr int m2 = min(-1, 2); // 编译时评估
int m3 = min(-1, v); // 运行时评估
// constexpr int m4 = min(-1, v); //error: 'v' is not a constant expression
}
int main() {
return 0;
}
mov DWORD PTR [rbp-4], -1
mov DWORD PTR [rbp-8], -1
mov eax, DWORD PTR [rbp-20]
mov esi, eax
mov edi, -1
call min(int, int)
mov DWORD PTR [rbp-12], eax
constexpr
Meaning that the function must be of a simple form such that, if given a constant expression argument, it can be evaluated at compile time, the function body may consist only of declarations, statements,null
and singlereturn
statements. A parameter value must be present such that after parameter substitution,return
the expression in the statement produces a constant expression.
factorial example
#include <iostream>
#include <vector>
using namespace std;
constexpr int fac(int n) {
constexpr int max_exp = 2;
if (0 <= n && n < max_exp) return 1;
int x = 1;
for (int i = 2; i <= n; ++i) x *= i;
return x;
}
int main() {
int ddd = fac(5);
cout << " ddd = " << ddd << endl;
}
The assembly code part is as follows:
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 120
//c++11的实例 constexpr 函数必须把一切放在单条 return 语句中
#include <iostream>
constexpr long long factorial(long long n) {
return (n == 0) ? 1 : n * factorial(n - 1);
}
int main() {
char test[factorial(3)];
std::cout << factorial(7) << '\n';
}
//c++14的实例 无此要求
#include <iostream>
#include <utility>
constexpr long long factorial(int n) {
long long result = 1;
for (int i = 1; i <= n; ++i) {
result *= i;
}
return result;
}
constexpr long long factorial1(long long n) {
if (n == 0)
return 1;
else
return n * factorial(n - 1);
}
int main() {
std::cout << factorial(9) << " " <<factorial1(9) << std::endl; }
Consider
enum Flags {
good=0, fail=1, bad=2, eof=4 };
constexpr int operator|(Flags f1, Flags f2) {
return Flags(int(f1)|int(f2)); }
void f(Flags x)
{
switch (x) {
case bad: /* ... */ break;
case eof: /* ... */ break;
case bad|eof: /* ... */ break;
default: /* ... */ break;
}
}
- In addition to being able to evaluate expressions at compile time, we also want to be able to ask expressions to be evaluated at compile time; the preceding variable definition
constexpr
does this (and impliesconst
):
constexpr int x1 = bad|eof; // ok
void f(Flags f3)
{
constexpr int x2 = bad|f3; // error: can't evaluate at compile time
int x3 = bad|f3; // ok
}
Often we want compile-time evaluation guarantees on global objects or namespace objects (usually objects we want to put in read-only storage).
decorated constructor
To construct a constant expression data value from a user-defined typeconstexpr
, you can use a declared constructor. constexpr
The function body of a constructor can only contain declarations and null
statements, and cannot constexpr
declare variables or define types like functions. Parameter values must be present for members of the class to be initialized with constant expressions after parameter substitution. Destructors for these types must be trivial.
=delete;
Constructors whose function body is not constexpr
must meet the following additional requirements:
- For class or struct constructors, each subobject and each non-variant non-static data member must be initialized. If the class is a union-style class, then for each of its non-null anonymous union members, exactly one variant member must be initialized(
C++20
ex) - For constructors of non-null unions, exactly one non-static data member is initialized(
C++20
ex) - Every constructor chosen to initialize non-static members and base classes must be a constexpr constructor.
constexpr
When modifying the constructor of a class, it is required that the function body of the constructor must be empty, and when using the initialization list to assign values to each member, constant expressions must be used.
Unless declared as=delete
,constexpr
the function body of the constructor is generally empty, andconstexpr
all data members are initialized using the initialization list or other constructors.
Aconstexpr
constructor is implicitly inlined.
struct Point{
constexpr Point(int _x, int _y)
:x(_x),y(_y){
}
constexpr Point()
:Point(0,0){
}
int x;
int y;
};
constexpr Point pt = {
10, 10};
For a type with any constexpr
constructors, the copy constructor should usually also be defined as a constexpr
constructor, to allow objects of that type constexpr
to be returned by value from the function. Any member function of a class, such as a copy constructor, operator overloading, etc., constexpr
can be declared as long as it meets the requirements of the function constexpr
. This allows the compiler to copy objects, perform operations on them, etc. at compile time.
An example of using constexpr
a constructor is as follows:
- Example 1
#include <iostream>
using namespace std;
struct BASE {
};
struct B2 {
int i;
};
// NL is a non-literal type.
struct NL {
virtual ~NL() {
}
};
int i = 11;
struct D1 : public BASE {
// OK, BASE的隐式默认构造函数是一个constexpr构造函数。
constexpr D1() : BASE(), mem(55) {
}
// OK, BASE的隐式复制构造函数是一个constexpr构造函数。
constexpr D1(const D1& d) : BASE(d), mem(55) {
}
// OK, 所有引用类型都是文字类型。
constexpr D1(NL& n) : BASE(), mem(55) {
}
// 转换运算符不是constexpr.
operator int() const {
return 55; }
private:
int mem;
};
// 不能是虚继承
// struct D2 : virtual BASE {
// //error, D2 must not have virtual base class.
// constexpr D2() : BASE(), mem(55) { }
// private:
// int mem;
// };
struct D3 : B2 {
// error, D3 必须不能包含try语句块
// constexpr D3(int) try : B2(), mem(55) { } catch(int) { }
// error: 'constexpr' 构造函数内不能包含任何语句
// constexpr D3(char) : B2(), mem(55) { mem = 55; }
// error, initializer for mem is not a constant expression.
constexpr D3(double) : B2(), mem(i) {
}
// error, 隐式转换不是一个 constexpr.
// constexpr D3(const D1 &d) : B2(), mem(d) { }
// error: invalid type for parameter 1 of 'constexpr' function 'constexpr D3::D3(NL)'
// constexpr D3(NL) : B2(), mem(55) { }
private:
int mem;
};
- Example 2
int main() {
constexpr D3 d3instance(100);
cout << " D3 i " << d3instance.i;
return 0;
}
struct Point {
int x,y;
constexpr Point(int xx, int yy) : x(xx), y(yy) {
}
};
constexpr Point origo(0,0);
constexpr int z = origo.x;
constexpr Point a[] = {
Point(0,0), Point(1,1), Point(2,2) };
constexpr int x = a[1].x; // x becomes 1
constexpr int f1(); // OK, function declaration
int f1() {
// Error, the constexpr specifier is missing
return 55;
}
Modified template function
C++11
In syntax, constexpr
template functions can be modified, but due to the uncertainty of the type in the template, it is also uncertain whether the instantiated function of the template function meets the requirements of the constant expression function .
Example 1:
#include <iostream>
using namespace std;
template <typename Type>
constexpr Type max111(Type a, Type b) {
return a < b ? b : a;
}
int main() {
int maxv = max111(11, 5);
cout << maxv << endl;
string maxv1 = max111("12", "22");
cout << maxv1 << endl;
return 0;
}
The assembly code is as follows:
Example 2:
Same at compile time, but two different function calls at runtime.
#include <iostream>
using namespace std;
template <int i>
auto f() {
if constexpr (i == 0)
return 10;
else
return std::string("hello");
}
int main() {
cout << f<0>() << endl; // 10
cout << f<1>() << endl; // hello
}
The compiled output is as follows:
#include <iostream>
using namespace std;
template<int i>
auto f()
{
if constexpr(i == 0) {
return 10;
} else /* constexpr */ {
return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >("hello", std::allocator<char>()));
}
}
#ifdef INSIGHTS_USE_TEMPLATE
template<>
int f<0>()
{
if constexpr(true) {
return 10;
}
}
#endif
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > f<1>()
{
if constexpr(false) {
} else /* constexpr */ {
return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >("hello", std::allocator<char>()));
}
}
#endif
int main()
{
std::cout.operator<<(f<0>()).operator<<(std::endl);
std::operator<<(std::cout, f<1>()).operator<<(std::endl);
return 0;
}
constexpr
Lambdas (c++17)
illustrate
constexpr
Explicitly specifies that the function call operator or any given operator template specialization is aconstexpr
function.constexpr
When this specifier is absent, it will be if the function call operator or any given operator template specialization happens to satisfy all function requirementsconstexpr
.
using F = ret(*)(params);
constexpr operator F() const noexcept;
template<template-params> using fptr_t = /*see below*/;
template<template-params>
constexpr operator fptr_t<template-params>() const noexcept;
lambda
This user-defined conversion function is defined only if the expression's capture list is empty . public
It is a ( constexpr
since C++17) non-virtual, non-explicit, const noexcept
member function of the closure object .
use
lambda
An expression may be declared as or used in a constant expression when initialization of each data member it captures or introduces isconstexpr
permitted in the constant expression.
#include <iostream>
#include <numeric>
using namespace std;
constexpr int Increment(int n) {
return [n] {
return n + 1; }();
}
int main() {
int y = 32;
auto answer = [&y]() {
int x = 10;
return y + x;
};
constexpr int incr = Increment(100);
cout << "Increment = " << incr; //101
return 0;
}
- It is implicit if
lambda
the result of satisfies the function's requirements :constexpr
constexpr
#include <iostream>
#include <vector>
using namespace std;
int main() {
std::vector<int> c = {
1, 2, 3, 4, 5, 6, 7};
auto answer = [](int n) {
return 32 + n; };
constexpr int response = answer(10);
cout << " response = " << response << endl; // response = 42
}
The assembly code is as follows:
mov DWORD PTR [rbp-80], 1
mov DWORD PTR [rbp-76], 2
mov DWORD PTR [rbp-72], 3
mov DWORD PTR [rbp-68], 4
mov DWORD PTR [rbp-64], 5
mov DWORD PTR [rbp-60], 6
mov DWORD PTR [rbp-56], 7
lea rax, [rbp-80]
mov r12, rax
mov r13d, 7
lea rax, [rbp-37]
mov rdi, rax
call std::allocator<int>::allocator() [complete object constructor]
lea rdx, [rbp-37]
mov rsi, r12
mov rdi, r13
mov rcx, r12
mov rbx, r13
mov rdi, rbx
lea rax, [rbp-112]
mov rcx, rdx
mov rdx, rdi
mov rdi, rax
call std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&) [complete object constructor]
lea rax, [rbp-37]
mov rdi, rax
call std::allocator<int>::~allocator() [complete object destructor]
mov DWORD PTR [rbp-36], 42
- If one
lambda
is implicit or explicitconstexpr
and converts it to a function pointer, the resulting function is alsoconstexpr
:
#include <iostream>
#include <vector>
using namespace std;
auto Increment = [](int n) {
return n + 1; };
constexpr int (*inc)(int) = Increment;
int main() {
constexpr int response = inc(22);
cout << " response = " << response << endl;
}
The assembly code is as follows:
Increment::{
lambda(int)#1}::operator()(int) const:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov DWORD PTR [rbp-12], esi
mov eax, DWORD PTR [rbp-12]
add eax, 1
pop rbp
ret
Increment::{
lambda(int)#1}::_FUN(int):
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, 0
call Increment::{
lambda(int)#1}::operator()(int) const
leave
ret
Increment:
.zero 1
.LC0:
.string " response = "
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 23
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov esi, 23
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
mov eax, 0
leave
ret
Constexpr example in c++11
const int array_size1 (int x) {
return x+1;
}
// Error, constant expression required in array declaration
int array[array_size1(10)];
constexpr int array_size2 (int x) {
return x+1;
}
// OK, constexpr functions can be evaluated at compile time
// and used in contexts that require constant expressions.
int array[array_size2(10)];
struct S {
S() {
}
constexpr S(int) {
}
constexpr virtual int f() {
// Error, f must not be virtual.
return 55;
}
};
struct NL {
~NL() {
} // The user-provided destructor (even if it is trivial)
// makes the type a non-literal type.
};
constexpr NL f1() {
// Error, return type of f1 must be a literal type.
return NL();
}
constexpr int f2(NL) {
// Error, the parameter type NL is not a literal type.
return 55;
}
constexpr S f3() {
return S();
}
enum {
val = f3() }; // Error, initialization of the return value in f3()
// uses a non-constexpr constructor.
constexpr void f4(int x) {
// Error, return type should not be void.
return;
}
constexpr int f5(int x) {
// Error, function body contains more than
if (x<0) // return statement.
x = -x;
return x;
}
'C++' has always had the concept of constant expressions. These expressions (like 3 + 4 ) always produce the same result at compile time and at run time. Constant expressions are an optimization opportunity for the compiler, which often executes constant expressions at compile time and hardcodes the result into the program. Also, in some places, the C++ specification requires the use of constant expressions. A constant expression is required to define an array, and the enumeration value must be a constant expression.
However, constant expressions are never allowed to contain function calls or object constructors. A simple piece of code like this is invalid:
int get_five() {
return 5;}
int some_value[get_five() + 7]; // Create an array of 12 integers. Ill-formed C++
This C++03
is not valid in because get_five() + 7
it is not a constant expression. C++03
The compiler has no way of knowing get_five()
whether it is constant at runtime . In theory, this function could affect a global variable, call other non-runtime constant functions, etc.
C++11
The keyword was introduced constexpr
, which allows the user to guarantee that a function or object constructor is a compile-time constant. The above example can be rewritten as follows:
constexpr int get_five() {
return 5;}
int some_value[get_five() + 7]; // Create an array of 12 integers. Valid C++11
This allows the compiler to understand and verify get_five()
that is a compile-time constant.
constexpr
with const
the difference
- The purpose is different.
constexpr
Primarily used for optimizations, butconst
actually forconst
objects, e.g.Pi
values. They can both be applied to member methods. Make member methodsconst
to ensure no accidental changes in methods.constexpr
The idea is to useEvaluate expressions at compile time, to save time when running the code .
const
Can only be used with non-static member functions, whileconstexpr
can be used with member functions and non-member functions, even constructors, provided that the parameter and return types must be literal types.
- Functions differently:
constexpr
is notconst
a generic replacement for (and vice versa):
const
The main function of is to express the idea of not modifying an object through an interface (even though the object is likely to be modified through other interfaces). Declaring an object constant happens to provide the compiler with excellent optimization opportunities. In particular, if an object is declared asconst
and its address is not taken, the compiler is usually able to calculate its initializer at compile time (although this is not guaranteed), and keep the object in its table instead of sending into the generated code.constexpr
The main function of is to extend the range of what can be computed at compile time, making such computations type-safe .constexpr
The initial values of declared objects are computed at compile time; they are basically values held in compiler tables and sent to the generated code only when needed.
The basic differences when applied to objects are:
const
Declare the object as a constant. This means that the value of the object is guaranteed not to change once initialized, and the compiler can take advantage of this fact for optimizations. It also helps prevent programmers from writing code to modify objects that should not be modified after initialization.constexpr
declares an object suitable for what a standard statement calls a constant expression. . Note, however,constexpr
that's not the only way to do this.const
constexpr
The main difference between a variable and a variable is that the initializationconst
of a variable can be deferred until runtime .constexpr
Variables must be initialized at compile time . Allconstexpr
variables areconst
.
When applied to functions, the basic differences are:
const
Can only be used for non-static member functions, not for general functions. It guarantees that member functions do not modify any non-static data members (except mutable data members, which can be modified anyway).constexpr
Can be used for both member functions, non-member functions, and constructors. It declares that the function is suitable for use in constant expressions. The compiler will only accept a function if it satisfies certain conditions, most importantly:- The function body must be non-virtual and very simple: only one return statement is allowed except for
typedefs
and .static assert
For constructors, only initialization lists,typedefs
and static assertions are allowed. (= default
And= delete
are also allowed.) - In , the rules are more relaxed, and declarations , statements, statements with labels other than and , , definitions of variables of non-literal type, definitions of static or thread storage duration variables, which do not perform initialization, are allowed in functions from
c++14
then on The definition of the variable.constexpr
:asm
goto
case
default
try-block
- Parameter and return types must be literal types (i.e., generally speaking, very simple types, usually scalars or aggregates)
- The function body must be non-virtual and very simple: only one return statement is allowed except for
const
Can be initialized by any function, but initialization constexpr
by a non constexpr
(function not marked with constexpr
a or non- constexpr
expression) will generate a compiler error.
const int function5(){
return 100;} // 返回一个常数
constexpr int function8(){
return 100;}
int main(){
constexpr int temp5 = function5();// error: call to non-'constexpr' function 'const int function5()'
constexpr int temp6 = funtion8(); //ok
const int temp7 = function8(); //ok
return 0;
}
#include <iostream>
template <typename T>
void f(T t) {
static_assert(t == 1, "");
}
constexpr int one = 1;
int main() {
f(one);
return 0;
}
gcc
The compiled output is as follows:
<source>: In instantiation of 'void f(T) [with T = int]':
<source>:10:6: required from here
<source>:5:21: error: non-constant condition for static assertion
5 | static_assert(t == 1, "");
| ~~^~~~
<source>:5:21: error: 't' is not a constant expression
inside f
the body of , t
is not a constant expression, so it cannot be used static _ assert
as an operand of ( static_assert
a constexpr
constant bool
expression is required for compile-time assertion checking). The reason is that the compiler simply cannot generate a function like this
template <typename T>
void f1(constexpr T t) {
static_assert(t == 1, "");
}
int main() {
constexpr int one = 1;
f1(one);
return 0;
}
The gcc compilation error output is as follows:
<source>:4:9: error: a parameter cannot be declared 'constexpr'
4 | void f1(constexpr T t) {
| ^~~~~~~~~
<source>: In function 'int main()':
<source>:9:8: error: 'one' was not declared in this scope
9 | f1(one);
| ^~~
The fact that the actual parameter is not a constant expression means that we cannot use it as a non-type template parameter, as an array binding parameter, in static_assert
or other objects that require a constant expression.
To preserve idiosyncrasies through parameter passing , the value constexpr
must be encoded as a type, and then an object of that type, not necessarily an object, must be passed to the function. The function must be a template and then have access to the value encoded in that type .constexpr
constexpr
constexpr
constexpr int sqrt(int i) {
if (i < 0) throw "i should be non-negative";
return 111;
}
constexpr int two = sqrt(4); // ok: did not attempt to throw
constexpr int error = sqrt(-4); // error: can't throw in a constant expression
gcc
The compiled output is as follows:
<source>:14:27: in 'constexpr' expansion of 'sqrt(-4)'
<source>:10:14: error: expression '<throw-expression>' is not a constant expression
10 | if (i < 0) throw "i should be non-negative";
#include <iostream>
template <typename T>
constexpr int f(T& n, bool touch_n) {
if (touch_n) n + 1;
return 1;
}
int main() {
int n = 0;
constexpr int i = f(n, false); // ok
constexpr int j = f(n, true); // error
return 0;
}
gcc
The compiled output in c++11
standard is as follows:
<source>: In function 'int main()':
<source>:11:24: error: 'constexpr int f(T&, bool) [with T = int]' called in a constant expression
11 | constexpr int i = f(n, false); // ok
| ~^~~~~~~~~~
<source>:4:15: note: 'constexpr int f(T&, bool) [with T = int]' is not usable as a 'constexpr' function because:
4 | constexpr int f(T& n, bool touch_n) {
| ^
<source>:12:24: error: 'constexpr int f(T&, bool) [with T = int]' called in a constant expression
12 | constexpr int j = f(n, true); // error
| ~^~~~~~~~~
#include <iostream>
template <typename T>
constexpr int f(T n) {
return 1;
}
int main() {
int n = 0;
constexpr int i = f(n);
return 0;
}
The only difference from our initial scenario is that f
parameters are now accepted by value instead of reference. However, this makes a world of difference. In effect, we now ask the compiler to make a copy n
, and pass this copy to f
. However, n
no constexpr
, so its value is only known at runtime. How does the compiler copy (at compile time) a variable whose value is only known at runtime? Of course not.
<source>: In function 'int main()':
<source>:10:26: error: the value of 'n' is not usable in a constant expression
10 | constexpr int i = f(n);
| ^
<source>:9:9: note: 'int n' is not const
9 | int n = 0;
|
postscript
- Don't try to make all the function declaration bits
constexpr
. Most calculations are best done at runtime. - Anything that might end up relying on advanced runtime configuration or business logic
API
should not be usedconstexpr
. The compiler cannot evaluate this customization, and any functions that depend on it must be refactored orAPI
removed .constexpr
constexpr
constexpr
The compiler will error out if you call a non-function where a constant is expected .
reference
[1] Constant expression
[2] What's the difference between constexpr and const?
[3] Appendix I: Advanced constexpr
[4] When to Use const vs constexpr in C++
[5] constexpr specifier (since C++11)
[ 6] CppCoreGuidelines F.4
[7] constexpr-cpp.md