%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
已产出的示例文件:
`type_conversion`:
- `explicit_type_conversion_syntax`:声明显式类型转换的几种语法
- `implicit_conversion_seq_in_different_conversion_path`:列举多个不同转换路径, 分析其中的"隐式转换序列"
- `user_defined_conversion`:
- `constructor_and_conversion_func_of_same_path_differ_in_different_conversion_context`:对比说明 "提供了相同转换路径的构造函数与转换函数" 在不同的"初始化"、"类型转换"上下文中的显/隐性不同
- `user_defined_conversion_with_wo_explicit_in_init_and_conversion`:比较两种用户定义转换(构造函数、转换函数)在初始化和类型转换上下文的应用
- convertion_path_explain:对比说明不同转换路径在直接初始化、拷贝初始化上下文中的差异
---
%%
# 类型推导
C++中的**类型推导**包括以下场景:
- **模版类型参数**推导
- 函数模版类型参数推导
- 类模版类型参数推导(since C++17)
- `auto` **自动类型推导**:
- **变量/对象类型**推导(根据初始值)
- **函数返回类型**推导(根据 `return` 语句表达式的值)
- `decltype()` 类型推导(推导实体类型,或根据表达式的值类别推导表达式的**值类型**)
- `typeid()` 运算符
- **结构化绑定** (since C++17)
<br>
# 类型推导规则总结 ⭐
从语法形式上来看,类型推导的具体形式包括:
- `typeid()`
- `auto` 自动类型推导: ` auto `、` const auto `、` auto& `、 ` const auto& `、 ` auto&& `
- 函数模版的**函数形参中的模版类型参数**的推导: `(T)`、`(const T)` 、`(const T&)` 、`(T&)`、`(T&&)`、
- `decltype()`
对于上述各种形式,类型推导规则总结如下 [^1] [^2]:
- `auto`、`const auto`、 `(T)` 、`(const T)`、`typeid` 都是对 "**==值类型==" 的推导**,因此会 **==忽略实参的 "顶层 ` const `" 和 "引用==(包括引用的 cv 限定符)"**,而**保留底层 `const`**。
- **忽略顶层 `const`**,**保留底层 `const`:**
- 对于**指针**类型,修饰**指针本身的"顶层 const"将被忽略**,修饰指针所指对象的 **"底层 const"将被保留**<br>(因为底层 `const` 属于 **==指针类型一部分==**);
- 对于**值类型**:修饰**对象本身的 "顶层 const" 将被忽略**。
- **忽略引用(及引用的 cv 限定)**:
- 对于引用类型,推导结果均为是 "**==被引用类型的 cv-unqualified 版本==**"
- `auto&` 、`(T&)` 都是对 "**==左值引用类型==**" 的推导,**==保留顶层/底层 `const`==**,只能接受 **==左值实参==**,忽略引用,推导结果**自然只能是==左值引用==类型**。
- 对于**左值/右值引用类型的实参**,其都是**用作为一个变量名("左值")**,因此占位符 `auto` 和 `T` 都推导为"**被引用的类型**" (没有发生引用折叠)。
- `const auto&`、`(const T&)` 都是对 "**`const` ==左值引用类型==**"的推导,可接受**任何左/右值实参**,**无论是左值还是右值,推导结果都是"const ==左值引用类型=="**。
- `auto&&`、`(T&&)` 都是"**==转发引用/完美引用==**",可接受**任何左/右值实参**,**保留顶层/底层 `const`**,其**最终类型**在 "**==类型推导==**" 以及 "**==引用折叠==**" 的共同作用下得到,根据**实参是左值 or 右值**最终推导结果分别为**实参值类型的==左值引用或右值引用类型==**。
- "**类型推导**" 本身只是**对占位符 `auto` 与 `T` 的推导**,根据实参为左值 or 右值,对应推导为 `X&` 和 `X`**,其中 `X` 为实参的值类型。
- "**引用折叠**":在类型推导之后,**`auto&&` 与 `T&&` 构成了 `X& &&` 或 `X&&`**,对于前者触发**引用折叠规则**,最终产出类型为 `X&` 或 `X&&`。
- `decltype` 用以 **==获取精确的==实体或表达式类型**,对于实体和表达式有所不同:
- 对于**实体**:返回**实体的==声明类型==**,**==保留 cv 属性,引用==**;
- 对于变量名,得到**变量的声明类型(包括引用)**
- 对于类型名,得到对应类型;
- 对于函数名,得到函数类型
- 对于结构化绑定的名称,得到被绑定对象的 **值类型(忽略引用)**
- 对于非类型模版参数的名称,得到模版形参的类型;
- 对于**表达式**,取决于表达式的"**值类别**":
- 对 `prvalue`,得到**表达式的==值类型**== `X`;
- 对 `lvalue`,得到**表达式==值类型的左值引用**== `X&`;
- 对 `xvalue`,得到**表达式==值类型的右值引用**== `X&&`。
> [!summary] 类型推导总结
> - 对 "**值类型**" 的推导,推导结果自然是"**值类型**"(无引用),因此**源实参的顶层 `const` 和引用(及引用的 cv 限定)都会被忽略**。
> >
> - 对于 "**左值引用类型**"的推导,推导结果自然是 "**左值引用类型**",因此 **==只能接受左值实参==**,会 **保留源实参的顶/底层 const 属性。**
> >
> - 对于 "**const 左值引用类型**"的推导,推导结果自然是 "**const 左值引用类型**",可接受**任何左/右值实参**,**无论是左值还是右值。**
> >
> - 对于 "**转发引用/完美引用**"的推导,则最终推导结果==**只会是实参值类型的"左值引用"或"右值引用"之一**==,编译器会**根据实参的值类别(左值 or 右值)将占位符对应推导为"左值引用"或"右值引用"**,并在 "**==引用折叠" 规则==** 的作用下,产出最终的推导结果。
> [!caution]
>
> - **==源参数的"引用"==本身在类型推断中会被完全忽略**,包括**引用的 CV 限定符**。
> >
> - 当**推导类型明确限定为"==引用"类型==** 时(无论是左/右值引用还是转发引用),都会**传递源参数的 `const`属性**。
> - 因为 **"==引用类型"的 `const` 是底层==的**,如果**源参数具有 `const` 属性**,<br>则被推导的**引用类型也必然得是一个 `const` 引用**,从而才能**引用该 `const` 对象**。
```cpp
template <typename T>
void func(T& p) {}
int main() {
int *p = nullptr;
const int* cp = nullptr;
int* const pc = nullptr;
const int* const cpc = nullptr;
func(p); // T 推导为 "int*"
func(cp); // T 推导为 "const int*"
func(pc); // T 推导为 "int* const"
func(cpc); // T 推导为 "const int* const"
}
```
<br><br><br>
# auto 自动类型推导
> 参见[^3] [^4] [^5]
`auto` 关键字用作 "**类型说明符**" 时,指示让编译器根据 **==初始化表达式==** 来推断**被初始化的变量**(左值 `lvalue`)的类型。
`auto` 自动类型推导可用于以下场景:
- 用于 "**==变量/对象类型==**" 的类型推导:指示编译器通过"**==初始值==**" 来**自动推导"被声明变量"的类型**。
- (1)声明变量时(**必须给变量一个==初始值==**);
- (2)range-based for 循环中使用;
- (3)用于**声明 lambda 表达式对应的闭包类型**;
- 用于 "**==函数返回类型==**" 的类型推导:编译器会根据 **`return` 语句表达式的类型** 来自动推断**函数的返回类型**。(since C++14)
- 用于泛型 lambda 中推导 "**函数形参类型**"(since C++14)
## auto 用于变量类型推导
> [!summary] `auto`、`const auto`、 `auto&` 、`auto&&`、`const auto&` 区别
>
> | | 可接受的初始值类型 | 推导结果 | 说明 |
> | ------------- | --------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
> | `auto` | 左值、右值 | **值类型** | 1. 忽略 **顶层 const**;<br>2. 忽略 **引用及其底层 const**;<br>3. 保留 **==指针==的底层 const**; |
> | `const auto` | 左值、右值 | **const 值类型** | |
> | `auto&` | 左值 | **==左值引用==** | 保留初始值的 "**顶层/底层 const**" |
> | `const auto&` | 左值、右值 | **==const 左值引用==** | |
> | `auto&&` | 左值、右值 | ==**左值引用**== or ==**右值引用**== | 万能引用,根据**初始值是左值 or 右值分别对 `auto` 推导为 ==左值引用== or ==右值==**,**进而 `auto&&` 为左值引用 or 右值引用类型**。 <br>- 结果为 "**左值引用**" 时,保留初始值的**顶层/底层 const 属性**; <br>- 结果为 "**右值引用**" 时,**忽略**初始值的**顶层/底层 const 属性**。 |
>
> [!caution] 当`auto`声明的变量使用花括号列表初始化时,`auto` 推导出的类型为`std::initializer_list<>`。
>
> 参见[^6],仅限于 "**变量定义**" 中,若在**函数返回值或 lambda 函数形参**中使用 `auto`,则不能将花括号列表推导为 `std::initializer_list<>`。
>
> 例如 `auto x = { 27 };` ,会推导为 `std::initializer_list<int>` 类型,里面有一项值为 27。
> **如果这样的一个类型不能被成功推导**(比如花括号里面包含的是不同类型的变量),编译器会拒绝。
>
> [!caution] `auto` 对于数组名与函数名退化为 "指针" 的处理
>
> `auto` 会将数组名与函数名**推导为 "指针" 类型**,而 `auto&` 会**推导为对 "数组" 或 "函数" 本身的引用类型**
>
> ![[_attachment/02-开发笔记/01-cpp/类型相关/cpp-类型推导.assets/IMG-cpp-类型推导-41F7AB8A76FEEE2BE6D00A94AAF8BB2C.png|683]]
>
>
说明示例:
```cpp title:auto_usage.cpp
void test_auto() {
int n = 1;
int &lref = n;
int &&rref = 9;
// `auto`忽略顶层const和引用, 可接受左右值, 其声明的总是一个"值类型".
auto a1 = n; // `auto` 推断为`int`
auto a2 = lref; // 忽略引用, `auto` 推断为`int`
auto a3 = rref; // 忽略引用, `auto` 推断为`int`
auto a4 = 35; // `auto` 推断为`int`
auto a5 = std::move(2); // `auto` 推断为`int`
// `const auto`声明为一个"const值类型", 推导出的auto类型是顶层const, 可接受左右值.
// 对于下列所有情况, 无论值类别如何, `const auto`推导结果都为`const int`.
const auto ca = n;
const auto cb = lref;
const auto cc = rref;
const auto cd = 35;
const auto ce = std::move(2);
// `auto&`声明一个"左值引用", 初始值只能为左值, 且将保留被引用对象的"const"属性.
auto& b1 = n; // `n`是lvalue, `auto&` 推断为`int&`
auto& b2 = lref; // `lref`是lvalue, `auto&` 推断为`int&`
auto& b3 = rref; // `rref`是lvalue, `auto&` 推断为`int&`
// auto& b4 = 5; // error: 左值引用不能接受prvalue
// auto& b5 = std::move(2); // error:左值引用不能接受xvalue
// `const auto&`声明一个"const左值引用", 左值或右值都能接受.
// 对下列所有情况, 无论值类别如何, `const auto&`推导结果都为`const int&`.
const auto& c1 = n; // `n`是lvalue
const auto& c2 = lref; // `lref`是lvalue
const auto& c3 = rref; // `rref`是lvalue
const auto& c4 = 5; // `5`是prvalue
const auto& c5 = std::move(2); // `2`是xvalue
// `auto&&`为"转发引用/完美引用", 完全保留被引用对象的"const"属性以及值类别.
auto&& au_1 = n; // `n`是左值, `auto&&` 推断为`int&`
auto&& au_2 = lref; // `lref`是左值, `auto&&` 推断为`int&`
auto&& au_3 = rref; // `rref`是左值(虽然是右值引用), `auto&&` 推断为`int&`;
auto&& au_4 = 5; // `5`是右值, `auto&&` 推断为`int&&`
auto&& au_5 = std::move(2); // `2`是右值, `auto&&` 推断为`int&&`
const auto* p = &n; // 得到`pointer to const auto`的指针
auto* const p = &n; // 得到`const pointer`常量指针.
}
```
#### 使用示例
```cpp
// 使用场景一: 声明变量时自动推导变量类型
auto x = 5; // x被推导为int
auto y = 1.5; // y被推导为double
// `auto`会忽略"引用"
int i = 0, &r = i; // r是`int&`类型
auto a = r; // a是int类型, 而不是"int&"
// `auto`会忽略"顶层const"
const int ci = i, &ct = ci; // i是`const int`类型, ct是`const int&`类型
auto b = ci; // b是`int`类型, `auto`忽略顶层const
auto c = ct; // c是`int`类型, `auto`忽略顶层const和引用.
auto d = &i; // d是`int*`指针类型, `auto`忽略顶层const和引用.
auto e = &ci; // e是`const int*`类型, 指向常量的指针.
// (对常量对象取地址, 得到指向常量的指针, 是一种`底层const`).
// 使用场景二: 在range-based for循环中使用
vector<int> vec = {1, 2, 3, 4, 5};
for (auto i : vec) {
cout << i << endl;
}
// 使用场景三: 用于声明lambda表达式, 自动推导出其类型
auto func = [](int a, int b) -> int { return a + b; };
```
`auto` 前可加上额外的限定符,例如:
```cpp
static auto vat = 0.19; // 静态变量
const auto &x = ...; // 常量引用
vector<string> vec;
for (const auto &s : vec) { // 常量引用
cout << s << endl;
}
```
一个适合使用 `auto` 的场景:
```cpp
// 模版参数是 "迭代器类型", 而函数中需要获取 "迭代器所指向的元素的值的类型", 这只有在实例化时编译器才知道.
template<typename It>
void dwin(It b, It e) {
while (b != e) {
typename std::iterator_traits<It>::value_type
currValue = *b;
// 上面语句可以改用auto声明, 即
// auto currValue = *b;
...
}
}
```
<br>
## lambda 表达式形参的类型推导
**C++14** 引入了**泛型 Lambda**(Generic Lambda),允许在 Lambda 表达式中**使用 `auto` 来推导参数类型**。
示例:在 lambda 表达式的**形参类型推导**中,使用 "**==转发引用==**"
```cpp
// 示例: 一个可对几乎任意函数进行计时的lambda表达式
auto timeFuncInvocation = [](auto&& func, auto&&... params) { // 万能引用形参
// TODO: start timer;
std::forward<decltype(func)>(func)( // 对func和params都进行完美转发
std::forward<decltype(params)>(params)...
);
// TODO: stop timer and record elapsed time;
};
```
<br>
## `decltype(auto)` 自动类型推导
C++14 中引入 `decltype(auto)` 语法,表示**自动推导类型时==按照 `decltype` 的规则==进行推导**,即 "**保留==完整的声明类型==**"。
示例一:用于推导 "**变量类型**" 时;
```cpp
Widget w;
const Widget& crw = w;
auto my_w = crw; // auto 推导为 Widget 类型
decltype(auto) my_w2 = crw; // decltype(auto) 推导为 const Widget& 类型
```
示例二:用于推导 "**函数返回类型**" 时;
```cpp
template <typename Container, typename Index>
auto
Access(Container& c, Index i)
->decltype(std::forward<Container>(c)[i]) // 尾置返回类型
{
return std::forward<Container>(c)[i];
}
// 上述写法可使用`decltype(auto)`等价表示为: ↓
template <typename Container, typename Index>
decltype(auto)
Access(Container& c, Index i) {
return std::forward<Container>(c)[i];
}
```
<br><br><br>
# decltype 类型推导
> 参见 [^7] [^6] [^8]
`decltype()` 返回传入的 ==**实体或表达式**== 的完整 "**==声明类型==**",但**不实际计算表达式的值**。
> [!NOTE] `decltype` 最常用于声明 "**函数模版**",其中 "**函数返回类型**" 依赖于 "**形参类型**" 的场景(需要采用 "**尾置返回类型**" 的写法)
<br>
### `decltype` 使用语法
![[_attachment/02-开发笔记/01-cpp/类型相关/cpp-类型推导.assets/IMG-cpp-类型推导-BE760BB0C3E8282FE157D8AAB39365CB.png]]
### `decltype` 作用效果
区分两种情况:
1. 用于**实体**时,推导**实体的 "==声明类型=="**(包括 **==顶层 const 和引用==**),但**不考虑值类别** ;
2. 用于**表达式** 时,记表达式的**值类型**为 `T`,则推导结果取决于表达式的 **==值类别==**(type value)。
> [!summary] `decltype` 使用总结
> 根据 cppreference 中的说明,个人总结如下:
>
> - `decltype` 直接作用于**变量名**时,得到 **==变量本身的声明类型==**(包括顶层 `const` 与引用);
> - 如果变量是"左值引用",则得到 **左值引用类型**;若是"右值引用",则得到 **右值引用类型**。
> - 如果为变量名加上一对圆括号 `()`,则编译器会将其视为"**左值表达式**",得到 **==左值引用类型==**。
> - `decltype` 作用于**非变量名的表达式**时,其结果受影响于**表达式的==值类别==**,如下文所述。
> - `decltype` 作用于"**函数名**",则取得的是"**函数类型**"(函数是一个实体)
> - `decltype` 作用于"**函数调用**", 则取得的是**函数"返回类型"**(因为**函数调用语句本身就是一条表达式**) ^7za9ac
#### 情况一:用于 Entity
用于**实体**(entity)时,推导 **实体的声明类型** (包括 **==顶层 const 和引用==**):
- 若参数是一个**无括号的变量名** 或 **无括号的类成员访问**(即 `obj.member` ),则返回 **该命名实体的类型**。
- 若参数是一个来自 "**结构化绑定**" 的 **变量名**,则返回实际绑定的 "**==值类型==**",**==忽略引用==**(since C++17)
- 如果参数是一个命名了 ==**非类型模板参数**== 的无括号 id 表达式,则 `decltype` 将返回 **模板形参的类型**(since C++20)。
- 得到的类型将是 `non-const` 的,即使该实体是一个模板参数对象(其为 const 对象)
> [!info] 实体包括:"变量名", "函数名", "类名", "可调用对象" 等
> [!caution] 如果对象的名称被圆括号 `()` 括起来,则将被视为一个普通的 "**==左值表达式==**",按下述规则解释,得到 **==左值引用类型==**。
```cpp
//示例一: 用于无括号的变量实体
int x = 42;
int &lref = x;
decltype(x) a; // a 是int
decltype(x) a; // a 是int
// 示例二: 用于结构化绑定的变量实体
pair<int, int> p {1, 3};
auto [a, b] = p;
auto& [a2, b2] = p;
decltype(a) x; // 推导结果是int类型.
decltype(a2) y; // 推导结果仍然是int类型, "a2"自身是引用, 其所绑定的对象的类型是`int`.
```
#### 情况二:用于表达式
用于**表达式**(expression)时,如表达式的**值类型**为 `T`,则推导结果取决于表达式的 **==值类别==**(type value):
- 对于 **`prvalue` 纯右值表达式**,得到 "**表达式的==值类型**==" `T`;
- 对于 **`lvalue` 表达式**(例如变量名+圆括号 `(var)`,或 `a=b`,或指针解引用`*p`),得到**表达式值类型**的 "**==左值引用==类型**" `T&`;[^9]
- 对于 **`xvalue` 表达式**(例如显式的 `std::move(var)`),得到**表达式值类型**的 "**==右值引用==类型**" `T&&`;
### 使用示例
说明示例:
```cpp
void test_decltype() {
int x = 1;
int &lref_x = x;
int &&rref_x = 3;
assert(typeid(decltype(x)) == typeid(int));
assert(std::is_lvalue_reference<decltype((x))>::value); // 变量名+圆括号的`(x)`为左值表达式, 得到左值引用类型
assert(std::is_lvalue_reference<decltype(lref_x)>::value); // 得到左值引用类型
assert(std::is_rvalue_reference<decltype(rref_x)>::value); // 得到右值引用类型
assert(std::is_lvalue_reference<decltype((rref_x))>::value); // "右值引用"变量本身是个左值, 所以得到左值引用类型
// assert(std::is_rvalue_reference<decltype(35)>::value); // error. `prvalue`, 得到表达式的"值类型"
assert(std::is_rvalue_reference<decltype(std::move(35))>::value); // `xvalue`, 得到右值引用类型
}
int func() {
return 15;
}
int main() {
// ----------1. decltype作用于实体, 得到实体的类型----------------------------------
// 实体包括"变量名", "函数名", "类名", "可调用对象"等.
int x = 1;
int &lref_x = x;
int &&rref_x = 3;
int *p = &x;
int **pp = &p;
decltype(x) y = 1; // 推导类型为`int`
decltype(lref_x) lref_y = x; // 推导类型为`int&`
decltype(rref_x) rref_y = 3; // 推导类型为`int&&`
decltype(p) p2 = &x; // 推导类型为`int*`
decltype(pp) pp2 = &p; // 推导类型为`int**`
// 对于函数名, 推导结果是"函数类型"
decltype(func) *ptr_func = func; // 推导类型为`int(*)(void)`
// 对于可调用对象
function<bool(int, int)> comp = [](int a, int b) { return a > b; };
decltype(comp) comp2 = comp; // 推导类型为`std::function<bool(int, int)>`
// ---------2. decltype作用于表达式, 则推导结果取决于表达式的值类别(type value)-------------
// (1) 对于lvalue左值表达式, 推导结果是"表达式值类型的左值引用类型"
// (2) 对于prvalue纯右值表达式, 推导结果是表达式的"值类型"
// (3) 对于xvalue亡值表达式, 推导结果是"表达式值类型的右值引用类型"
decltype((x)) lref_z = x; // 推导类型为`int&`. 变量名+圆括号, 将被视为一个"左值表达式".
decltype(35) z = 1; // 推导类型为`int`. `35`是一个"纯右值表达式".
decltype(std::move(35)) rref_z = 1; // 推导类型为`int&&`. `std::move(x)`是一个"右值表达式".
// 具体示例:
// 对函数调用表达式, 推导结果是"函数的返回类型"
decltype(func()) z2 = 1; // 推导类型为`int`.
// 对于解引用运算符表达式, 解引用的结果是一个左值, 因此得到"左值引用类型"
decltype(*p) lref_v = x; // 推导类型为`int&`.
// 对于取址运算符, 取址的结果是一个纯右值(为指针类型), 因此得到表达式的"值类型"
decltype(&x) p3 = &z; // 推导类型为`int*`.
// 对一个"指针"进行取址, 取值结果为纯右值(指向指针的指针类型), 因此得到表达式的"值类型"
decltype(&p) pp3 = &p; // 推导类型为`int**`.
}
```
使用示例:与 `auto` 结合使用,精确控制类型
```cpp
auto z = 1 + 2; // z被推导为int
decltype(z) w = z; // w的类型被推导为z的类型, 即int.
```
使用示例:**应用于模版编程**,推断模版参数或**函数返回值的类型**
```cpp
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) { // 返回类型使用decltype推断,确保类型与"t和u相加的结果类型"相匹配。
return t + u;
}
```
<br><br>
# `typeid` 运算符
`typeid` 用于获取**一个表达式的类型信息**。
### 用法说明
使用: `typeid (expr)`,其中 `expr` 可以是**任意类型的==表达式==** 或 **==类型名称==**,其返回一个 **`std::type_info` 常量对象的引用** [^10] [^11] [^12]。
(与 `sizeof` 运算符类似)
![[_attachment/02-开发笔记/01-cpp/类型相关/cpp-类型推导.assets/IMG-cpp-类型推导-E102E164BD724C206BBD620029A5A6A0.png|218]]
- 当 `expr` 为**非类类型,或是一个不包含任何虚函数的类**时,得到 **==静态类型==**;
- 当 `expr` 为**多态类型(包含有虚函数的类)的左值**时,`typeid` 将**在运行时才确认结果**,得到 **==动态类型==**。
> [!caution] `typeid` 会忽略顶层 `const` 与引用 (包括引用的 cv 限定符)
>
> - **==忽略顶层 `const`==**,**保留底层 `const`:**
> - 对于**指针**类型,修饰指针**本身的"顶层 const"将被忽略**,修饰指针所指对象的 **"底层 const"将被保留**;
> - 对于**值类型**:修饰对象本身的 "顶层 const" 将被忽略。
>
> >
> - **==忽略引用==(及引用的 cv 限定)**:
> - 对于引用类型,`typeid` 的结果将是 "**被引用类型的 cv-unqualified 版本**"
> [!NOTE] `typeid` 用于数组名或函数名时,将得到 "**数组类型**" 或 "**函数类型**",而不会隐式转换为 "指向数组元素的指针",或者 "函数指针" 。
> [!info] **`std::type_info` 类**
> 由头文件 `<typeinfo>` 提供。
> 该类中包含了类型信息,如**类型的名称**,其**成员函数 `.name()` 将返回一个 C 风格字符串,显示类型名**(编译器特定,非易读)。
```cpp title:typeid_usage.cpp
// `typeid` 会忽略顶层const和引用(包括引用的cv限定符), 保留底层const.
// 1)`typeid`会忽略顶层const, 保留底层const.
void typeid_ignore_top_const() {
// 对于指针
assert(typeid(int *const) == typeid(int*)); // 忽略修饰指针本身的"顶层const"
assert(typeid(const int* const) == typeid(const int*)); // 保留修饰指针所指向对象的"底层const"
// 对于const的非指针非引用类型
// `typeid(const T)==typeid(T)`.
assert(typeid(const int) == typeid(int)); // 忽略顶层const
}
// 2)`typeid` 会忽略引用, 包括引用的cv限定符, 返回"被引用类型"的`非cv限定`版本
void typeid_ignore_reference_and_its_cv_qualifiers() {
// 对于`const int`, `int&`, `const int&`, `int&&`等类型的表达式,
// typeid 都会报告为`int`类型.
assert(typeid(int) == typeid(int&)); // 忽略引用
assert(typeid(int) == typeid(int&&)); // 忽略引用
assert(typeid(int) == typeid(const int)); // 忽略顶层const
assert(typeid(int) == typeid(const int&)); // 忽略引用及其cv限定符
assert(typeid(int) == typeid(const int&&)); // 忽略引用及其cv限定符
}
```
`typeid` 通常用于**比较两条表达式的类型是否相同**,或者**判断一条表达式的类型是否为指定类型** :
```cpp title:type_id.cpp
Derived* dp = new Dervied;
Base* bp = dp;
if (typeid(*bp) == typeid(*dp)) {
// bp和dp指向同一类型的对象
}
if (typeid(*bp) == typeid(Derived)) {
// bp实际指向Derived对象
}
```
<br>
## `typeinfo` 类
![[_attachment/02-开发笔记/01-cpp/类型相关/cpp-类型推导.assets/IMG-cpp-类型推导-449D6DA7B5F8BA37767BB1C8A94D37F0.png|650]]
使用示例:
```cpp
// 打印x的类型名(名称为编译器特定的, 例如PK6Widget, 表示指向常量Widget类型的指针
cout << typeid(x).name() << endl;
```
> [!NOTE] 创建 type_info 对象的唯一途径是使用 `typeid` 运算符。
>
> `type_info` 类**没有默认构造函数**,其拷贝/移动构造函数、赋值运算符均定义为 `= delete`。
> 因此,无法定义或拷贝 `type_info` 类型的对象,也不能为 `type_info` 类型的对象赋值。
<br><br><br>
# 结构化绑定
> 结构化绑定(Structed Bindings),since C++17,是对 C++11 中 `std::tie` 的简化与改进,省略手动声明变量类型和引用的过程。
结构化绑定用于**解构对象中的多个值**,将其**直接绑定到==独立的变量或引用==**。
结构化绑定可用于下列类型:
- **数组**
- **`std::tuple` 或 `std::pair`** (STL 容器并不支持)
- **具有特定接口的用户自定义类型**。
> [!caution] 结构化绑定不支持 "省略" 某一项,与 `std::tie` 解包不同。
> 通常,可将变量名**取为 `_` 标识不使用**:`auto [var1, _, var2] = t;`
>
基本语法:
```cpp
// expr为支持解构的对象, 例如数组、`std::pair`、`std::tuple` 或用户自定义类型.
auto [var1, var2, ..., varn] = expr; // 默认创建的是expr中元素的副本
auto& [var1, var2, ..., varn] = expr; // 创建对于expr中元素的引用
```
使用示例:
```cpp
// 解构tuple
tuple<string, int, char> t {"Hello", 25, 'G'};
auto [str, i, ch] = t;
cout << str << " " << i << " " << ch << endl;
vector<tuple<string, int, char> vec;
for (const auto& [str, i, ch] : vec) {
// ...
}
// 解构pair
map<int, string> mp = {{1, "One"}, {2, "Two"}, {3, "Three"}};
for (auto& [key, value] : mp) {
cout << key << " " << value << endl;
}
```
<br>
### 解构用户自定义类型
用户自定义类型可通过**实现 `get<N>(T)` 和 `tuple_size<>` 两个特化版本**,来支持**结构化绑定**特性。
> ![[_attachment/02-开发笔记/01-cpp/类型相关/cpp-类型推导.assets/IMG-cpp-类型推导-11C0CABA7507B050E5B609E3E4104A35.png|498]]
<br><br><br>
# 检查类型推导结果的技巧
类型推导结果可在 IDE、编译器报错、通过 Boost TypeIndex 看出[^11]。
> [!caution] `typeid(x).name()` 给出的结果并不可靠,因为**会去掉顶层 const 以及引用**。
#### 利用编译器报错
一个可行的技巧是,借助一个 "**未定义的类模版**",利用 "**==编译器==**" 的报错信息来查看推导结果:
```cpp
// 检查 x 与 y 的类型推导结果
const int theAnswer = 42;
auto x = theAnswer; // x是int.
auto y = &theAnswer; // y是const int*.
template <typename T>
class TD;
TD<decltype(x)> xType;
TD<decltype(y)> yType;
// 由于模版未定义, 因此编译器在实例化时将会报错, 错误信息将给出对decltype(x)的类型推导结果, 例如:
// error: aggregate 'TD<int> xType' has incomplete type and cannot be defined
// error: aggregate 'TD<const int *> yType' has incomplete type and cannot be defined
```
<br><br><br>
# ❓ FAQ
## ❓ `auto` 与 `decltype` 的区别
用法不同:
- `auto` 关键字本身充当一个 "**==类型说明符==**",例如:
1. 用于声明**变量类型**:`auto x = y;`,编译器根据 "**==初始化表达式==**" 来推断 "被初始化变量" 的类型。
2. 用于声明**函数返回类型**:`auto func() { return 35; }`,编译器 **根据 `return` 语句返回值** 推断函数返回类型。
- `decltype(expr)` 关键字是对 "**传入的==实体或表达式==**" 推断其类型(但**不会实际计算表达式**)
- 主要用在模版元编程中。
- 例如,可用做 "**==模版参数==**": `priority_queue<int, vector<int>, decltype(cmp)>` (`auto` 不行)
- 例如,可**根据函数模版的参数推导 "返回类型"**。
> [!example] 一个差别对比的具体 `case`:
>
> ```cpp
> int obj = 5;
> const int& cref = obj;
>
> auto val = cref; // auto 推导为 Widget 类型, 不保留const和引用.
> decltype(cref) other_cref = cref; // decltype(cref) 推导为`const int&`, 即cref的"完整声明类型"
> decltype(auto) other_cref2 = cref; // decltype(auto) 推导为 const Widget& 类型
> ```
>
>
![[02-开发笔记/01-cpp/类型相关/cpp-类型推导#^7za9ac]]
# ♾️参考资料
# Footnotes
[^1]: 《Effective Modern C++》Item 1~3
[^2]: 《C++ Primer》P608
[^3]: [Placeholder type specifiers (since C++11) - cppreference.com](https://en.cppreference.com/w/cpp/language/auto)
[^4]:《C++ Primer》P62
[^5]: 《Effective Modern C++》
[^6]: 《Effective Modern C++》Item2
[^7]:《C++ Primer》P63
[^8]:《Effective Modern C++》Item3
[^9]: 《C++ Primer》P121
[^10]: [typeid operator - cppreference.com](https://en.cppreference.com/w/cpp/language/typeid)
[^11]: 《Effective Modern C++》Item4
[^12]: 《C++ Primer》P732