C++之模板元编程(前置知识 constexpr)
一、顶层const和底层const在C语言我们也遇见过这样的知识毕竟常考嘛算是老朋友了顶层const修饰变量本身变量不能被修改底层const修饰指向 / 引用的内容内容不能被修改大多数对象被const修饰都叫顶层const指针被const修饰时*左边的const叫底层const*右边的const叫做顶层const。注意const修饰引用时这个const是底层const例子int main() { int i 0; int* const p1 i; // 顶层const const int ci 42; // 顶层const const int* p2 ci; // 底层const const int r ci; // 底层const return 0; }二、constexpr2.1 constexpr介绍constexpr 是C11 引入、C14/17/20 大幅增强的关键字全称constant expression常量表达式。constexpr用于指定常量表达式。它允许在编译时计算表达式的值从而提高运行时性能并增强类型安全性。常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式字面值、常量表达式初始化的const对象都是常量表达式要注意变量初始化的const对象不是常量表达式因为变量初始化的const对象要在运行时才确定int main() { int x 5; //const int i x; // 错误用变量初始化的表达式不是常量表达式会导致语句constexpr int j i报错 const int i 1; constexpr int j i; return 0; }2.2 constexpr可以修饰的对象之前说过constexpr 允许编译时计算表达式的值可以提高我们的代码运行的性能那么constexpr可以作用在哪些上呢2.2.1 修饰变量constexpr可以修饰变量但是其修饰的变量的等号右边必须是常量表达式而且修饰constexpr修饰变量相当于顶层const比如 constexpr int value 常量表达式例子constexpr int c 3; int main() { int x 5; //const int i x; // 错误用变量初始化的表达式不是常量表达式会导致语句constexpr int j i报错 const int i 1; constexpr int j i; constexpr const int* p3 c; return 0; }注意constexpr可以修饰指针constexpr修饰的指针是顶层const也就是指针本身而且constexpr要修饰指针的话等号右边的地址不能是局部变量必须是全局变量或者static或nullptr类型因为之前我的一篇博客说过全局变量、静态变量存储在内存分布中的全局区/静态区但是局部变量在栈区而且栈区是运行时系统动态分配的每次运行程序、每次函数调用栈地址都可能变所以编译器在编译阶段完全不知道它的地址但是全局区和静态区不同其在编译时就固定好了地址。2.2.2 修饰函数特点constexpr修饰函数func的时候我们调用该函数不一定非得是编译时完成才行运行时也可以完成constexpr修饰普通函数要求函数声明的参数和返回值都是字面值类型(整形、浮点型、指针、引用等)不能有动态内存。函数返回值类型不能是空。要求函数体中只包含⼀条return返回语句意味着不能递归不能定义局部变量循环条件判断等控制流并且返回值必须是常量表达式。constexpr修饰构造函数constexpr不能修饰自定义类型但是用constexpr修饰类的构造函数后可以就可以。该类的所有成员变量必须是字面类型(literaltype)constexpr构造函数必须在初始化列表初始化所有成员变量构造对象实参必须使用常量表达式函数体必须为空。struct Point { int x, y; //成员变量必须是字面类型 constexpr Point(int a, int b) : x(a), y(b) //成员变量只能用初始化列表 { // 构造函数体内必须为空 } };constexpr修饰成员函数constexpr成员函数自动成为const成员函数这意味着它们不能修改对象的成员变量其他要求跟普通函数⼀样。成员函数必须同时加const只能return 成员不能有复杂逻辑。另外constexpr成员函数不能是虚函数因为虚函数有虚基表外部调用的时候得在运行时查找对应基类或子类的函数。struct Point { int x, y; constexpr Point(int a, int b) : x(a), y(b) {} constexpr int getX() const { return x; } //必须加const };constexpr 修饰模板函数由于模板中类型的不确定性因此模板函数实例化后的函数是否符合常量表达式函数的要求也是不确定的。C11标准规定如果constexpr修饰的模板函数实例 化结果不满足常量表达式函数的要求则constexpr会被自动忽略即该函数就等同于⼀个普通函数。2.3 样例#includeiostream using namespace std; constexpr int size() { return 10; } constexpr int func(int x) { return 10 x; } int main() { // 编译时N会被直接替换为10constexpr函数默认就是inline constexpr int N1 size(); int arr1[N1]; // func传10时func函数返回值是常量表达式所以N2是常量表达式 constexpr int N2 func(10); int arr2[N2]; int i 10; int N4 func(i);//运行时计算 //constexpr修饰的函数可以有⼀些其他语句但是这些语句运⾏时可以不执⾏任何操作就可以 // 如类型别名、空语句、using声明等 return 0; }三、constexpr随着cpp版本的演进以上constexpr的使用都是基于Cpp11的下面将介绍一下cpp14、17、20中constexpr的变化3.1 Cpp14中constexpr的变化C14最显著的改进是大幅放宽了对constexpr函数的限制使其语法和功能更接近普通函数。函数限制的全面放宽• 局部变量允许声明和初始化局部变量只要在constexpr上下文中使用• 控制流语句支持if条件分支、for/while循环、switch语句等• 多return语句函数体不再限于单⼀return语句• 支持更复杂的返回类型如void返回自定义类、STL容器(std::array)、其他符合constexpr要求的复合类型例1// C14允许的constexpr函数⽰例 constexpr int factorial(int n) { int res 1; // 允许局部变量 for (int i 2; i n; i) { // 允许循环 res * i; } return res;// 单⼀return } constexpr size_t stringLength(const char* str) { size_t len 0; while (str[len] ! \0) len; return len; } constexpr int func(int n) { return n 1 ? 1 : n * factorial(n - 1);//允许递归也就是多个return } constexpr int fact7 factorial(7); // 编译时计算 constexpr size_t len stringLength(Hello); // 编译期计算5 constexpr int fcun5 func(5); // 编译时计算例2支持更复杂的返回类型#includeiostream #includevector #includearray using namespace std; struct Point { constexpr Point(double x, double y) : x(x), y(y) {} double x, y; }; constexpr Point midpoint(Point a, Point b) { return Point((a.x b.x) / 2, (a.y b.y) / 2); } constexpr std::arrayint, 5 createArray() { std::arrayint, 5 arr{}; for (size_t i 0; i arr.size(); i) { arr[i] i * i; } return arr; } constexpr int fibonacci(int n) { return (n 1) ? n : (fibonacci(n - 1) fibonacci(n - 2)); } int main() { Point p1 midpoint({ 1.1,1.1 }, { 2.2,2.2 }); constexpr Point p2 midpoint({ 1.1,1.1 }, { 2.2,2.2 }); constexpr std::arrayint, 5 a1 createArray(); constexpr int fibArray[] { fibonacci(0), fibonacci(1), fibonacci(2), fibonacci(3), fibonacci(4), fibonacci(5), fibonacci(6), fibonacci(7) }; return 0; }3.2 Cpp17中constexpr的变化C17对constexpr进行了重大扩展使其能力大幅提升进⼀步模糊了编译时和运行时的界限。引入if constexpr语法if constexpr 是 C17引入的⼀种条件编译语句它允许在编译时根据常量表达式的结果决定编译哪部分代码未选择的分支代码不会编译成指令直接丢弃。constexpr的lambda表达式lambda表达式可标记为constexpr捕获的对象必须是编译期常量函数体需满足constexpr函数要求例1if constexpr语法的使用#includetype_traits templateclass T auto get_value(T t) { if constexpr (std::is_integral_vT) { return 1; } else return 1.0; } int main() { get_value(1); }由于auto是自动推导返回值类型Cpp14引入的但是我们在编译时如果用if elsereturn返回的类型不一致一个是int一个是浮点数所以我们编译时会报错但是我们把if替换成if constexpr的话我们会在编译时确定具体的分支比如get_value11是int类型从而编译时else的代码不会编译进去从而导致return只有一个返回值编译通过如果是普通的if else 两个分支都要实例化、都编译。例2constexpr的lambda表达式的使用int main() { constexpr int n 10; int y 0; constexpr auto square [n](int x) constexpr { return x * x * n; }; constexpr int result square(5); // 编译期计算250 return 0; }3.3 Cpp20中constexpr的变化C20标准对constexpr关键字进行了革命性的增强将编译期计算能力提升到了前所未有的高度。这些改进不仅大幅扩展了constexpr的应用范围还使其成为现代C元编程和性能优化的核心⼯具。constexpr 支持 new /delete动态内存constexpr 支持 std::string /std::vectorconstexpr 支持 虚函数virtualconstexpr 支持 堆、try/catch 异常constexpr 析构函数例1constexpr int func() { int* p new int(10); // ✅ C20 允许 int val *p; delete p; // 必须配对 return val; } constexpr int res func(); // 编译期分配内存 constexpr string s hello; // ✅ C20 constexpr vectorint v{1,2,3};// ✅ C20 // 支持虚函数 struct Base { constexpr virtual int f() { return 1; } }; //支持try-catch但是这里不能抛出真正的异常因为不是常量表达式 constexpr int func() { try { throw 1; } catch(...) { return 2; } } constexpr int res func(); // ✅ C20同时constexpr还引入了可变(mutable)成员。constexpr成员函数中成员变量是不能修改的但是我们定义成员变量时加上mutable修饰这个成员变量在constexpr成员函数中就可以修改了。样例class A { mutable int _i; int _j; public: constexpr A(int i, int j) :_i(i) , _j(j) {} constexpr int Func1() const { _i;// 可以修改 // _j; // 不能修改 return _i _j; } constexpr int Func2() const { return _i _j; } }; int main() { constexpr A aa(1, 1); int ret1 aa.Func1();// 这⾥不能加constexpr因为Func1修改了_iFunc1已经⽆法做到编译时计算了。 constexpr int ret2 aa.Func2(); return 0; }同时C20还引入了constexpr的两个兄弟一个是consteval,要求是只能在编译时执行计算另一个是constinit感兴趣的自己问ai了解一下