22. 类型自动推导

一、auto类型推断

  C++ 11 之前,auto 与 static 是对应的,表示变量是自动存储的,但是非 static 的局部变量默认都是自动存储的,因此这个关键字变得非常鸡肋。在 C++ 11 中,为它赋予了新的含义,使用这个关键字可以自动推导出变量的类型。

  C++ 11 中,auto 并不代表一种实际的数据类型,它只是一个类型声明的 “占位符”,auto 并不是万能的在任意场景上都能推导出变量的实际类型。使用 auto 声明的变量必须要进行 初始化,以便让编译器推导出它的实际类型,在编译时将 auto 占位符替换成真正的类型,它的使用语法如下:

auto 变量名 = 变量值;

  auto 还可以和指针,引用结合起来,也可以带上 const、volatile 关键字,auto 在不同场景上有对应的不同的推导规则。

  • 如果 auto 声明的变量是按值初始化,则推导出的类型会忽略 cv 限定符。进一步解释为,在使用 auto 声明变量时,既没有使用引用,也没有使用指针,那么编译器在推导的时候会忽略 const 和 volatile 限定符。当然 auto 本身也支持添加 cv 限定符。
  • 使用 auto 声明变量初始化时,目标对象如果是引用,则引用属性会被忽略。
  • 使用 auto 声明变量,如果目标对象是一个数组或者函数,则 auto 会被推导为对应的指针类型。
#include <iostream>

using namespace std;

int main(void)
{
    auto x = 3.14;              // double
    auto y = 520;               // int
    auto z = 'a';               // char

    // 如果 auto 声明的变量是按值初始化,则推导出的类型会忽略 cv 限定符
    const int i = 5;
    auto j = i;                 // auto: int

    int temp = 110;
    auto *a = &temp;            // auto: int
    auto b = &temp;             // auto: int*
    auto &c = temp;             // auto: int

    // 使用 auto 声明变量初始化时,目标对象如果是引用,则引用属性会被忽略
    auto d = c;                 // auto: int

    // 使用 auto 声明变量,如果目标对象是一个数组或者函数,则 auto 会被推导为对应的指针类型
    int array[3];
    auto e = array;             // auto: int*
  
    return 0;
}

  auto 自动类型推导并不是万能的,它在以下情景无法使用:

【1】、不能作为函数的形参使用,这是因为只有在函数调用的时候才会给形参传递实参,auto 要求必须要给修饰的变量赋值,因此二者矛盾。

int func(auto a, auto b)            // error
{
    cout << "a: " << a << ", b: " << b << endl;
}

【2】、不能用于类的非静态成员的初始化,这是因为类的非静态成员不属于类,它是属于对象的。只有对象被创建时,类的非静态成员才能进行初始化操作(类的静态成员不允许在类的内部直接初始化)。

class Person
{
    auto v1 = 0;                    // error
    static auto v2 = 0;             // error,类的静态成员不允许在类的内部直接初始化
    static const auto v3 = 0;       // ok
};

【3】、不能使用 auto 关键字定义数组

int main(void)
{
    int array[3];
    auto a1[3];       // error
    auto a1 = array;  // ok,这里定义了一个指针
  
    return 0;
}

【4】、无法使用 auto 推导模板参数

template <typename T>
class MyClass
{
public:
    T a;
};

int main(void)
{
    MyClass<int> t1;
    MyClass<auto> t2;   // error
  
    return 0;
}

二、decltype类型推断

  在某些情况下,如果不需要或者不能定义变量,但是希望得到某种类型,这时就可以使用 C++ 11 提供的 decltype 关键字,它的作用是 在编译器编译的时候推导出一个表达式类型,语法如下:

decltype(表达式)

  decltype 是 declare type 的缩写,意思是 “声明类型”。decltype 的推导是在编译期间完成的,它只是用于表达式类型的推导,并不会计算表达式的值。

#include <iostream>

using namespace std;

int main(void) 
{
    int a = 10;
    decltype(a) b = 100;            // b: int
    decltype(a + 3.14) c = 3.33;    // c: double
    decltype(a + b * c) d = 2.512;  // d: double
  
    return 0;
}

  如果我们使用 decltype 推导的表达式为 普通变量 或者 普通表达式 或者 类表达式,在这种情况下,使用 decltype 推导出的类型和表达式的类型是一致的。

  如果我们使用 decltype 推导的表达式是 函数调用,则推导出的结果与函数的返回值一致。但如果函数的返回值是一个 纯右值(右值则是那些临时性的、没有持久状态的表达式,如字面量、临时对象等,它们通常无法被取地址)的话,只有类类型可以携带 const、volatile 限定符,除此之外的情况,需要忽略掉这两个限定符。

  如果我们使用 decltype 推导的表达式是 左值(左值通常指的是那些有持久状态的表达式,比如变量、数组元素等,它们有明确的内存地址,可以被取地址操作符 & 获取地址。)会使用 () 包围,如果右 const、volatile 限定符则不能忽略。

三、返回值类型后置

  在 C++11 及以后的版本中,函数返回值类型可以后置(trailing return type)。这种语法在处理模板函数时特别有用,因为有时候编译器无法直接推导出模板函数的返回类型。使用 auto 关键字和 decltype 运算符可以方便地指定后置返回类型。后置返回类型的语法如下:

auto 函数名(形参列表) -> decltype(参数表达式)

  返回值类型后置的基本语法是使用 auto 关键字作为占位符,然后在函数声明末尾使用 -> 后跟实际的返回类型。

template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) 
{
    return t + u;
}

posted @ 2023-05-10 22:55  星光樱梦  阅读(25)  评论(0编辑  收藏  举报