详解C++11中的类型推断

C++11中的类型推断

C++11中为了更好的支持泛型编程,提供了 autodecltype两个关键词,目的就是提供编译阶段的自动类型推导。

1.auto关键词的新意义

在C++98中,auto是一个类型修饰符,用以显式声明自动变量(局部变量的),而在C++11中,这一用法已经弃用,现在auto用于声明变量。

1.1 auto声明变量

我们通过几个例子看一下,auto声明变量时的规则。

    int x;
    int *y;
    double foo();
    int& bar();
    auto *a=&x;//int *
    auto &b=x;//int &
    auto c=y;//int *
    auto *d=y;//int *
    auto e=bar();// int 
    auto &f=bar();//int &

1.如果使得auto声明的变量是引用类型,必须使用auto &.

2.如果变量本身是指针类型,则auto *auto是一样的,这里*变成冗余符号

    double foo();
    float * bar();
    const auto a=foo();//const double 
    const auto & b=foo();//const double &
    volatile auto  * const c=bar();//volatile float * const

    auto d=a;//double
    auto &e=a;//const double &
    auto f=c;//volatile float *
    volatile auto &g=c;//volatile float * &

3.声明为auto的变量不能从其初始化表达式中带走顶层cv限定符.

auto i=1,j=1.12f;//编译错误

4.auto可以声明多个变量,不过这些变量必须类型相同

1.2 auto无法推导的情况

#include<vector>
using namespace std;
void fun(auto x=1){}//错误
struct str
{
    auto var=10;//错误
};
int main()
{
    char x[3];
    auto y=x;
    auto z[3]=x;//错误
    vector<auto>V={1,2,3};//错误
}

auto不能作为函数形参(这是模板函数的事情)类中,auto不能用来声明非静态成员auto不能声明数组auto不能用于模板实参 2.decltype类型推导

decltypeauto都是用来类型推导的,不过 decltype的功能更加强大 ,下面就是decltype的简单用法

2.1 decltype的应用场景

一种情况是,decltypeauto一样来声明变量:

#include<typeinfo>
#include<iostream>
using namespace std;
int main()
{
    int i;
    decltype(i) j=0;
    cout<<typeid(j).name()<<endl;//i

    float a;
    double b;
    decltype(a+b) c;
    cout<<typeid(c).name()<<endl;//d
}

另一种情况是:typedefusing配合使用decltype

using  size_t=decltype(sizeof(0));
using  ptrdiff_t=decltype((int*)0-(int*)0);
using  nullptr_t=decltype(nullptr);

顺便一提,在C++11中,using已经完美替代typedef:

#include<iostream>
#include<type_traits>
#include<string>
#include<map>
using namespace std;

typedef unsigned int UINT;
using uint=unsigned int;

template<typename T>
using Mapstring=map<T,char*>;
Mapstring<int> number_string;

int main()
{
    cout<<is_same<uint,UINT>::value<<endl;//1
    cout<<is_same<map<int,char*>,decltype(number_string)>::value<<endl;//1
}

typedef能干的事情,using都能干,但是using能干的,比如给模板取一个别名,typedef做不到

decltype的另一种功能就是给匿名的结构体或者枚举推导类型来重用,

enum class {K1,K2,K3} anon_e;
decltype(anon_e) as;

2.2 decltypeauto更加精确

首先decltypeauto最明显的不同就是,decltype(e)它和sizeof()差不多,可以接收一个参数,不过这里我讲的不同是,同一个变量,使用decltypeauto得到的结果不同。

说直接点,decltype的类型推断比auto准确

const int ic=0;
decltype(ic) a;//const int
auto b=ic;//int

volatile int iv;
decltype(iv) c;//volatile int
auto d=iv;//int

struct S
{
    int i;
};
const S cs={0};
decltype(cs.i) e;//int

auto它不能带走变量的顶层cv限定符,而decltype(e)却可以带走ecv限定符,所以说,decltype的类型推断更加准确。还要一点细节,就是说类本身是用cv限定符修饰的,而类成员使用decltype时确推断不出来。

我们知道,auto只能带走指针类型,却无法带走引用类型,而decltype就可以同时带走引用和指针

#include<iostream>
#include<type_traits>
using namespace std;
int main()
{
    int i=1;
    decltype(i) & var1=i;// int &
    cout<<is_lvalue_reference<decltype(var1)>::value<<endl;//1

    int &j=i;
    decltype(j) var2=i;
    decltype(j)& var3=i;
    cout<<is_same<decltype(j),decltype(j)&>::value<<endl;//1,`&`的冗余

    int* p=&i;
    decltype(p) var4=&i;//int *
    decltype(p)* var5=&p;//int **

    const int k=1;
    const decltype(k) var6=1;//const int `const`冗余
}

上面这段代码,信息量很大

首先,decltype(e)可以带走e的引用和指针类型

其次,decltype(e)会对&cv限定符产生冗余,而不会对*产生冗余

最后,如果不确定decltype(e)的类型,可以使用<type_traits>头文件中的一些方法

总之,就是一句话:decltype(e)能直接返回e的准确类型

但是,如果decltype更加优越,那么为什么还要auto呢?

一种说法是auto用法更加简单,更重要的原因是,autolambda函数的配合使得,C++11相对于C++98,变得脱胎换骨,我个人认为C++11最重要的就是lambda函数

2.3 decltype对于表达式的推断

我们知道在decltype(e)中,e被要求是一个表达式,即expression,而在上面我们所讲的e通常是一个名称,即id_expression,如果e是一个非名称的表达式,那么推断结果也会不同

int i;
decltype(i) a;//int
decltype((i)) b;//int &

在上面例子中,i就是一个id_expression,而(i)它不是id_expression,而是一个左值表达式,所以上述推导结果会不同。

我们直接来看decltype(e)的推导细则

  • 如果eid_expression或者类成员表达式,decltype(e)的结果就是e本身的类型
  • 否则,如果e是左值表达式,设它的类型是T,那么decltype(e)的结果就是T&
  • 否则,如果e是将亡值,设它的类型是T,那么decltype(e)的结果就是T&&
  • 否则,如果e是纯右值,设它的类型是T,那么decltype(e)的结果就是T
int i=4;
int arr[5]={0};
int *ptr=arr;
struct S
{
    double d;
}s;
void foo(int);
void foo(double);
int && Rref();//函数返回值是将亡值
const bool Func(int);

decltype(arr) var1;//int[5]
decltype(ptr) var2;//int *
decltype(s.d) var3;//double
decltype(foo) var4;//无法通过编译,foo被重载

decltype(Rref()) var5;//int && 

decltype(true? i:i) var6;//int&
decltype((i)) var7;//int &
decltype(++i) var8;//int &
decltype(arr[3]) var9;// int &
decltype(*ptr) var10;//int &
decltype("abc") var11;//const char(&) [4]

decltype(1) var12;//int
decltype(i++) var13;//int
decltype(Func(1)) var14=true;//const bool

3.追踪返回类型

autodecltype可以进行配合使用,来实现泛型编程中的追踪返回类型

template<class T1,class T2>
decltype(t1+t2) sum(T1& t1,T2& t2)
{
    return (t1+t2);
}

上面这段代码,想法狠简单,但是它都无法通过C++11和C++98中的编译器,因为编译器是从左往右读的,读到decltype(t1+t2)时,t1t2没有声明,所以无法通过编译,我们可以通过返回类型后置的方法实现上述功能

template<typename T1,typename T2>
auto sum(T1& t1,T2& t2)->decltype(t1+t2)
{
    return (t1+t2);
}

上面就是追踪返回类型,最终sum函数的返回类型由decltype(t1+t2)确定,而不是auto确定,如果我们把->decltype(t1+t2)去掉,那么最终返回类型就由auto指定,我们其实很不希望这样,因为auto并不精确,decltype更加精确。

追踪返回类型其实就是返回类型后置,它的还有一种用法就是,提高函数指针的可读性:

#include<type_traits>
#include<iostream>
using namespace std;
int (*(*pf())())(){
    return nullptr;
}

auto pf1() ->auto (*)() -> int (*)()
{
    return nullptr;
}
int main()
{
    cout<<is_same<decltype(pf),decltype(pf1)>::value<<endl;//1
}

上述代码中,pfpf1都是函数指针,其返回的也是一个函数指针,该函数指针又返回一个函数指针,不过明显pf1的定义方式可读性更高。

原文地址:https://blog.csdn.net/m0_71009069/article/details/128800260