文章目录
- 型别推导
-
- 查看型别推导结果的方法
- 模板型别推导
- [auto 型别推导](#auto 型别推导)
- [decltype 型别推导](#decltype 型别推导)
型别推导

查看型别推导结果的方法
IDE 编辑器
IDE 中的代码编辑器通常会在你将鼠标指针悬停至某个程序实体,如变量、形参、函数等时,显示出该实体的型别。
cpp
const int theAnswer=42;
//推导为int
auto x=theAnswer;
//推导为const int*
auto y=&theAnswer;


编译器诊断信息
想要让编译器显示其推导出的型别,一条有效的途径是使用该型别导致某些编译错误。
而报告错误的消息几乎肯定会提及导致该错误的型别。
cpp
//只声明TD(Type Displayer)而不定义
template<typename T>
class TD;
int main(){
const int theAnswer=42;
//x推导为int
auto x=theAnswer;
//y推导为const int*
auto y=&theAnswer;
//诱发包括x和y的型别的错误消息
TD<decltype(x)>xType;
TD<decltype(y)>yType;
}



运行时输出
typeid 和 std::type_info::name
针对某个对象,如 x 和 y 调用 typeid,就得到了一个 std::type_info 对象,而后者拥有一个
cpp
#include <iostream>
#include <vector>
#include <typeinfo> // 必须包含此头文件以使用 typeid
using namespace std;
class Widget {
public:
Widget(int id) : id(id) {}
private:
int id;
};
template<typename T>
void f(const T& param) {
// 使用宏或特定的编译器技巧可以获得更易读的型别名称
// 这里使用标准 typeid().name()
cout << "T 类型推导为: " << typeid(T).name() << endl;
cout << "param 类型推导为: " << typeid(param).name() << endl;
cout << "------------------------------------------" << endl;
}
// 工厂函数:返回一个包含 Widget 的 vector
std::vector<Widget> createVec() {
std::vector<Widget> v;
v.emplace_back(1);
v.emplace_back(2);
return v;
}
int main() {
// 1. 使用工厂函数返回值初始化 vw
// vw 的型别被推导为 const std::vector<Widget>
const auto vw = createVec();
// 2. 调用 f
if (!vw.empty()) {
// 我们传入的是 &vw[0],即一个指向 const Widget 的指针
// 观察模板 f 如何推导 T 和 param 的型别
f(&vw[0]);
}
return 0;
}

输出结果为 PK6Widget,PK 表示"pointer to konst const"(指涉到常量的指针),6 表示后面类名的字符数。
但 **<font style="color:#DF2A3F;">std::type_info::name</font>**并不可靠 ,因为它处理型别的方式就仿佛是向函数模板按值传递传递形参一样,其引用性被忽略,实际上应该为 const Widget*const &性别,但被输出为 const Widget*。
更不幸的是,IDE编辑器同它输出一样,并不可靠。

而更可靠的是使用 Boost 的 TypeIndex 库。
Boost 的 TypeIndex 库
函数模板
<font style="color:#DF2A3F;">boost::typeindex::type_id_with_cvr</font>接受一个型别实参(我们想要获取信息的型别),而不会移除**<font style="color:#DF2A3F;">const</font>**、**<font style="color:#DF2A3F;">volatile</font>**和引用饰词 ,并利用成员函数<font style="color:#DF2A3F;">pretty_name</font>产生了一个包含人类可读的型别表示的<font style="color:#DF2A3F;">std::string</font>。
cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(const T¶m){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param"<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
class Widget {
public:
Widget(int id) : id(id) {}
private:
int id;
};
// 工厂函数:返回一个包含 Widget 的 vector
std::vector<Widget> createVec() {
std::vector<Widget> v;
v.emplace_back(1);
v.emplace_back(2);
return v;
}
int main() {
// 1. 使用工厂函数返回值初始化 vw
// vw 的型别被推导为 const std::vector<Widget>
const auto vw = createVec();
// 2. 调用 f
if (!vw.empty()) {
// 我们传入的是 &vw[0],即一个指向 const Widget 的指针
// 观察模板 f 如何推导 T 和 param 的型别
f(&vw[0]);
}
return 0;
}

可以看到输出的型别和我们理想要的型别是一样的,说明用这种方式是要比 IDE 显示的型别信息更加可靠可信。
模板型别推导
模板的型别推导是现代 C++最广泛应用的特性之一 ------auto 的基础。如果你想要使用 C++11 中推导 auto 型别,那么了解模板的型别推导必然是其前提,此外,当模板型别推到规则应用于 auto 语境时,它们并不像应用于模板规则那么符合直觉。
函数模板及其调用大致形如:
cpp
//函数模板
template<typename T>
void f(ParamType param);
//调用模板函数
f(expr);
在编译器编译期间,编译器会通过 expr的值推导两个型别:**T 的型别**和 ParamType 的型别。
这两个型别常常并不一样,原因在于 ++ParamType 常会包含如 const,voliate 和引用等一些修饰词++。
根据 ParamType 的形式大致可分为三种主流情况:
- ParamType 是个指针或引用,但不是个万能引用。
- ParamType是个万能引用。
- ParamType既非指针也非引用。
也另含一些边缘情况,比如传入的实参是数组 或者函数等。
ParamType 是个指针或引用,但不是个万能引用
📌**推导方式****:**
- 若 expr 具有引用型别,先将引用部分忽略。
- 尔后,对 expr 的型别和 ParamType 的型别执行模式匹配来决定 T的型别。
cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(T& param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
int main() {
//x的型别为int
int x=27;
f(x);
//x的型别为const int
const int cx=x;
f(cx);
//x的型别为const int&
const int&rx=x;
f(rx);
return 0;
}

cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(const T& param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
int main() {
//x的型别为int
int x=27;
f(x);
//x的型别为const int
const int cx=x;
f(cx);
//x的型别为const int&
const int&rx=x;
f(rx);
return 0;
}

cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(T* param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
int main() {
//x的型别为int
int x=27;
f(&x);
//px的型别为const int
const int *px=&x;
f(px);
return 0;
}

ParamType 是个万能引用
📌**推导方式****:**
- 如果expr是个左值,T和ParamType都会被推导为左值引用。这个结果具有双重的奇特之处:首先,这是在模板型别推导中,T 被推导为引用型别的唯一情形。其次,尽管在声明时使用的是右值引用语法,它的型别推导结果却是左值引用。
- 如果exptr是个右值,则应用上一个情况。
cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(T&& param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
int main() {
//x的型别为int
int x=27;
f(x);
//x的型别为const int
const int cx=x;
f(cx);
//x的型别为const int&
const int&rx=x;
f(rx);
f(27);
return 0;
}

ParamType 既非指针也非引用
📌**推导方式(按值传递)****:**
- 一如之前,若 expr 具有引用型别,则忽略其引用部分。
- 忽略 expr 的引用性之后,若 expr 是个 const 对象,也忽略之。若其是个 volatile 对象,同忽略之。
cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(T param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
int main() {
//x的型别为int
int x=27;
f(x);
//x的型别为const int
const int cx=x;
f(cx);
//x的型别为const int&
const int&rx=x;
f(rx);
//ptr是个指涉到const对象的const指针
//前一个const修饰ptr指向的对象,表示字符串不可修改
//后一个const修饰ptr本身,表示ptr不可以修改指向对象
const char*const ptr="hello world";
//模板推动规则为:忽略指针的常量性
//所以只会忽略后一个const,保留前一个const
//推导的型别为const char*
f(ptr);
return 0;
}

数组实参
📌**推导方式****:**
- 当按值传递时,数组退化为指针。
- 当按引用传递时,推导为数组类型,该型别包含数组尺寸。
cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(T param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
int main() {
//name的型别为const char[11]
const char name[]="J.P.Briggs";
f(name);
//数组退化为指针
const char*ptrToName=name;
f(ptrToName);
return 0;
}

cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(T& param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
int main() {
//name的型别为const char[11]
const char name[]="J.P.Briggs";
f(name);
//数组退化为指针
const char*ptrToName=name;
f(ptrToName);
return 0;
}

函数实参
📌**函数型别推导同数组型别推导一样**:
- 当按值传递时,函数 退化为指针。
- 当按引用传递时,推导为函数类型。
cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(T param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
void someFunc(int,double){
}
int main() {
f(someFunc);
return 0;
}

cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(T& param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
void someFunc(int,double){
}
int main() {
f(someFunc);
return 0;
}

auto 型别推导
绝大多数情况,auto 的型别推导和**模板**型别推导一样,两者之间可以建立起一一映射,也确实存在双向的算法交换。
当某变量采用 auto 来声明时,auto 就扮演了模板中的T 的角色,而变量的型别饰词 则扮演的是 ParamType 的角色。
💥然而,只有一种情况例外:auto 会假定用大括号括起的初始化表达式代表一个 **<font style="color:#DF2A3F;">std::initializer_list</font>**,但模板型别推导却不会。(C++17 之后,为了减少困惑,规则改为:如果是单值的直接列表初始化(没有等号),则直接推导为该值的类型)
cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(T param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
int main() {
//型别为int
auto x1=27;
f(x1);
//型别为int
auto x2(27);
f(x2);
//型别为std::initializer_list<int>
auto x3={27};
f(x3);
//C++11/C++14:型别为std::initializer_list<int>
//C++17:型别为int
auto x4{27};
f(x4);
return 0;
}

cpp
#include<iostream>
#include<boost/type_index.hpp>
#include<vector>
template<typename T>
void f(T param){
using boost::typeindex::type_id_with_cvr;
//显示T的型别
std::cout<<"T="<<type_id_with_cvr<T>().pretty_name()<<std::endl;
//显示param的型别
std::cout<<"param="<<type_id_with_cvr<decltype(param)>().pretty_name()<<std::endl;
}
int main() {
//型别为int
auto x={11,23,9};
f(x);
//auto 会假定用大括号括起的初始化表达式代表一个 std::initializer_list
//但模板型别推导却不会
//所以向模板直接给大括号表达式模板不认识,但auto会认它为一个容器
//f({{11,23,9}}); //编译错误
return 0;
}

⚠️在 C++14 中允许使用 auto 来说明函数返回值需要推导,而且 C++14 中的 lambda 表达式也会在形参声明中用到 auto。然而,这些 auto 用法是在使用模板型别推导而非 auto 型别推导。
所以,带有 auto 返回值的函数不能返回一个大括号括起的初始化表达式。

也不能将一个大括号括起的初始化表达式作为 lambda 的形参型别。

decltype 型别推导
对于给定的名字或表达式,decltype 能给出该名字或表达式的型别。
大多数情况它会给定确切的型别, 它不会像 <font style="color:#000000;">auto</font> 那样剥离 <font style="color:#000000;">const</font>、<font style="color:#000000;">volatile</font> 或引用(reference)修饰符 :
cpp
#include <vector>
#include <iostream>
// 定义一个简单的结构体和类
struct Point {
int x, y; // decltype(Point::x) 是 int
};
class Widget {};
// 声明函数 f
bool f(const Widget& w); // decltype(f) 是 bool(const Widget&)
int main() {
// --- 基础类型 ---
const int i = 0; // decltype(i) 是 const int
// --- 对象与函数调用 ---
Widget w; // decltype(w) 是 Widget
if (f(w)) { // decltype(f(w)) 是 bool
// ...
}
// --- 容器与成员访问 ---
// 模拟 std::vector 的简化逻辑
std::vector<int> v; // decltype(v) 是 std::vector<int>
if (!v.empty()) {
// 重点:vector 的 operator[] 通常返回的是引用
// 所以 decltype(v[0]) 是 int&
if (v[0] == 0) {
// ...
}
}
return 0;
}
cpp
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i) -> decltype(c[i]) {
// 逻辑处理...
return c[i]; // 推导为引用,如 int&
}
cpp
template<typename Container, typename Index>
decltype(auto) authAndAccessV2(Container& c, Index i) {
return c[i]; // 完美保留了 c[i] 的引用特性
}
// 示例应用
std::vector<int> v = {1, 2, 3};
authAndAccessV2(v, 0) = 10; // 正确:返回的是 int&,可以赋值
⚠️**特殊情况 (双括号陷阱) :**
- 对于一个名字(id-expression),
<font style="color:#000000;">decltype</font>返回该名字的类型。 - 对于一个左值表达式,如果它不是简单的名字,
<font style="color:#000000;">decltype</font>会推导出该类型的引用。例如<font style="color:#000000;">decltype((x))</font>永远是引用,而<font style="color:#000000;">decltype(x)</font>取决于<font style="color:#000000;">x</font>本身。
cpp
int x = 0;
decltype(x) y = x; // y 的类型是 int
decltype((x)) z = x; // z 的类型是 int& !!
// 警告:在函数中使用时
decltype(auto) f1() {
int x = 0;
return x; // 返回 int
}
decltype(auto) f2() {
int x = 0;
return (x); // 灾难!返回了局部变量的引用 (int&),会导致未定义行为
}