c++类型推导

来源:互联网 发布:日上免税店 mac 编辑:程序博客网 时间:2024/06/07 09:32

类型推导

现代c++类型推导分为三个,一个为模板函数的形参推演。一个为auto的类型推导。一个decltype的类型推导。auto的类型推导是以模板函数的类型推导为基础的。

模板函数类型推导

推导过程

模板函数的类型推导,是通过调用表达式即实参的类型与模板函数相应的形参类型依依匹配以此来推导出模板参数类型。

template<typename T>void f(ParamType param);f(expr); // deduce T and ParamType from expr

如上可以看出模板参数和形参同不是一个东西。因为形参是可以包含更多的修饰的。举个列子这里的形参可以写成 T*/T&/const T。故函数模板类型推导分为三种情况。

函数模板形参推导规则

1当形参是指针或者引用时(引用不能是一个万能引用)

推导时,模板参数会忽略掉实参的指针/引用类型。并且模板参数是通过函数形参和实参一个一个匹配后的结果来推导出来的。

template<typename T>void f(T& param); int x = 27; //int const int cx = x; // const int const int& rx = x; //const int & f(x); // T int, param int&f(cx); // T  const int   ,  param const int&f(rx); // T const int  ,    param  const int&~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~template<typename T>void f(const T& param); // param const T &int x = 27; // 如上const int cx = x; // 如上const int& rx = x; // 如上f(x); // T int  , param const int&                               f(cx); // T int , param  const int&                                               f(rx); //  T int ,param  const int&                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~          template<typenameT>void f(T* param); // param T*int x = 27; // const int *px = &x; // px 为一个指向const int的指针                f(&x); // T int ,  param  int *f(px); // T const int ,  param const int *

从以上列子可以看出对于函数形参类型是指针或者引用的函数模板,类型推演看的就是我上面说的那俩点。模板参数忽略引用和指针,具体类型看调用表达式和形参匹配的结果。

2 函数模板形参是以传值类型的形参

对于这种模板类型推导规则为,既忽略实参的CV限定符又会忽略掉实参的引用类型(首先指针类型不能被忽略,其次 忽略的const 是忽略引用的const , 对于指针来说,指针的底层const 不会被忽略 , 很想吐槽一句什么gui啊,要忽略把俩个有底层const的类型都忽略了啊,为什么只忽略引用的,不忽略指针的)

template<class T>void f( T param){}int x = 27; // const int cx = x; //const int& rx = x; // f(x); // 俩个都为int f(cx); //都为int f(rx); //都为int特殊情况 const int * p = &x;f(p); // 贼恐怖这个列子,忽略const却不能忽略掉指针的底层const,所以T与param 都被推导成了      //                                              const int * // 底层 const 只有俩个,一个是 const type & ,一个是 const type * ,都是表明指向对象为const 

如上可以看出调用表达式的const 与 指针 在推导的过程中全被忽略掉了。形参以值方式的时候,无论 param 还是 T 都会忽略掉volatile/const/&。

这里需要记住一个特点,在推演时只有当形参以值传递时,const 限定符才能被忽略,其他任何方式的推演都不能忽略掉const限定符

3 当形参类型是万能引用时的类型推导

万能引用只有在模板推导和在auto中auto&&定义变量时才会出现。它的写法是右值引用的写法看起来像是这样子的:

template<typename T>void f(T&& param); // param 参数是一个万能引用

第一点:对于模板参数是万能引用,虽然形参看起来像一个右值引用,但是它不是右值引用它一个万能引用。
第二点:对于当实参传递是左值时,param和T都会被推演成相应类型的左值引用。当实参传递的是一个右值的时候,推导类型参数和情况1一样。这里需要注意的一点是虽然param被声明成右值引用的语法,但当实参传递为一个左值的时候,param却被推导成了一个左值的引用!!!!

template<typename T>void f(T&& param); //万能引用int x = 27; const int cx = x; const int& rx =x;f(x); // x左值 , 所以T 和 param 都被推导成了 int &= x; f(cx); // cx左值,所以 T 和 param 都被推导成了 const int &f(rx);//rx左值,T和param都被推导成了 const int&f(27); // 27为右值,所以T和param推导规则遵循第一种情况的,T被推导成了int,param int&&

4一些杂七杂八的推导

数组和函数形参的推导会退化为指针,除非形参类型为引用时才不会发生退化。类型会被推导成相应的数组或者函数的引用。一个区别就是对param取sizeof对于数组来说大小是不一样的。

auto类型推导

auto的推导规则是以函数模板推导规则为基础的。当出现auto声明时,编译器就把它当作一个相应的模板函数来处理。在auto类型推导中,auto扮演着函数模板中T的角色,而类型说明符( const int a=1 , 如这个列子 const int 就是类型说明符)扮演着形参param的角色。另外auto推导依赖于初始化的值,所以被auto声明的必须初始化。

                          template<typename T> auto x = 27              void func_for_x(T param);                          func_for_x(x);                          template<class T> const auto & rx=x        void func_for_x(const T & param);                          func_for_rx(x);                            template<class T> const auto &&rx=x/27     void func_for_x(T && param)                          func_for_ux(x)/ func_for_ux(27)                           

上面这三个列子都是理论上编译器在编译auto声明的变量时发生的转换。可以看出类型说明符就相当于param参数。而auto就相当于T。

auto与模板类型推导的唯一一个不同点

基本上auto类型推导可以看成是模板类型推导。但是当遇见列表初始化的时候就不一样了。也仅仅当出现列表的时候,它们俩个才不一样。

auto x1 = 27; // type is int, value is 27auto x2(27); // 如上auto x3 = { 27 }; // type is std::initializer_list<int> value is { 27 }auto x4{ 27 }; // 如上

首先从上面的列子可以看出来正常的初始化时auto推导等同于模板推导的,但是列表初始化是不等同于模板推导的。如果转换成函数模板的话:

                  template<class T>auto x3 = { 27 }  void fun_to_x3(T param)                  fun_to_x3({ 27 })

auto转换成函数模板如上所示。如果熟悉模板的一眼就可以看出这个编译是不能通过的。这个param应该为 std::initializer_list < T >这个类型。这样才能编译通过。
所以在auto用于列表初始化的时候,实际上这里发生了俩步类型推导
1.auto自己特有的类型推导,先推导出 std::initializer_list< T > 这个模板类类型。
2.推导完第一步后再推导类型T。推导类型T就属于模板类型推导的范畴了。
所以综上这个在列表初始化时,auto 的类型推导与 模板类型推导是不同的。另外需要注意的一个点是:当用auto声明变量初始化时,用列表初始化,类型总是被推导成相应的 initializer_list< T > 的类型。如 auto x ={1} , 感觉这个x 应该是int,但是十分坑的是它推导出来的类型是 std::initializer_list< int > 所以还是很坑的。

auto的其他坑

1 auto当函数返回值的坑

我们知道c++11中尾随返回值的语法,返回类型出 必须有一个auto出现。大概写出的样子是这样的, auto Fun( ) -> int。这个auto并不用于类型推导,它仅仅只是尾随返回值语法的一部分。
在 c++ 14 中,auto可以真正的用于返回值类型,编译器可以自行完成推导。但是十分坑爹的是,虽然auto可以直接用于返回值的类型推导,但是它使用的是 模板推导 规则。这就代表着如果你返回一个列表对象,还是不能推导出来相应的类型,会编译报错。因为模板类型T ,是无法同时推导出 initializer_list < T > 这俩个类型的。

2 auto 在 lambda 表达式中的坑

在 c++14中,auto可以直接当 lambda 表达式的param形参类型,但是坑爹的是使用的推导规则还是 模板的规则,所以碰到列表还是不能编译通过。

example:auto object= [](const auto& x){};object({1,2,3})

3 auto用在返回值时,以值类型返回的时候会忽略引用类型

template<typename Container, typename Index> auto authAndAccess(Container&& c , Index i) //万能引用{ authenticateUser();return std::forward<Container>(c)[i]; //完美转发确保c是左值引用,c[i]就是左值引用}                                     //c是右值引用,c[i]就是右值引用std::vector<int> v;authAndAccess(v,5)=10; // 我们这里传的是左值,应当返回一个左值引用但是这里报错右值不能被修改。                       //这里因为auto忽略引用的原因,返回的是一个vector<int>是一个右值

这个auto会忽略掉引用类型。所以当我们使用函数的返回值的时候会编译报错。=号左边必须是一个左值。这个坑添了在c++14中,不过不是auto填的,是decltype(auto)填的这个坑。

decltype类型推导

其实对于decltype没什么说的,这个用起来很舒服,基本上推导出来的变量的类型是正确的。如果出错也是应用场景有问题,decltype没问题。需要注意的一点就是,如果被推导的是一个左值表达式,decltype推导结果是这个左值表达式相应类型的引用。
还有个地方需要注意的是,(names) 这个特殊的表达式,c++ 定义对于变量括上( )后是一个特殊的表达式,并且是左值表达式。

decltype(auto)

这个东西是一个类型说明符就像int一样。这个东西专门是用来填auto做返回值的坑的,但是它不仅用于返回值还可以用到变量声明等其他地方。它其中的auto代表这个类型说明符是用于类型推导的,decltype代表它推导的规则是decltype的规则。如下完美的填了这个坑。

template<typename Container, typename Index> decltype(auto) authAndAccess(Container&& c , Index i){ authenticateUser();return std::forward<Container>(c)[i];}std::vector<int> v;authAndAccess(v,5)=10;

decltype(auto)的坑

其实这个坑是我们自己的用法错误,不能怪decltype(auto)。

decltype(auto) Fun(T a , T b)    {       int ret= (a>b)? a : b;       return (ret);// c++ 中(变量)是一个特殊的左值表达式    }

根据decltype的推导规则来看,推导的结果是相应类型的一个引用。所以这里返回了一个局部变量的引用。引发的错误有多么蠢相比大家知道。

总结

其实类型推导真的很方便,增加了代码的扩展性。十分方便,只要我们注意不踩这些坑,这些新特性用起来真的很舒服。以下是我对此的总结:

第一点

对于decltype的理解,它应用于一个变量名时,也就是正常的用法时,它做的就是公布该变量声明时的类型而已,可以理解比推导靠谱100倍,公布不会出错!

第二点

对于auto类型推导时,我们可以把它转换为一个函数模板来看,auto就是模板类型T,param 形参就是类型说明符。

第三点

那对于函数模板的类型推导,这个是最基础的如果想理解上面的俩个类型推导符,可以从这个先入手。

显式看出类型推导的类型

不好意思各位,虽然上面已经写总结俩字,但是后面又出现这个。很明显接下来内容与语法无关。接下来是一些如何看类型推导的类型结果的一些小技巧 :
1. 把鼠标光标落在被类型推导符修饰的变量上,如果是一些简单的类型推导会有相应的类型出现。因为我们日常使用的IDE中是有个c++编译器在你写代码的时候就一直在编译,你出现错误它会下相应位置落红线,基于这个原来可以看下一些简单的类型推导的结果
2. 用一个编译期错误,让编译期报错显示被推导出来的类型,这个技巧是这样子的:

template<class T> class Type;auto a = something;Type<decltype(a)> result;

以下列子会在编译期报错,我们可以看下报错的信息中相应模板出对应的类型,这个技巧很实用哦。
3. 使用typeid(object).name() 这个运行时显示相应的类型吧
这个极度不推荐,因为是运行期,我们在写代码的时候十分不方便还得跑到被测出。而且不同编译器name函数返回的信息不同,有的编译器返回的信息神踏马难以理解。

原创粉丝点击