c++ decltype

 

定义

c++11新增关键字decltype(declare type)声明类型, 功能是在编译时期进行自动类型推导

autodecltype 关键字都可以自动推导出变量类型

auto 在推导类型时会根据value推导出变量类型, 要求变量必须初始化

auto vat_name = value;

decltype 根据 exp 表达式推导出变量类型, 跟 value 没有关系, 不要求变量必须初始化

decltype(exp) vat_name = value;
  • 示例
int a = 0;

// b 被推导成了 int
decltype(a) b = 1;

// x 被推导成了 double
decltype(10.8) x = 5.5;

// y 被推导成了 double
decltype(x + 100) y;

decltype 能够根据变量、字面量、带有运算符表达式推导出变量类型, 但如果表达式的类型为 void, 则会导致编译错误

void func() {}
decltype(func()) x;  // 错误, 返回类型是 void, 不能推导类型

推导规则

decltype 根据不同类型的表达式有不同的推导规则:

  1. 普通表达式和类成员访问表达式:

如果 exp 是一个普通表达式(如变量或常量), 则 decltype(exp) 推导结果与 exp 类型一致

如果 exp 是类成员访问表达式, 推导结果与该成员类型一致

  1. 函数调用:

如果 exp 是函数调用, 则 decltype(exp) 推导结果与该函数的返回类型一致

  1. 左值和括号表达式:

如果 exp 是一个左值或被括号包围, 则 decltype(exp) 推导结果为该表达式的引用类型

#include <iostream>
#include <string>

class Student {
public:
    static int total;

    int age;
    float scores;
    std::string name;
};

int Student::total = 0;

int main() {
    int n = 0;

    const int &r = n;

    Student stu;

    // 普通表达式: n 被推导为 int
    decltype(n) a = n;

    // 常量引用: r 被推导为 const int&
    decltype(r) b = n;

    // 类成员: Student::total 被推导为 int
    decltype(Student::total) c = 0;

    // 类成员: stu.name 被推导为 string
    decltype(stu.name) url = "http://666.com";

    return 0;
}

按照推导规则 1, 对于一般表达式, decltype 推导结果和这个表达式类型一致

函数调用

decltype可以根据函数调用推导返回值类型

注意, 函数调用时需要传入适当的参数, 但 decltype 只根据表达式的类型来推导, 并不会实际调用函数

// 函数声明

// 返回值为 int&
int& func_int_r(int, char);

// 返回值为 int&&
int&& func_int_rr(void);

// 返回值为 int
int func_int(double);

// 返回值为 const int&
const int& fun_cint_r(int, int, int);

// 返回值为 const int&&
const int&& func_cint_rr(void);

// decltype类型推导
int n = 100;

// a类型为 int&
decltype(func_int_r(100, 'A')) a = n;

// b类型为 int&&
decltype(func_int_rr()) b = 0;

// c类型为 int
decltype(func_int(10.5)) c = 0;

// x类型为 const int &
decltype(fun_cint_r(1,2,3)) x = n;

// y类型为 const int&&
decltype(func_cint_rr()) y = 0;

参数exp中调用函数时需要带上括号和参数, 但这仅仅是形式, 并不会真去执行函数代码

左值与括号表达式

当表达式是一个左值或括号包围的表达式时, decltype 会推导为其引用类型

#include <iostream>

class Base {
public:
    int x;
};

int main() {
    const Base obj;
    // 带有括号表达式

    // obj.x 为类成员访问表达式, 符合推导规则一, a 类型为 int
    decltype(obj.x) a = 0;

    // obj.x 带有括号, 符合推导规则三, b类型为 int&
    decltype((obj.x)) b = a;

    // 加法表达式
    int n = 0, m = 0;

    // n+m 得到一个右值, 符合推导规则一, 所以推导结果为 int
    decltype(n + m) c = 0;

    // n=n+m 得到一个左值, 符号推导规则三, 所以推导结果为 int&
    decltype(n = n + m) d = c;

    return 0;
}

左值是指那些在表达式执行结束后依然存在数据, 也就是持久性数据

右值是指那些在表达式执行结束后不再存在数据, 也就是临时性数据

对表达式取地址, 若编译器不报错就为左值, 否则为右值

应用

auto 只能用于类静态成员, 不能用于类非静态成员(普通成员), decltype 特别有用的一个场景是推导复杂类型, 如类的成员类型

  • 示例: 解决容器迭代器类型推导
#include <iostream>
#include <vector>

template <typename T>
class Base {
public:
    void func(T& container) {
        // 使用 decltype 来推导容器的迭代器类型
        m_it = container.begin();
    }
private:
    decltype(T().begin()) m_it;  // 推导出迭代器类型
};

int main() {
    const std::vector<int> v;
    Base<const std::vector<int>> obj;
    obj.func(v);  // 正确, 推导出 const_iterator 类型

    return 0;
}
  • 示例: 非静态成员类型推导

在某些情况下, 需要获取类的非静态成员类型, decltype 提供了更强的支持

假设类成员变量的类型由外部传入:

template <typename T>
class Base {
public:
    void func(T& container) {
        // 使用 decltype 来推导成员类型
        m_it = container.begin();
    }
private:
    decltype(T().begin()) m_it;
};