编程语言 cpp C++ 11拓展学习 星野暗涌 2026-05-08 2026-07-04 前面我们对C++有了一些学习,但是C++11 standars对于C++本身来说是一个机器重要的版本的更新,其中还有很多有趣的知识需要我们继续学习。 🌈 😋
1. const 与constexpr 1.1 顶层const和底层const
指针本身就是一个对象,他可以指向另一个对象,因此指针就涉及到了本身是不是 **const**和被指向的对象是不是 **const**的问题, C++为了很好的区分这个,就把本身被const修饰的对象成为**顶层const**, 把指向对象被 **const**修饰的叫做 **底层const。**
大多数的对象被const修饰都是等层const, 指针被const修饰时, ***左边的const叫底层const,*右边的const叫做顶层const**。
const修饰引用时,这个const就是底层const
1 2 3 4 5 6 7 8 9 int main () { int i = 0 ; int * const p1 = &i; const int ci = 42 ; const int * p2 = &ci; const int & r = ci; return 0 ; }
1.2 constexpr表达式
常量表达式是指值不会改变并且在编译过程中就可以得到结果的表达式, 字面量、常量表达式初始化的const对象都是常量表达式,要注意变量的const初始化不是常量表达式。
constexpr(constant expression)是C++11引入的一个关键字,用于指定常量表达式的值。他允许在变异时期就得出表达式的值,从而提高运行时效率并增加类型的安全性!
constexpr可以修饰指针,但是constexor修饰的是顶层指针,也就是指针本身。
constexpr可以稀释变量,constexpr修饰的变量一定是常量表达式,且必须用常量表达初始化!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;int size () { int n = 10 ; return n; } int main () { const int a = 1 ; const int b = a + 1 ; int c = 1 ; const int d = c; const int e = size (); int arr[a]; constexpr int aa = 1 ; constexpr int bb = aa + 1 ; const int *p2 = &d; constexpr const int *p3 = &d; return 0 ; }
1.3 constexpr函数
要求函数声明的参数和返回值多是字面值类型(整形、浮点数类型、指针、引用等 )**,**函数的返回值不能为空,只包含一条return语句,不能定义局部变量,循环判断等控制流,并且返回值必须是常量值类型!
constexpr不能修饰自定义类型,但是用constexpr修饰类的构造函数是可以的。但是 该类的所有成员变量必须是字面两类型(literal type),constexpr构造函数必须在构造函数中初始化所有的成员变量!构造对象的 实参必须是常量表达式,函数体必须为空,析构函数必须是不做任何的实际清理工作!
constexpr可以修饰模板函数,但由于模板的不确定性质,因此模板实例化之后是否符合常量表达式的函数要求未知。在C++11标准规定,如果constexpr修饰的模板函数实例化结果不满足常量表达式的要求,则**constexpr****会被自动忽略,**该函数就和普通的函数一样。
constexpr函数自动变成const成员函数,这意味着他们不能再作用域内修改类的成员变量,其他的和普通的成员函数一致。另外,consexpr不能是虚函数
在 C++11 标准的时候,constexpr还是有诸多限制,但是到了后面的 C++14/C++17 等版本会逐渐放宽限制,在后面模板元编程的概念也会逐步出现。
1.4 constexpr在C++14中的演进 C++14 中显著放宽了对constexpr的函数限制,使其在语法上更加接近普通函数。
函数限制的全面放宽
局部变量:允许声明和初始化局部变量 (只要在constexpr上下文使用);
控制流语句: 支持if条件分支、 for/while/switch等控制分支
多return语句的支持
支持更加复杂的返回类型值: 如void类型,自定义类、STL容器(std::array)、其他符合constexpr要求的符合类型
代码演示:
支持更加复杂的返回类型 c++14允许constexpr函数返回非基本类型, 包括自定义类型、STL容器std::array, 其他符合要求的符合类型
代码演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <vector> #include <array> 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::array<int , 5> createArray () { std::array<int , 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::array<int , 5> a1 = createArray (); constexpr int fibArray[] = { fibonacci (0 ), fibonacci (1 ), fibonacci (2 ), fibonacci (3 ), fibonacci (4 ), fibonacci (5 ), fibonacci (6 ), fibonacci (7 )}; for (auto v : fibArray) { std::cout << v << " " ; } cout << endl; return 0 ; }
就如c++ 专家评论的一般,c++14将constexpr 从一个受限制的语法标记转变为一个真正的编译期编程工具, 开启了c++的元编程时代,这些改变在后续的c++17、c++20中更强大的constexptr功能奠定了基础,使得编译计算成为了现代C++不可或缺的核心特征。
1.5 constexpr在C++17中的演进 C++17在constexpr中进行了重大的扩展使得他的能力有了重大的拓展,进一步的模糊了编译时和运行时的界限!
if-constexpr编译期条件分支if constexpr是c++17中引入的重要·编译语句,它允许在编译时期根据常量的表达式结果来判断决定需要编译那一部分的代码,没有被选择的代码则直接抛弃不参与到编译的过程中! (这里我们只是简单看看。到了后面c++17的章节我们会详细了解)
constexpr lambda表达式
lambda表达式可以标记为constexpr
捕获的必须是编译器的常量!
函数体需满足constexpr函数的要求
1.6 constexpr在C++20中的演进 c++20 标准在constexpr关键字上做出了革命性的演进,将编译期计算能力提高到了前所未有的高度!
这些改进不仅大幅度的扩展了constexpr的使用场景,还其成为现代c++ 元编程和性能优化的核心工具。下面将从多个维度解析c++20中constexpr关键的演进及其深远的影响。
动态分配内存的编译支持
new/delete支持:允许在constexpr上下文中进行动态内存管理
编译期容器: 使得std::vectorstd::string在编译期实现成为了可能
内存的生命周期:所有的分配的内存必须在编译期被释放
在c++的标准库上面慢慢的也在向这一标准靠齐:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <vector> #include <array> #include <string> #include <algorithm> using namespace std;constexpr auto sort_example () { std::array<int , 5> arr{ 5 , 3 , 4 , 1 , 2 }; std::sort (arr.begin (), arr.end ()); return arr; } int main () { constexpr array<int , 10> a1 = { 3 ,2 ,1 ,4 ,5 }; vector<int > v2 = {3 ,2 ,1 ,4 ,5 }; sort (v2. begin (), v2. end ()); auto it1 = find (v2. begin (), v2. end (), 3 ); constexpr auto sorted = sort_example (); constexpr auto it2 = find (a1. begin (), a1. end (), 4 ); static_assert (*it2 == 4 , "编译期查找" ); return 0 ; }
try-catch的全面支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using namespace std;constexpr int safe_divide (int a, int b) { try { if (b == 0 ) throw "Division by zero" ; else return a / b; } catch (...) { return 0 ; } } int main () { constexpr int val1 = safe_divide (10 , 2 ); constexpr int val2 = safe_divide (10 , 0 ); }
constexpr联合体(union)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 constexpr union Data { int i; float f; constexpr Data (int val) : i(val) { } constexpr Data (float val) : f(val) { } }; int main () { constexpr Data d1 (42 ) ; constexpr Data d2 (3.14 ) ; constexpr temp1 = d1. i; constexpr temp2 = d2. i; }
constexpr可变(mutable)成员constexpr成员函数中,成员变量不可修改,三十定义成员变量时,加上mutable修饰,这个成员变量就可以在constexpr函数中修改了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> class A { mutable int _i; int _j; public : constexpr A (int i, int j) : _i(i) , _j(j) { } constexpr int Func () const { ++_i; return _i + _j; } }; int main () { constexpr A aa (1 , 1 ) ; std::cout << aa.Func () << std::endl; return 0 ; }
constexpr对虚函数的支持之前虚函数是不支持定义为虚函数的,在c++20开始就支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> class Base {public : virtual constexpr int value () const { return 1 ; } }; class Derived : public Base {public : constexpr int value () const override { return 2 ; } }; constexpr int get_value (const Base& b) { return b.value (); } int main () { constexpr int ret1 = get_value (Base ()); std::cout << "ret1: " << ret1 << std::endl; constexpr int ret2 = get_value (Derived ()); std::cout << "ret2: " << ret2 << std::endl; return 0 ; }
1.7 c++20的consteval
constexpr(常量表达式)的核心思想是允许在编译器进行计算。被他修饰的函数,编译器会验证其是否可以在编译器求值。如果可以,他很可能就在编译器就计算好结果;但如果上下文要求他在运行时计算,他可以像一个函数一样运行。所以
也就是说constexpr函数可以在编译期运行,也可以在运行期间运行,他是否在编译期间求值,取决于他的上下文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> constexpr int square (int x) { return x * x; } int main () { constexpr int const_val = square (10 ); int array[const_val]; int runtime_input = 5 ; int runtime_val = square (runtime_input); std::cout << "Square of " << runtime_input << " is " << runtime_val << std::endl; return 0 ; }
consteval就是为了解决这个不确定性而诞生的。consteval的核心思想是创建一个编译期求值函数,它被称为立即函数 ,他比constexpr更加的严谨,他修饰的函数必须在编译器求值和调用。如果一个 **consteval****函数不能在编译期求值,那么程序将无法编过。**将上面的函数改用consteval修饰就无法通过!
1.8 C++20的constinit constinit特性
constinit:必须在编译时初始化,但是他的值在运行时可以改变
constexpr:必须在编译时初始化,并且其值整个生命周期内不可变
constexpr和constinit都可以修饰变量,他们的核心是初始化时机和可变性
constinit只能运用于具有静态存储期和线程存储期的变量(如全局变量、 **static**变量、 **thread_local**变量), 不能用于函数内的变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> consteval int square (int n) { return n * n; } constexpr int compute_value () { return 42 ; } constinit int global = compute_value (); constinit int squared_value = square (5 ); class ComplexInit { int value; public : constexpr ComplexInit (int v) : value(v) { } }; constinit ComplexInit obj{ 42 }; int main () { squared_value = 30 ; }
1 2 3 4 5 6 int a = 42 ;extern int a;int b = a;
constinit在需要确保编译时初始化但不要求变量保持为常量的场景下非常的有用,特备是大型的项目管理中管理全局变量或者是静态变量的初始化顺序时
1 2 3 4 5 6 constinit int a = 42 ;extern int a;constinit int b = a;
关于模板元编程其实还有很多的东西值得说,到了后面我们会详细的介绍 🌈 😋
2. 处理类型 2.1 auto 前面的课程中我们已经使用过auto了,auto是一个类型说明符,他让编译器替我们分析表达式的类型,auto x = y + z; 编译器自动根据y + z的结果来推导x的类型,在一些比较长的表达式场景下,会有很大的用处。
**auto**的一些特性
编译器推到auto类型的时候,有时候回合初始值得结果不一样,编译器会适当的改变类型,使其更加的符合初始化规则。首先使用引用其实时使用引用的对象,特别是当引用被用作为初始值时,真正参与初始化得其实是引用的值,所以编译器推导 **auto**的时候推导出来的是被引用对象的类型,而不是引用!
auto不能自动的推导出引用类型,所以我们如果想要推导出引用类型就需要显示的指定auto& x = i;
auto不能推导出顶层的const如果想要推导出就需要明确的指出: const auto x = ci
设置一个类型为auto的引用时,初始值中的顶层const属性上仍然保留,否则存在权限放大的问题
一个带有const属性的值初始化auto对象时推导出来的值会忽略掉顶层const, 但是会保留底层const
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 int main () { int i = 0 ; int & ri = i; const int ci = 42 ; int * const p1 = &i; const int * p2 = &ci; const int & ri1 = ci; const int & ri2 = i; auto j = ri; j++; auto k = i; k++; auto r1 = ci; r1++; auto r2 = p1; r2++; auto r3 = p2; auto r4 = ri1; r4++; auto r5 = ri2; r5++; const auto r7 = ci; auto & r8 = ri1; auto & r9 = ri2; auto & r10 = ci; auto & r11 = ri; r11++; return 0 ; }
auto&用于声明一个const的左值引用,只能用于绑定左值,如果出事还对象有const的属性,再进行推到时就会保const的属性,非这会涉及到权限放啊大的问题。
const auto& 是申明一个左值引用, 既可以绑定左值又可以绑定右值,不会修改绑定对象。
auto&& 是万能引用,遵循引用折叠的规则,既可以绑定到右值又可以绑定到左值,初始化表达式自动退到为左值引用和右值引用,如果出事还对象有const属性,推到时就会保留 **const**限定符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <iostream> #include <string> #include <vector> using namespace std;void func (int & x) { cout << "void func(int& x)" << endl; } void func (int && x) { cout << "void func(int&& x)" << endl; } void func (const int & x) { cout << "void func(const int& x)" << endl; } void func (const int && x) { cout << "void func(const int&& x)" << endl; } int main () { int x = 10 ; const int cx = 20 ; auto & rx1 = x; auto & rx2 = cx; func (rx1); func (rx2); const auto & rx3 = x; const auto & rx4 = cx; func (rx3); func (rx4); auto && rx5 = x; auto && rx6 = cx; func (rx5); func (rx6); auto && rx7 = move (x); auto && rx8 = move (cx); func (forward<int >(rx7)); func (forward<const int >(rx8)); return 0 ; }
2.2 尾置类型 尾置返回类型是c++11引入的一种函数声明语法,他允许函数的返回类型放在参数列表之前而不是函数名之前。尾置返回类型的语法这里我们简单做个了解即可,因为C++14引用了auto做返回类型时返回类型就会自动推导,很多时候就不太会用到尾置返回类型了。
基本语法 1 2 3 auto functionName (parameters) -> returnType { }
为什么需要尾置返回类型?
使用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 auto getComplexType () -> std::map<std::string, std::vector<int >> { } template <class T , class U >auto add (T t, U u) -> decltype (u + t) { return t + u; } auto lambda = [](int x) -> doble { return x * 1.5 ; }
2.3 decltype decltype的特性3. 强类型枚举(enum class) C++11引入强制枚举类型(也成为枚举类 enum class), 解决了传统的C++枚举的多个缺点,提供了更好啊的安全性和封装性。
传统的C++枚举类存在的问题 强制类型枚举语法:
1 2 3 4 5 enum class EnumName [: UnderlyingType]{ enumerator1, enumerator2, }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 enum class Color {red, green, bule};enum class TrafficLight {red, yellow, green};enum class SmallEnum : uint8_t {value1, value2}; enum class BigEnum : uint32_t {value1, valu2}; int main () { Color c1 = Color::red; int j = static_cast <int >(Color::red); using enum Color ; Color c = Red; return 0 ; }
4. static_assert static_assert是c++11引入的编译期断言机制 ,它允许开发者在编译期间检查条件是否满足,如果条件不满足,则会直接导致编译错误。static_assert是C++模板元编程中非常有用的工具,它可以帮助开发者在编译期间就捕获错误,提高代码的健壮性!
1 2 3 static_assert (常量表达式, 错误消息)
常见的使用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 template < typename T>void process (T value) { static_assert (std::is_integral<T>::value, "T must be an integral type" ); } constexpr int buffer_size = 1024 ;static_assert (buffer_size > 0 , "Buffer size must be positive" );static_assert (buffer_size % 4 == 0 , "Buffer size must be divisible by 4" );static_assert (sizeof (void *) == 8 , "This code requires 64-bit platform" );static_assert (sizeof (int ) == 4 , "int must be 4 bytes" );
运行时和assert 的区别
特性
static_assert
assert
检查时机
编译时
运行时
影响
编译错误
程序终止
表达式要求
必须为编译时常量表达式
任何表达式
用途
类型检查、编译时常量验证
运行时逻辑验证
5. std::tuple std::tuple是C++11引入的一个模板类,他允许将多个不同类型的值组成一个单一的对象。类似于结构体,但不需要预先定义类型名称。
**std::tuple****(元组), 是一个固定大小的异构值集合,可以包含不同类型的元素。**他是std::pair<int, int>的泛化类型,pair是只能保存两个元祖,而tuple可以保存任意数量的元素。
创建tuple 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <tuple> #include <string> #include <iostream> int main () { std::tuple<int , double , std::string> t1 (10 , 3.14 , "hello" ) ; auto t2 = std::make_tuple (20 , 2.718 , "world" ); std::tuple t3 (30 , 1.618 , "cpp" ) ; std::cout << std::get <0 >(t1) << ", " << std::get <1 >(t1) << ", " << std::get <2 >(t1) << std::endl; return 0 ; }
访问元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <tuple> #include <iostream> int main () { std::tuple<int , double , std::string> t1 (10 , 3.14 , "hello" ) ; std::cout << std::get <0 >(t1) << std::endl; std::cout << std::get <1 >(t1) << std::endl; std::cout << std::get <2 >(t1) << std::endl << std::endl; std::get <0 >(t1) = 100 ; std::cout << std::get <int >(t1) << std::endl; std::cout << std::get <double >(t1) << std::endl; return 0 ; }
解包tuple 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <tuple> #include <iostream> #include <string> int main () { int x; double y; std::string z; std::tie (x, y, z) = t1; auto [a, b, c] = t1; }
6. 模板元编程 现代C++的一个进化方向就是编译时做更多的工作,模板元编程 ( Template Metaprograming, TMP)是C++中一个利用模板机制在编译期进行计算和代码生成的高级技术。它通过模板特化、递归实例化和类型操作,在编译时完成传统运行时才能完成的任务,从而实现零运行时开销的优化!下面我们将从核心概念、关键技术、发展方向等全面讲解C++模板元编程。
📖 模板元编程最早由Erwin Unruh在1994年提出,他展示了如何让编译器在错误信息中输出素数数列随后被Todd Veldhuizen和Vandevoorde等人系统化,Todd Veldhuizen证明了C++模板具有图灵完备 ,理论上可以进行任何计算任务,他遵循函数式编程范式,模板参数作为不可变数据参与计算期计算
6.1 模板元编程的核心概念 6.2 模板元编程的基础语法 6.2.1 基本模板结构 模板元编程主要使用的是类模板(而非函数模板),因为类模板可以包含类型成员和静态成员,再利用模板的特化和递归实现。
1 2 3 4 5 template <class T >struct MyTemplate { using type = T; static const int value = 42 ; };
6.2.2 编译期计算 最简单的模板元编程是编译期计算阶乘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> template <unsigned int N>struct Factorial { static const unsigned int value = N * Factorial<N - 1 >::value; }; template <>struct Factorial <0 >{ static const unsigned int value = 1 ; }; int main () { constexpr unsigned int f5 = Factorial<5 >::value; std::cout << f5 << std::endl; }
6.2.3 编译器类型计算 编译时获取或者是修改类型信息的操作,这个也会引出下面将要详细讲解的类型萃取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <iostream> using namespace std;namespace bit{ template < typename T> struct is_pointer { static constexpr bool value = false ; }; template < typename T> struct is_pointer <T*> { static constexpr bool value = true ; }; template < typename T, typename U> struct is_same { static constexpr bool value = false ; }; template < typename T> struct is_same <T, T> { static constexpr bool value = true ; }; template < typename T> struct remove_const { using type = T; }; template < typename T> struct remove_const <const T> { using type = T; }; template < typename T> struct remove_pointer { using type = T; }; template < typename T> struct remove_pointer <T*> { using type = T; }; template < typename T> struct remove_pointer <T* const > { using type = T; }; void func () { static_assert (is_pointer<int *>::value, "int* is a pointer" ); static_assert (is_same<int , int >::value, "int and int should be the same" ); static_assert (is_same<remove_pointer<int *>::type, int >::value, "int and int should be the same" ); static_assert (is_same<remove_const<const int >::type, int >::value, "int and int should be the same" ); } } int main () { bit::func (); return 0 ; }
6.2.4 类型萃取(type_traits)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <type_traits> std::is_void<void >::value; std::is_integral<int >::value; std::is_floating_point<float >::value; std::is_pointer<int *>::value; std::is_reference<int &>::value; std::is_const<const int >::value; std::is_function<void ()>::value; std::is_member_object_pointer<int (Foo::*)>::value; std::is_compound<std::string>::value; std::is_same<int , int32_t >::value; std::is_base_of<Base, Derived>::value; std::is_convertible<From, To>::value; std::add_const<int >::type; std::add_pointer<int >::type; std::add_lvalue_reference<int >::type; std::remove_const<const int >::type; std::remove_pointer<int *>::type; std::remove_reference<int &>::type; std::conditional<true , int , float >::type; std::conditional<false , int , float >::type; std::result_of<F (Args...)>::type; std::invoke_result<F, Args...>::type; template < class F , class ... Args>using invoke_result_t = typename invoke_result<F, Args...>::type;
C++17为类型萃取添加了_v和_t后缀类型的遍历变量和类型别名
1 2 3 4 5 6 7 8 9 10 11 12 std::is_integral<int >::value; std::remove_const<const int >::type; std::is_integral_v<int >; std::remove_const_t <const int >; template < typename T>inline constexpr bool is_integral_v = is_integral<T>::value;template < typename T>using remove_const_t = typename remove_const<T>::type;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> template < typename T>void process (T value) { if constexpr (std::is_pointer_v<T>) { std::cout << "Processing pointer: " << *value << std::endl; } else if constexpr (std::is_integral_v<T>) { std::cout << "Processing integer: " << value * 2 << std::endl; } else if constexpr (std::is_floating_point_v<T>) { std::cout << "Processing float: " << value / 2.0 << std::endl; } else { std::cout << "Processing unknown type" << std::endl; } } #if defined(_WIN32) #include <winsock2.h> using socket_t = SOCKET;#else using socket_t = int ;#endif template < typename T>void close_handle (T handle) { if constexpr (std::is_same_v<T, SOCKET>) { closesocket (handle); } else { close (handle); } } int main () { int i = 42 ; process (i); process (&i); process (3.14 ); process (std::string ("hello" )); return 0 ; }
6.3 现代C++中对模板元编程特性的增强和优化 前面我们在讲解constexpr的时候就讲解了一些,在这里我们就做一个总结概括,串联起我们前面已经学过的知识;
6.3.1 constexpr函数(C++11起) C++11/C++14/C++17/C++20都逐步增强了constexpr的能力,许多模板元编程任务都可以使用constexpr函数来替代,替代部分传统模板元编程的递归化实现,constexpr简化了很多相对复杂的模板元编程实现。
1 2 3 4 5 constexpr int factorial (int n) { return n <= 1 ? 1 : factorial (n - 1 ) * n; } constexpr int x = factorial (5 );
6.3.2 变量模板 变量模板直接定义编译期的常量值,有了变量模板类型萃取的一些特性就可以简化一些,如is_integral_v:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 C#include <iostream> template < typename T>constexpr T pi = T (3.1415926535897932385 );template < class T >constexpr bool is_integral_v = is_integral<T>::value;int main () { float f = pi<float >; double x = pi<double >; std::cout.precision (6 ); std::cout << "float π: " << f << std::endl; std::cout.precision (10 ); std::cout << "double π: " << x << std::endl; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> template < typename T>constexpr T pi = T (3.1415926535897932385 );template < class T >constexpr bool is_integral_v = is_integral<T>::value;int main () { float f = pi<float >; double x = pi<double >; std::cout.precision (6 ); std::cout << "float π: " << f << std::endl; std::cout.precision (10 ); std::cout << "double π: " << x << std::endl; return 0 ; }
6.3.3 if constexpr(C++17) 在编译期根据条件选择代码路径,避免生成无效代码分支,简化模板特化的复杂逻辑,提升代码的可读性。
1 2 3 4 5 6 7 8 9 10 template <class T>auto process (T value) { if constexpr (std::is_integral_v<T>) { return value * 2 ; }else if constexpr (std::is_floating_point_v<T>){ return value / 2 ; }else { return value; } }
6.3.4 折叠表达式(C++17) 简化可变参数末班的参数包展开操作,具体的细节C++17中进行讲解
1 2 3 4 template <class ... Args>void print (Args&&... args) { (std::cout << ... << args) << 'n'; }
6.3.5 概念(C++20) 这个很重要在介绍C++20时会详细讲解。
6.4 模板元编程的优缺点分析 优点
零运行时开销:所有计算在编译期完成
类型安全:编译期类型检查
高度抽象:可构建灵活通用的库
缺点
编译时间长:复杂的模板实例化会增加编译时间
学习成本增加:很多的模板元编程写法比较晦涩难懂,大大的增加了学习成本
错误信息晦涩难懂:模板的错误常常让人难以理解
调试困难:难以调试编译期计算。