总述
- 模板机制-SFINAE - C++模板的核心机制 (重点)
- 模板机制-类型萃取(C++ 11) - 用于在编译时获取和操作类型信息 (重点)
- constexpr关键字 (C++11,持续增强) - 编译时计算和常量表达式 (重点)
- explicit关键字 (C++98/03,C++11增强) - 防止隐式类型转换
- decltype关键字 (C++11) - 类型推导机制
- 返回值后置语法 (C++11) - 函数返回类型的新写法
- std::enable_if -(C++11)模板约束的语法糖
- 变量模板(C++14) - 允许创建参数化的变量,就像函数模板和类模板一样
- 泛型Lambda (C++14) - 支持auto参数的Lambda表达式
- 属性(Attributes) (C++11起) - 编译器提示和优化指令
- 折叠表达式 (C++17) -可变参数模板的强化
1. SFINAE 机制
(1). 概念
SFINAE(Substitution Failure Is Not An Error)是C++模板元编程中的一个重要概念,字面意思是”替换失败不是错误”。它是C++模板系统的一个特性,当编译器在模板实例化过程中进行类型替换时,如果替换失败,编译器不会报错,而是简单地从重载决议候选集中移除该模板。
特点
- 编译时决策:SFINAE在编译时进行类型检查和决策
- 非侵入性:不需要修改现有代码结构
- 类型安全:在编译时确保类型正确性
- 重载决议:影响函数模板的重载决议过程
- 条件编译:实现条件性的模板实例化
(2) . 工作原理
SFINAE的工作原理基于C++模板的两阶段编译过程:
阶段一:模板定义检查
编译器检查模板语法的正确性,但不进行类型替换。
阶段二:模板实例化
当模板被使用时,编译器进行类型替换:
- 成功替换:模板成为重载决议的候选
- 替换失败:模板被静默移除,不产生编译错误
- 重载决议:从剩余候选中选择最佳匹配
具体分析
1 |
|
情况一:T
是整数类型
当调用 add(1, 2)
时,编译器在推导模板参数 T
为 int
后,开始对两个 add
函数模板进行实例化:
- 对于第一个
add
函数模板,std::is_integral<int>::value
为true
,该函数模板实例化成功,成为候选函数。 - 对于第二个
add
函数模板,!std::is_integral<int>::value
为false
,替换失败,根据 SFINAE 规则,编译器不会报错,而是将这个函数模板从候选集中排除。最终,第一个add
函数被调用。
情况二:T
不是整数类型
当调用 add(s1, s2)
时,编译器推导模板参数 T
为 string
,然后进行模板实例化:
- 对于第一个
add
函数模板,std::is_integral<string>::value
为false
,替换失败,根据 SFINAE 规则,该函数模板被从候选集中排除。 - 对于第二个
add
函数模板,!std::is_integral<string>::value
为true
,该函数模板实例化成功,最终被调用。
正是由于 SFINAE 机制的存在,当 T
替换失败的时候编译器不会报错,而是排除该模板重载,继续寻找其他合适的模板重载,从而保证代码能够正确编译和运行。
注意,替换失败不是错误的前提是,总有一个模板是能替换成功的。如果编译器试了你所有的模板还是没有找到可以替换成功的,他依然是错误
(3). 偏特化/全特化和SFINAE
全特化(Full Specialization)
全特化是为特定类型提供完全定制的模板实现。
1 | // 主模板 |
偏特化(Partial Specialization)
偏特化是为一类类型提供特定实现,仍保留部分模板参数。
1 | // 指针类型的偏特化 |
SFINAE与特化的结合
通过SFINAE可以实现条件性的特化,根据类型特征自动选择合适的实现。
2. 类型萃取
(1). 概念
类型萃取是C++模板元编程的核心技术,用于在编译时获取和操作类型信息。它允许我们查询类型的属性、转换类型,以及基于类型特征进行条件编译。
基本原理
类型萃取基于模板特化和SFINAE技术,通过模板元编程在编译时计算类型信息。
(2). 基础设施
2.1 integral_constant
integral_constant
是类型萃取的基础模板,定义如下:
1 | template <class T, T v> |
2.2 基础类型定义
1 | typedef integral_constant<bool, true> true_type; |
2.3 核心成员
::value
- 编译期常量,存储萃取结果
- 对于布尔类型萃取,值为
true
或false
- 对于数值类型萃取,存储具体的数值
1 | static_assert(std::is_integral<int>::value == true); |
::type
- 类型别名,通常指向自身
- 在类型变换萃取中指向变换后的类型
- 支持嵌套萃取操作
1 | using type1 = std::remove_const<const int>::type; // int |
::value_type
- 值的类型,通常为
bool
或其他基础类型 - 在
integral_constant
中定义
2.4 typename模板功能
- 声明模板参数 :用于声明类型模板参数
- 消除歧义 :告诉编译器某个依赖名称是一个类型
依赖名称消歧义(核心功能)
依赖名称是指依赖于模板参数的名称。当编译器遇到依赖名称时,无法确定它是类型还是值,需要 typename 关键字明确指示。
1 | template<typename T> |
3. constexpr关键字 (C++11,持续增强)
特性说明与通俗解释
constexpr
是C++11引入的一个革命性特性,它的核心思想是将计算从运行时转移到编译时。
通俗比喻:想象你要做一道数学题,传统方式是考试时现场计算,而constexpr
就像是提前在家里算好答案,考试时直接写结果。这样不仅节省了考试时间(运行时间),还能提前发现计算错误(编译时错误检查)。
核心作用:
- 性能提升:编译时完成计算,运行时直接使用结果
- 类型安全:编译时就能发现错误
- 常量需求:可用于需要编译时常量的场合(如数组大小)
- 优化机会:给编译器更多优化空间
语法模板
1 | // constexpr变量 |
constexpr的各种用法及作用
constexpr变量 - 编译时常量
作用:创建真正的编译时常量,可用于模板参数、数组大小等需要编译时确定值的场合。
1 | constexpr int buffer_size = 1024; // 编译时确定 |
constexpr函数 - 编译时计算
作用:函数可以在编译时执行,如果参数是编译时常量,结果也是编译时常量。
1 | // 简单数学计算 |
constexpr类和构造函数 - 编译时对象
作用:允许在编译时创建和操作对象,实现复杂的编译时计算。
1 | class Point { |
constexpr if - 编译时条件分支 (C++17)
作用:在编译时根据条件选择不同的代码路径,实现真正的零开销抽象。这是模板元编程的重要工具,可以根据类型特征在编译时生成不同的代码。
适用场景:
- 模板函数中根据类型特征选择不同实现
- 避免运行时分支,提高性能
- 简化SFINAE的使用
- 实现类型安全的泛型代码
1 | // 基本用法 |
constexpr与其他关键字结合
constexpr + static
作用:创建编译时确定的静态常量
1 | class Config { |
constexpr + lambda (C++17)
作用:创建可在编译时执行的lambda表达式
1 | constexpr auto square_lambda = [](int x) constexpr { |
4. explicit关键字 (C++98/03,C++11增强)
特性说明
explicit
关键字是一个访问控制修饰符,用于防止编译器进行隐式类型转换。它强制要求必须显式地调用构造函数或转换运算符,从而避免意外的类型转换导致的bug。
语法模板
1 | // 构造函数 |
使用示例
1 | class MyString { |
5. decltype关键字 (C++11)
特性说明
decltype
是一个类型推导关键字,它可以在编译时推导出表达式的类型。与auto
不同,decltype
会保留表达式的完整类型信息,包括引用、const等修饰符。
语法模板
1 | decltype(表达式) 变量名; |
使用示例
1 | int x = 42; |
6. 返回值后置语法 (C++11)
特性说明
返回值后置语法(Trailing Return Type)是一种新的函数声明方式,使用auto
关键字占位,然后用->
指定真正的返回类型。这种语法在模板编程中特别有用,可以更容易地表达复杂的返回类型。
语法模板
1 | auto 函数名(参数列表) -> 返回类型 { |
使用示例
1 | // 简单函数 |
7. std::enable_if
std::enable_if 介绍
std::enable_if
是C++11引入的类型特征工具,是基于SFINAE机制的具体的模板约束的语法糖。
定义和参数
1 | template<bool B, class T = void> |
参数说明:
B
:布尔条件,当为true
时启用模板T
:当条件为true
时的类型,默认为void
基本用法
1. 函数模板的条件启用
1 |
|
2. 模板参数的条件启用
1 | template<typename T, |
8. 变量模板
变量模板是C++14引入的重要特性,允许创建参数化的变量,就像函数模板和类模板一样。
基本语法
1 | template<模板参数列表> |
语法示例
1 | // 基本语法 |
9. 泛型Lambda (C++14)
特性说明
泛型Lambda是C++14引入的特性,允许Lambda表达式使用auto
参数,从而支持多种类型的参数。它本质上是函数模板的简化版本,编译器会为每种使用的类型自动生成特化版本。
语法模板
1 | // 基本语法 |
使用示例
1 | // 基本泛型lambda |
10. 属性(Attributes) (C++11起)
特性说明
属性是一种标准化的语法,用于向编译器提供额外的信息和提示。它们不改变程序的语义,但可以帮助编译器进行优化、生成警告或进行静态分析。
语法模板
1 | [[属性名]] 声明; |
使用示例
[[deprecated]] - 标记弃用
1 | [[deprecated("Use newFunction() instead")]] |
[[nodiscard]] - 返回值不应被忽略
1 | [[nodiscard]] int calculate() { |
[[maybe_unused]] - 可能未使用
1 | void debugFunction() { |
常用属性表格
属性 | 标准版本 | 作用 | 示例 |
---|---|---|---|
[[noreturn]] |
C++11 | 函数不返回 | [[noreturn]] void exit_program(); |
[[carries_dependency]] |
C++11 | 内存序依赖传递 | void func([[carries_dependency]] int* p); |
[[deprecated]] |
C++14 | 标记弃用 | [[deprecated]] void old_func(); |
[[deprecated("msg")]] |
C++14 | 带消息的弃用标记 | [[deprecated("Use new_func")]] void old_func(); |
[[fallthrough]] |
C++17 | switch穿透 | case 1: doSomething(); [[fallthrough]]; |
[[nodiscard]] |
C++17 | 返回值不应忽略 | [[nodiscard]] int get_value(); |
[[maybe_unused]] |
C++17 | 可能未使用 | [[maybe_unused]] int debug_var = 0; |
[[likely]] |
C++20 | 分支可能执行 | if (condition) [[likely]] { ... } |
[[unlikely]] |
C++20 | 分支不太可能执行 | if (error) [[unlikely]] { ... } |
[[no_unique_address]] |
C++20 | 空基类优化 | [[no_unique_address]] Empty e; |
11 . 折叠表达式
概述
折叠表达式(Fold Expressions)是C++17引入的一项功能,它提供了一种简洁的语法来实现对参数包(parameter pack)的操作,可以将二元运算符应用于参数包中的所有元素,从而将参数包”折叠”成单个值。
语法
折叠表达式有四种形式:
- 一元右折叠:
(pack op ...)
- 一元左折叠:
(... op pack)
- 二元右折叠:
(pack op ... op init)
- 二元左折叠:
(init op ... op pack)
其中:
pack
是包含参数包的表达式op
是二元运算符init
是初始值
结合性
- 左折叠:
(... op pack)
展开为(((...(init op e1) op e2) op ...) op eN)
- 右折叠:
(pack op ...)
展开为(e1 op (e2 op (... op (eN op init))))
结合性对于某些运算符(如减法-
)会产生不同的结果。
空参数包处理
当使用一元折叠处理空参数包时,只有以下运算符允许使用:
- 逻辑与(
&&
):空包的值为true
- 逻辑或(
||
):空包的值为false
- 逗号运算符(
,
):空包的值为void()
使用示例
1. 求和
1 | template<typename... Args> |
2. 打印参数
1 | template<typename... Args> |
3. 使用逗号运算符执行多个操作
1 | template<typename T, typename... Args> |
4. 类型特性检查
1 | template<typename... Ts> |