Effective Modern C++ [Practice] -> Whenever possible to use constexpr, use it

  • constexprVariables have constproperties and must be initialized with values ​​known to the compiler.
  • constexprWhen the function is called, if the actual parameter values ​​passed in are known at compile time, the returned result is also constexpra variable, otherwise it returns a non- constexprvariable
  • Functions or variables can be used in a wider context than non- constexprvariables or constexprfunctionsconstexprconstexpr
  • 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, constmodification, and constexprmodification. 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 Cthe C++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 use constor enumdefine well-understood constants, to inlineavoid the overhead of function calls, to templatespecify families of functions and types, to namespaceavoid 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 #definesymbols 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:

  1. #include guardAdd guards to headers (files): Use include guards ( ) with unique names in all header files to prevent unintentional multiple inclusions .
  2. Assertions generally only generate code in debug mode ( NDEBUGwhen the macro is not defined), so they don't exist in release builds.
  3. #ifdefand in conditional compilation if 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 ≥1an 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();
}

constfor what

  1. Modified variable, indicating that the variable cannot be changed;
  2. Modified pointers are divided into pointers to constants and pointer constants;
  3. Constant references are often used for formal parameter types, which avoids copying and modification of values ​​by functions;
  4. 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();

constare our friends: constant values ​​are easier to understand, track, and analyze, so constants should be used instead of variables whenever possible , and should be constthe 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 constthe 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

constVariables 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

  1. 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 .
  2. 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.
  3. 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.
  4. Inline functions can be debugged at runtime , while macro definitions cannot.

shortcoming

  1. ** 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.
  2. Inline functions cannot be upgraded with library upgrades. Changes to inline functions require recompilation , unlike non-inline functions that can be directly linked.
  3. 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?

  1. 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 .
  2. 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).
  3. inline virtualThe only time it can be inlined is when: the compiler knows which class (as in) the object being called is Base::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

constexprmechanism

  • Provides more general constant expressions
  • Allow constant expressions containing user-defined types
  • Provides a way to guarantee that initialization is done at compile time

constexprA 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++14before) constexprimplies both const.
  • Use of specifiers when declaring a function or static member variable( C++17s) constexprimplies at the same time inline. If a declaration of a function or function template has constexpra 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 constexprinitialization 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 constexprdestructor; for a fake expression whose effect is only to destroy the object e, if the object is related to its mutablenon- subobjects (but not its mutable child object) whose lifetime begins ewithin, then eis 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- voidreturn type

  • If any declarations of a function or function template are constexprspecified viaconstexpr

constexpr int f1();  // OK, function declaration
int f1() {
    
               // Error, the constexpr specifier is missing
  return 55;
}     
  • constexprdoes 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. If constexpra function or constructor is called with an argument that is not a constant expression, the call behaves as if the function were not constexpran r function, and the resulting value is not a constant expression. constexprLikewise, 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
  • constexprMeaning 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 single returnstatements. A parameter value must be present such that after parameter substitution, returnthe 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 constexprdoes this (and implies const):
	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 constexprdeclare 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.
    constexprWhen 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, constexprthe function body of the constructor is generally empty, and constexprall data members are initialized using the initialization list or other constructors.
    A constexprconstructor 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 constexprconstructors, the copy constructor should usually also be defined as a constexprconstructor, to allow objects of that type constexprto be returned by value from the function. Any member function of a class, such as a copy constructor, operator overloading, etc., constexprcan 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 constexpra 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++11In 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:
insert image description here

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;
}

constexprLambdas (c++17)

illustrate

constexprExplicitly specifies that the function call operator or any given operator template specialization is a constexpr 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 requirements constexpr.

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;

lambdaThis user-defined conversion function is defined only if the expression's capture list is empty . publicIt is a ( constexprsince C++17) non-virtual, non-explicit, const noexcept member function of the closure object .

use

  1. lambda An expression may be declared as or used in a constant expression when initialization of each data member it captures or introduces is constexprpermitted 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;
}
  1. It is implicit if lambdathe 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
  1. If one lambdais implicit or explicit constexprand converts it to a function pointer, the resulting function is also constexpr:
#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++03is not valid in because get_five() + 7it is not a constant expression. C++03The 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++11The 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.

constexprwith const the difference

  1. The purpose is different.
  • constexprPrimarily used for optimizations, but const actually for constobjects, e.g. Pi values. They can both be applied to member methods. Make member methods constto ensure no accidental changes in methods.
  • constexprThe idea is to useEvaluate expressions at compile time, to save time when running the code .
    insert image description here
  • const Can only be used with non-static member functions, while constexprcan be used with member functions and non-member functions, even constructors, provided that the parameter and return types must be literal types.
  1. Functions differently: constexpris not consta 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 as constand 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.
  • constexprThe main function of is to extend the range of what can be computed at compile time, making such computations type-safe . constexprThe 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:

  • constDeclare 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.
  • constexprdeclares an object suitable for what a standard statement calls a constant expression. . Note, however, constexprthat's not the only way to do this.
  • constconstexprThe main difference between a variable and a variable is that the initialization const of a variable can be deferred until runtime . constexprVariables must be initialized at compile time . All constexprvariables are const.

When applied to functions, the basic differences are:

  • constCan 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).
  • constexprCan 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 typedefsand . static assertFor constructors, only initialization lists, typedefsand static assertions are allowed. ( = defaultAnd = deleteare 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++14then on The definition of the variable.constexpr:asmgotocasedefaulttry-block
    • Parameter and return types must be literal types (i.e., generally speaking, very simple types, usually scalars or aggregates)

constCan be initialized by any function, but initialization constexprby a non constexpr(function not marked with constexpra or non- constexprexpression) 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;
}

gccThe 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 fthe body of , t is not a constant expression, so it cannot be used static _ assert as an operand of ( static_asserta constexprconstant boolexpression 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_assertor other objects that require a constant expression.
To preserve idiosyncrasies through parameter passing , the value constexprmust 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 .constexprconstexprconstexpr

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

gccThe 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;
}

gccThe compiled output in c++11standard 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 fparameters 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, nno 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 APIshould not be used constexpr. The compiler cannot evaluate this customization, and any functions that depend on it must be refactored or API removed . constexpr constexpr
  • constexprThe 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

Guess you like

Origin blog.csdn.net/MMTS_yang/article/details/126344277