目录
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.案例
在STL内部很多地方都用到了decltype,一般都是结合auto使用。
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来表示。
4.decltype与typeid的区别
typeid关键字用于在运行时获取对象的类型信息,返回一个std::type_info对象。而decltype关键字用于在编译时推导表达式的类型。typeid关键字主要用于运行时类型识别(RTTI)场景,而decltype关键字主要用于编译时类型推导。
5. decltype与std::decay的关系
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
6.总结
decltype
可以作用于变量、表达式及函数名。①作用于变量直接得到变量的类型;②作用于表达式,结果是左值的表达式得到类型的引用,结果是右值的表达式得到类型;③作用于函数名会得到函数类型,不会自动转换成指针。
总的来说,decltype
在C++中是一个非常强大的工具,它允许开发者以更简洁和灵活的方式处理复杂的类型问题。