%% # 纲要 > 主干纲要、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