C/C++中decltype关键字用法总结以及与typeinfo区别

decltype是C++11新增关键字,用于编译时期自动类型推导。本文介绍了其推导规则,如根据表达式推导类型等,还阐述了典型应用场景,如推导函数返回类型等。同时对比了decltype与auto、typeid的区别,以及它与std::decay的关系,是处理复杂类型问题的强大工具。

目录

1.引言

2. 推导规则

3.典型应用场景

4.实际案例

5.与 auto 的区别

6.decltype与typeid的区别

7. decltype与std::decay的关系

8.总结


1.引言

 decltype是C++11新增的一个关键字,和auto的功能一样,用来在编译时期进行自动类型推导。引入decltype是因为auto并不适用于所有的自动类型推导场景,在某些特殊情况下auto用起来很不方便,甚至压根无法使用。

auto varName = value;
decltype(exp) varName = value;

1) auto根据=右边的初始值推导出变量的类型,decltype根据exp表达式推导出变量的类型,跟=右边的value没有关系
2) auto要求变量必须初始化,这是因为auto根据变量的初始值来推导变量类型的,如果不初始化,变量的类型也就无法推导
3) 而decltype不要求,因此可以写成如下形式

decltype(exp) varName;

原则上讲,exp 只是一个普通的表达式,它可以是任意复杂的形式,但必须保证 exp 的结果是有类型的,不能是 void;如果 exp 为一个返回值为 void 的函数时,exp 的结果也是 void 类型,此时会导致编译错误。示例如下:

int x = 0;
decltype(x) y = 211;            // y -> int
decltype(x + y) z = 333;        // z -> int
const int& i = x;
decltype(i) j = y;            // j -> const int&
const decltype(z) *p = &z;    // *p -> const int, p -> const int*
decltype(z) *m = &z;          // *m -> int, m -> int*
decltype(m)* n = &m;          // *n -> int*, n -> int**

2. 推导规则

decltype的推导规则可以简单概述如下:

  • 如果exp是一个类成员访问表达式,或者是一个单独的变量,decltype(exp)的类型和exp一致
  • 如果exp是函数调用,则decltype(exp)的类型就和函数返回值的类型一致
  • 如果exp是一个表达式(包含括号()包围),decltype(exp)的类型就是exp的引用,假设exp的类型为T,则decltype(exp)的类型为T&

规则1示例:

#include<string> 
#include<iostream>
using namespace std;
 
class MyTest{
public:
    static int a;
    std::string b;
}
 
int MyTest::a=0;
 
int main()
{
    int x=0;
    const int &r=x;
    MyTest y;
    decltype(x) x1=x;    //x为Int,x1被推导为Int
    decltype(r) y=n;    //r为const int &,y被推导为const int &
    decltype(MyTest::a)  z=0;  ///total是类MyTest的一个int 类型的成员变量,z被推导为int
    decltype(y.b) url="www.sina.com.cn";//url为std::string
    return 0;
}

规则2示例:

int& func1(int ,char);//返回值为int&
int&& func2(void);//返回值为int&&
int func3(double);//返回值为int
 
int n=147457;
decltype(func1(5555,'D')) a=n;//a的类型为int&
decltype(func2()) b=325235;//b的类型为int&&
decltype(func3(4365345.6)) c=0;//c的类型为int

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

规则3示例:

class MyFunc{
public:
   int m_i;
}
 
int main()
{
    const MyFunc obj;
    decltype(obj.m_i) a=0;//a的类型为int
    decltype((obj.m_i)) b=a;//b的类型为int&
 
    int c=0,d=0;
    decltype(c+d) x=0;//c+d得到一个右值,x的类型为int
    decltype(c=c+d) y=x;//c=c+d得到一个左值,y的类型为int &

    int i = 42, * p22 = &i;
    decltype((p22)) temp = p22; //temp的类型为int*&
    bool bValue3 = std::is_same<decltype(temp), int*&>::value; //true

    return 0;
}

左值:表达式执行结束后依然存在的数据,即持久性数据;右值是指那些在表达式执行结束不再存在的数据,即临时性数据。一个区分的简单方法是:对表达式取地址,如果编译器不报错就是左值,否则为右值。

3.典型应用场景

1.推导函数返回类型(配合 auto 和尾置返回类型)

C++11 中,当函数返回类型依赖参数类型时,可用 decltype 推导:

template <typename T1, typename T2>  
auto add(T1 a, T2 b) -> decltype(a + b) {  // 尾置返回类型,推导返回值为 a+b 的类型  
    return a + b;  
}  

C++14 后支持直接用 auto 作为返回类型(编译器自动推导,本质依赖 decltype):

template <typename T1, typename T2>  
auto add(T1 a, T2 b) {  // C++14 及以后,等价于上述写法  
    return a + b;  
}  

 2.获取容器迭代器类型(避免手动书写复杂类型)

#include <vector>  
std::vector<int> vec;  
decltype(vec.begin()) it = vec.begin();  // 推导 it 为 std::vector<int>::iterator  

3.定义模板中的类型别名(结合 typedef/using

template <typename T>  
using ptr_type = decltype(&T::member);  // 推导类成员指针的类型  

4.处理表达式的精确类型(包括引用 /const 限定)

int x = 0;  
const int& rx = x;  
decltype(rx) var1 = x;  // var1 是 const int&(保留引用和 const)  
decltype(x) var2 = rx;  // var2 是 int(x 是普通变量,推导为值类型,丢失 const 和引用)  

4.实际案例

在STL内部很多地方都用到了decltype,一般都是结合auto使用。

C++17之std::invoke: 使用和原理探究(全)_c++新特性 invoke-CSDN博客

std::invoke内部实现很多地方都用到了decltype,  它是根据_Callable的不同分派到不同的_Invoker1去处理,_Invoker1调用_Call的返回值推导都用到了decltype,从部分源代码可以看出:

enum class _Invoker_strategy {
    _Functor,
    _Pmf_object,
    _Pmf_refwrap,
    _Pmf_pointer,
    _Pmd_object,
    _Pmd_refwrap,
    _Pmd_pointer
};

struct _Invoker_functor {
    static constexpr _Invoker_strategy _Strategy = _Invoker_strategy::_Functor;

    template <class _Callable, class... _Types>
    static constexpr auto _Call(_Callable&& _Obj, _Types&&... _Args) noexcept(
        noexcept(static_cast<_Callable&&>(_Obj)(static_cast<_Types&&>(_Args)...)))
        -> decltype(static_cast<_Callable&&>(_Obj)(static_cast<_Types&&>(_Args)...)) {
        return static_cast<_Callable&&>(_Obj)(static_cast<_Types&&>(_Args)...);
    }
};

struct _Invoker_pmf_object {
    static constexpr _Invoker_strategy _Strategy = _Invoker_strategy::_Pmf_object;

    template <class _Decayed, class _Ty1, class... _Types2>
    static constexpr auto _Call(_Decayed _Pmf, _Ty1&& _Arg1, _Types2&&... _Args2) noexcept(
        noexcept((static_cast<_Ty1&&>(_Arg1).*_Pmf)(static_cast<_Types2&&>(_Args2)...)))
        -> decltype((static_cast<_Ty1&&>(_Arg1).*_Pmf)(static_cast<_Types2&&>(_Args2)...)) {
        return (static_cast<_Ty1&&>(_Arg1).*_Pmf)(static_cast<_Types2&&>(_Args2)...);
    }
};

struct _Invoker_pmf_refwrap {
    static constexpr _Invoker_strategy _Strategy = _Invoker_strategy::_Pmf_refwrap;

    template <class _Decayed, class _Refwrap, class... _Types2>
    static constexpr auto _Call(_Decayed _Pmf, _Refwrap _Rw, _Types2&&... _Args2) noexcept(
        noexcept((_Rw.get().*_Pmf)(static_cast<_Types2&&>(_Args2)...)))
        -> decltype((_Rw.get().*_Pmf)(static_cast<_Types2&&>(_Args2)...)) {
        return (_Rw.get().*_Pmf)(static_cast<_Types2&&>(_Args2)...);
    }
};

struct _Invoker_pmf_pointer {
    static constexpr _Invoker_strategy _Strategy = _Invoker_strategy::_Pmf_pointer;

    template <class _Decayed, class _Ty1, class... _Types2>
    static constexpr auto _Call(_Decayed _Pmf, _Ty1&& _Arg1, _Types2&&... _Args2) noexcept(
        noexcept(((*static_cast<_Ty1&&>(_Arg1)).*_Pmf)(static_cast<_Types2&&>(_Args2)...)))
        -> decltype(((*static_cast<_Ty1&&>(_Arg1)).*_Pmf)(static_cast<_Types2&&>(_Args2)...)) {
        return ((*static_cast<_Ty1&&>(_Arg1)).*_Pmf)(static_cast<_Types2&&>(_Args2)...);
    }
};

struct _Invoker_pmd_object {
    static constexpr _Invoker_strategy _Strategy = _Invoker_strategy::_Pmd_object;

    template <class _Decayed, class _Ty1>
    static constexpr auto _Call(_Decayed _Pmd, _Ty1&& _Arg1) noexcept -> decltype(static_cast<_Ty1&&>(_Arg1).*_Pmd) {
        return static_cast<_Ty1&&>(_Arg1).*_Pmd;
    }
};

struct _Invoker_pmd_refwrap {
    static constexpr _Invoker_strategy _Strategy = _Invoker_strategy::_Pmd_refwrap;

    template <class _Decayed, class _Refwrap>
    static constexpr auto _Call(_Decayed _Pmd, _Refwrap _Rw) noexcept -> decltype(_Rw.get().*_Pmd) {
        return _Rw.get().*_Pmd;
    }
};

struct _Invoker_pmd_pointer {
    static constexpr _Invoker_strategy _Strategy = _Invoker_strategy::_Pmd_pointer;

    template <class _Decayed, class _Ty1>
    static constexpr auto _Call(_Decayed _Pmd, _Ty1&& _Arg1) noexcept(noexcept((*static_cast<_Ty1&&>(_Arg1)).*_Pmd))
        -> decltype((*static_cast<_Ty1&&>(_Arg1)).*_Pmd) {
        return (*static_cast<_Ty1&&>(_Arg1)).*_Pmd;
    }
};

每个_Call的返回值都是通过auto结合decltype来实现的。

再比如我们编写一个通用的加法算法,代码如下:

//auto是起到占位符的作用因为返回值不可以不给
template<typename T1, typename T2>
auto add(T1&& t1, T2&& t2) ->decltype(std::forward<T1>(t1) + std::forward<T2>(t2))
{
    return std::forward<T1>(t1) + std::forward<T2>(t2); 
}
int main()
{
	cout << typeid(decltype(add(1, 1.2))).name() << endl; //推断出类型为:double
	return 0;
}

为啥不能把decltype(std::forward<T1>(t1) + std::forward<T2>(t2))写在前面代替auto呢?那是因为编译器编译代码从左到右编译,那么将decltype(std::forward<T1>(t1) + std::forward<T2>(t2)) 写到返回值处,这时编译器就懵逼了t1和t2是个啥东西,因为这里还没有编译到t1和t2;所以我们一般都是写到函数名后面,函数的返回值用占位符auto来表示。

5.与 auto 的区别

特性autodecltype
推导依据初始化值的 值类型(忽略引用 / 顶层 const)表达式的 类型属性(保留引用 /const)
是否需要初始化必须初始化(根据值推导类型)无需初始化(仅推导类型,不计算表达式)
典型场景变量类型推导(简化代码)复杂表达式 / 模板中的类型推导(泛型编程)

举例对比:

int x = 5;  
const int& rx = x;  

auto a = rx;       // a 是 int(rx 是 const int&,auto 推导为值类型,丢失 const 和引用)  
decltype(rx) b = x; // b 是 const int&(保留引用和 const)  

6.decltype与typeid的区别

  typeinfo获取对象 / 类型在 运行时的具体类型信息,用于类型识别、类型比较等场景(依赖 C++ 的 RTTI 机制)。

  • typeid运算符,用于获取类型对应的 std::type_info 对象的引用。
  • std::type_info,封装了类型的元数据(如类型名称、相等性比较等)。

常用用法

1.获取类型的 type_info 对象

int x = 10;  
const std::type_info& ti1 = typeid(x);       // 通过对象获取  
const std::type_info& ti2 = typeid(int);     // 直接通过类型名获取  
  • 对 基本类型intdouble 等)和 自定义类类型 均有效。
  • 若对 指针 应用 typeid,获取的是指针本身的类型(如 int* 的 type_info),而非指针指向的类型。

2.比较类型是否相等

class Base {};  
class Derived : public Base {};  

Base b;  
Derived d;  

std::cout << (typeid(b) == typeid(Base)) << std::endl;  // 1(true)  
std::cout << (typeid(d) == typeid(Base)) << std::endl;  // 0(false)  

对于 多态类(包含虚函数的类),typeid 会返回对象的 实际动态类型(运行时类型):

class Base { virtual void foo() {} };  
class Derived : public Base {};  

Base* ptr = new Derived();  
std::cout << (typeid(*ptr) == typeid(Derived)) << std::endl;  // 1(true,动态类型推导)  

注意:此时 ptr 是基类指针,但 *ptr 的 typeid 会根据实际指向的对象(Derived)返回类型信息(依赖虚函数表,RTTI 机制的核心)。

3.获取类型名称(调试用)

std::cout << typeid(int).name() << std::endl;       // 输出 "i"(编译器相关,不可靠)  
std::cout << typeid(Derived).name() << std::endl;  // 输出类名(如 "7Derived",不同编译器格式不同)  
  • name() 的返回值 依赖编译器实现(如 GCC、Clang、MSVC 输出不同),不可用于逻辑判断,仅作调试参考。

注意事项

  • RTTI 的开启:默认情况下 C++ 编译器会启用 RTTI,但部分场景(如为了优化体积)可能通过编译器选项(如 GCC 的 -fno-rtti)关闭,此时 typeid 会报错。
  • 对 void 类型的限制typeid(void) 合法,但不能创建 void 类型的对象,也无法获取 void 对象的 type_info
  • 性能开销:RTTI 是运行时机制,涉及类型信息的存储和查询,可能带来轻微性能损耗(相比编译时机制)。

核心区别

        typeid关键字用于在运行时获取对象的类型信息,返回一个std::type_info对象。而decltype关键字用于在编译时推导表达式的类型。typeid关键字主要用于运行时类型识别(RTTI)场景,而decltype关键字主要用于编译时类型推导。

维度typeinfo(RTTI)decltype(编译时推导)
作用时机运行时(程序运行期间获取类型信息)编译时(推导类型,不产生运行时开销)
类型来源对象 / 类型的实际运行时类型(多态类支持动态类型)表达式的静态类型(编译期确定的类型属性)
典型用途类型比较(如 dynamic_cast 辅助)、调试输出类型名模板编程中的类型推导、复杂类型简化
依赖机制依赖编译器的 RTTI 支持(可能被关闭)语言核心特性(C++11 及以上必支持)
返回类型std::type_info 对象(含类型元数据)表达式的精确类型(可带引用 /const 限定)

7. decltype与std::decay的关系

C++之std::decay_c++ std::decay-CSDN博客

        std::decay是一个模板类,用于模拟传值调用的效果,去除引用和限定符。当需要通过decltype推导类型,同时希望去除引用和限定符时,可以结合std::decay使用。

int x = 42;
int& rx = x;
decltype(rx) a = rx; // a的类型是int&
typename std::decay<decltype(rx)>::type b = rx; // b的类型是int

8.总结

         decltype可以作用于变量、表达式及函数名。①作用于变量直接得到变量的类型;②作用于表达式,结果是左值的表达式得到类型的引用,结果是右值的表达式得到类型;③作用于函数名会得到函数类型,不会自动转换成指针。       

        总的来说,decltype在C++中是一个非常强大的工具,它允许开发者以更简洁和灵活的方式处理复杂的类型问题。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值