C++ 11类型推导 auto和decltype
占位符类型
对于变量,指定要从其初始化器自动推导出其类型。
对于函数,指定要从其 return 语句推导出其返回类型。(C++14 起)
对于非类型模板形参,指定要从实参推导出其类型。(C++17 起)
auto和decltype都是占位符类型
占位符类型说明符可出现于下列语境:
变量的类型说明符中
new 表达式中的类型标识。
(C++14 起) 函数或 lambda 表达式的返回类型中
(C++17 起) 非类型模板形参的形参声明中
more
auto
auto可以在声明变量时根据变量初始值的类型自动为此变量选择匹配的类型。
规则及用法
auto
关键字指示编译器使用已声明变量的初始化表达式,或使用 lambda 表达式参数来推导其类型。
使用auto关键字做类型自动推导时,依次施加以下规则:
如果初始化表达式是引用,则去除引用语义。
如果初始化表达式为const或volatile(或者两者兼有),则除去const/volatile语义。
这一组规则同于模板函数的模板参数推导(template argument deduction)时的规则。但auto关键字可以从C++11风格的花括号{与}包围的值列表推导出std::initializer_list;而模板函数的形参推导时不认为这种值列表是一个类型,因此不能由值列表推导出std::initializer_list类型。
auto 不能在函数的参数中使用。
auto 不能作用于类的非静态成员变量(也就是没有 static 关键字修饰的成员变量)中。
auto 关键字不能定义数组。
auto 不能作用于模板参数。
如果auto关键字带上&号,则不去除const语意。
初始化表达式为数组时,auto关键字推导类型为指针。
用auto声明的变量必须初始化
auto不能与其他类型组合连用
定义在堆上的变量,使用了auto的表达式必须被初始化
定义在一个auto序列的变量必须始终推导成同一类型
auto不能自动推导成CV-qualifiers (constant & volatile qualifiers)
auto会退化成指向数组的指针,除非被声明为引用
建议
auto
关键字指示编译器使用已声明变量的初始化表达式,或使用 lambda 表达式参数来推导其类型。
建议你在 auto
大多数情况下使用关键字,除非你确实需要转换,因为它具有以下优势:
可靠性: 如果表达式的类型发生更改(这包括在函数返回类型发生更改时),则它将正常工作。
性能: 您可以保证不会进行任何转换。
可用性: 无需担心类型名称拼写错误和拼写错误。
效率: 你的编码可能更高效。
可能不想使用的转换情况 auto
:
当需要特定类型且不需要执行任何其他操作时。
表达式模板帮助程序类型,例如 (valarray+valarray)
。
若要使用 auto
关键字,请使用它(而不是类型)来声明变量,并指定初始化表达式。 此外,还可以 auto
使用说明符和声明符(如 const
、 volatile
、指针 (\*
) 、引用 (&
) 和右值引用 (&&
) 来修改关键字。 编译器计算初始化表达式,然后使用该信息来推断变量类型。
初始化表达式可以是赋值 (等号语法) 、直接初始化 (函数样式的语法) 、 operator new 表达式或初始化表达式可以是基于范围的 for 语句 (c + +) 语句中的范围声明参数。 有关详细信息,请参阅本文档后面的 初始值设定项 和代码示例。 auto 关键字是类型的占位符,但它本身不是类型。 因此, auto 关键字不能用于 sizeof c + +/cli) 的强制转换或运算符(例如和) typeid 。
示例
根据初始化表达式自动推断被声明的变量类型
1 2 3 4 auto f=3.14 ; auto s ("hello" ) ; auto z = new auto (9 ); auto x1 = 5 , x2 = 5.0 , x3='r' ;
但是,这么简单的变量声明类型,不建议用auto关键字,而是应更清晰地直接写出其类型。
auto关键字更适用于类型冗长复杂、变量使用范围专一时,使程序更清晰易读:
1 2 3 4 5 std ::vector <int > vect; for (auto it = vect.begin(); it != vect.end(); ++it){ std ::cin >> *it; }
或者保存lambda表达式类型的变量声明:
1 auto ptr = [](double x){return x*x;};
在模板函数定义时,如果变量的类型依赖于模板参数,使用auto关键字使得在编译期确定这些类型,如:
1 2 3 4 5 template <class T , class U >void Multiply (T t, U u) { auto v = t * u; std ::cout <<v; }
模板函数的返回类型如果也是依赖于从模板参数推导,
1 2 3 4 5 6 template <typename _Tx, typename _Ty>auto multiply(_Tx v1, _Ty v2) -> decltype( _Tx * _Ty ) { return v1*v2; } auto result = multiply(101 , 1.414 );
使用auto关键字声明变量的类型,不能自动推导出顶层的CV-qualifiers,也不能自动推导出引用类型,需要显式指定。
1 2 3 4 5 6 const int v1 = 101 ;auto v2 = v1; v2=102 ; auto al = { 10 , 11 , 12 };template <class T> void foo (T arg) ; foo(v2);
如果需要具有顶层的CV-qualifiers,或者引用的类型,解决办法是显式指明:
1 2 3 const auto & v3=v1;foo<const int &>(v1); template <class T> void foo (const T& arg) ;
如果auto关键字还带上&号,声明引用类型,则不执行const剥除(const-stripping),例如:
1 2 3 const int c = 0 ;auto & rc = c;rc = 44 ;
这是因为如果不抑制const剥除,则得到了一个非常量引用型变量,指向了const变量,这显然是不可接受的。模板参数推导也遵循此规则。
初始化表达式为数组,auto关键字推导的类型为指针。这是因为数组名在初始化表达式中自动隐式转换为首元素地址的右值。例如:
1 2 3 int a[9 ];auto j = a;std ::cout << typeid (j).name() << " " <<sizeof (j)<<" " <<sizeof (a)<< std ::endl ;
由于C++规定字符串字面量是左值,因此可以通过&运算符直接取地址:
请注意,使用 auto
"删除引用"、" const
限定符" 和 " volatile
限定符"。 请考虑以下示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std ;int main ( ) { int count = 10 ; int & countRef = count; auto myAuto = countRef; countRef = 11 ; cout << count << " " ; myAuto = 12 ; cout << count << endl ; }
在上面的示例中,myAuto 是一个 int
,而不是一个 int
引用,因此输出是 11 11
, 11 12
如果引用限定符未被删除,则不会出现这种情况 auto
。
用大括号内初始值设定项进行类型推导 (c + + 14)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <initializer_list> int main () { auto A = { 1 , 2 }; auto B = { 3 }; auto C{ 4 }; auto D = { 5 , 6.7 }; auto E{ 8 , 9 }; return 0 ; }
auto关键字的类型完美转发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <vector> #include <typeinfo> using namespace std ; struct Widget { };Widget makeWidget () { return Widget(); } int main () { Widget&& var1 = Widget(); auto && var2 = var1; std ::vector <int > v = { 1 , 2 , 3 }; auto && val = v[0 ]; Widget&& var3 = makeWidget(); Widget var4 = static_cast <Widget&&>(var1); std ::cout << typeid (var1).name() << std ::endl ; std ::cout << typeid (var2).name() << std ::endl ; std ::cout << typeid (val).name() << std ::endl ; std ::cout << typeid (var3).name() << std ::endl ; std ::cout << typeid (var4).name() << std ::endl ; }
输出:
1 2 3 4 5 6Widget 6Widget i 6Widget 6Widget
iter
elem
当 for
和范围 for
循环开始时,以下代码段将声明变量的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <deque> using namespace std ;int main () { deque <double > dqDoubleData (10 , 0.1 ) ; for (auto iter = dqDoubleData.begin(); iter != dqDoubleData.end(); ++iter) { } for (auto elem : dqDoubleData) { } for (auto & elem : dqDoubleData) { } for (const auto & elem : dqDoubleData) { } }
下面的代码片段使用 new
运算符和指针声明来声明指针。
1 2 double x = 12.34 ;auto *y = new auto (x), **z = new auto (&x);
下一个代码片段在每个声明语句中声明多个符号。 请注意,每个语句中的所有符号将解析为同一类型。
1 2 3 4 auto x = 1 , *y = &x, **z = &y; auto a(2.01), *b (&a); // Resolves to double. auto c = 'a' , *d(&c); auto m = 1 , &n = m;
此代码片段使用条件运算符 (?:
) 将变量 x
声明为值为 200 的整数:
1 2 int v1 = 100 , v2 = 200 ;auto x = v1 > v2 ? v1 : v2;
下面的代码片段将变量初始化 x
为类型,将变量引用为对类型的 int
引用,并将变量初始化为指向 y
const int
fp
返回类型的函数的指针 int
。
1 2 3 4 5 6 7 8 9 10 int f (int x) { return x; }int main () { auto x = f(0 ); const auto & y = f(1 ); int (*p)(int x); p = f; auto fp = p; }
在定义模板函数时,用于声明依赖模板参数的变量类型。
1 2 3 4 5 6 template <typename _Tx,typename _Ty>void Multiply (_Tx x, _Ty y) { auto v = x*y; std ::cout << v; }
模板函数依赖于模板参数的返回值
1 2 3 4 5 template <typename _Tx, typename _Ty>auto multiply(_Tx x, _Ty y)->decltype(_Tx*_Ty) { return x*y; }
当模板函数的返回值依赖于模板的参数时,我们依旧无法在编译代码前确定模板参数的类型,故也无从知道返回值的类型,这时我们可以使用auto。格式如上所示。 decltype操作符用于查询表达式的数据类型,也是C++11标准引入的新的运算符,其目的也是解决泛型编程中有些类型由模板参数决定,而难以表示它的问题。 auto在这里的作用也称为返回值占位 ,它只是为函数返回值占了一个位置,真正的返回值是后面的decltype(_Tx*_Ty)。为何要将返回值后置呢?如果没有后置,则函数声明时为:
1 decltype (_Tx*_Ty)multiply(_Tx x, _Ty y)
而此时_Tx,_Ty还没声明呢,编译无法通过。
auto 用于泛型编程
auto 的另一个应用就是当我们不知道变量是什么类型,或者不希望指明具体类型的时候,比如泛型编程中。我们接着看例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 include <iostream> using namespace std ;class A {public : static int get (void ) { return 100 ; } }; class B {public : static const char * get (void ) { return "http://c.biancheng.net/cplus/" ; } }; template <typename T>void func (void ) { auto val = T::get(); cout << val << endl ; } int main (void ) { func<A>(); func<B>(); return 0 ; }
运行结果:
本例中的模板函数 func() 会调用所有类的静态函数 get(),并对它的返回值做统一处理,但是 get() 的返回值类型并不一样,而且不能自动转换。这种要求在以前的 C++ 版本中实现起来非常的麻烦,需要额外增加一个模板参数,并在调用时手动给该模板参数赋值,用以指明变量 val 的类型。
但是有了 auto 类型自动推导,编译器就根据 get() 的返回值自己推导出 val 变量的类型,就不用再增加一个模板参数了。
下面的代码演示了不使用 auto 的解决办法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std ;class A {public : static int get (void ) { return 100 ; } }; class B {public : static const char * get (void ) { return "http://c.biancheng.net/cplus/" ; } }; template <typename T1, typename T2> void func (void ) { T2 val = T1::get(); cout << val << endl ; } int main (void ) { func<A, int >(); func<B, const char *>(); return 0 ; }
定义在堆上的变量,使用了auto的表达式必须被初始化
1 2 3 4 5 int * p = new auto (0 ); int * pp = new auto (); auto x = new auto (); auto * y = new auto (9 ); auto z = new auto (9 );
decltype
decltype用于获取表达式的数据类型。declare type
规则及用法
若表达式e
为一个无括号的变量、函数参数、类成员访问,那么返回类型即为该变量或参数或类成员在源程序中的“声明类型”;
否则的话,根据表达式的值分类(value categories),设T为e的类型:
若e
是一个左值(lvalue,即“可寻址值”),则decltype(e)
将返回T&
;
若e
是一个临终值(xvalue),则返回值为T&&
;
若e
是一个纯右值(prvalue),则返回值为T
。
1 2 decltype (entity)decltype (expression)
示例
基本使用
1 2 3 4 5 6 7 8 9 const int bar () ;int i;struct A { double x; };const A* a = new A();decltype (foo()) x1; decltype (bar()) x2; decltype (i) x3; decltype (a->x) x4; decltype ((a->x)) x5;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 const int * bar () { return new int [0 ]; } struct A { double x; }; template <class T > T tFoo (const T& t) { return t; } bool func () { return false ; } struct Foo { template <typename T, typename U> static decltype ((*(T*)0 ) * (*(U*)0 )) foo (const U& arg1, const T& arg2) { return arg1 * arg2; } }; template <typename T, typename U> struct Bar { typedef decltype ((*(T*)0 ) + (*(U*)0 )) btype ; static btype bar (T t, U u) ; }; int main () { int i = 4 ; const int j = 6 ; const int & k = i; int a[5 ]; int *p; decltype (i) var1; decltype (1 ) var2; decltype (2 +3 ) var3; decltype (i=1 ) var4 = i; decltype ((i)) var5 = i; decltype (j) var6 = 1 ; decltype (k) var7 = j; decltype ("decltype" ) var10 = "decltype" ; decltype (a) var8; decltype (a[3 ]) var9 = i; decltype (*p) var11 = i; decltype (tFoo(A())) var12; decltype (func()) var13; decltype ((func())) var14; decltype (func) var15; decltype (&func) var16; decltype (&A::x) var17; decltype (Foo::foo(3.0 , 4u )) var18; decltype (Bar<float , short >::bar(1 ,3 )) var19; return 0 ; }
错误用法示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int func () { return 0 ; } int func (int a) { return 0 ; } int main () { int i = 4 ; decltype (func) var1; decltype (func(i)) var2; return 0 ; }
将decltype与结构成员变量一起使用的规则
当您使用decltype(expression)获取类型时,expression是对象表达式的非括号成员变量(带有.
运算符)或指针表达式(带有->
运算符),则以下规则适用:
如果用常量或易变限定符指定对象表达式或指针表达式,则类型限定符对decltype(expression)的结果无贡献。
对象表达式或指针表达式的左值或右值不影响decltype(expression)是否为引用类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 struct Foo { int x; }; int main () { struct Foo f ; const struct Foo g = {0 }; volatile struct Foo * h = &f; struct Foo func () ; decltype (g.x) var1; decltype (h->x) var2; decltype (func().x) var3; return 0 ; }
如果表达在decltype(声明表达式)是一个圆括弧结构成员变量,父对象表达或指针表达式的常数或挥发性类型限定符表达有助于decltype(的结果表达)。
同样,对象表达式或指针表达式的左值或右值会影响decltype(expression)的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct Foo { int x; }; int main () { int i = 1 ; struct Foo f ; const struct Foo g = {0 }; volatile struct Foo * h = &f; struct Foo func () ; decltype ((g.x)) var1 = i; decltype ((h->x)) var2 = i; decltype ((func().x)) var3 = 1 ; return 0 ; }
与using/typedef合用,用于定义类型。
1 2 3 4 5 6 7 8 9 using size_t = decltype (sizeof (0 ));using ptrdiff_t = decltype ((int *)0 - (int *)0 );using nullptr_t = decltype (nullptr );vector <int >vec;typedef decltype (vec.begin()) vectype ;for (vectype i = vec.begin; i != vec.end(); i++){ }
这样和auto一样,也提高了代码的可读性。
重用匿名类型
在C++中,我们有时候会遇上一些匿名类型,如:
1 2 3 4 5 struct { int d ; doubel b; }anon_s;
而借助decltype,我们可以重新使用这个匿名的结构体:
括号是必不可少的,但也是需要慎重使用的,因为表达式如果被括号括着,会被当做通常的左值表达式,所以decltype(x)
和decltype((x))
通常会得到不同的结果:
1 2 3 int i; decltype(i) a; // a: int decltype((i)) b; // b: int &,无法编译通过
推导出表达式类型
1 2 int i = 4 ;decltype (i) a;
与using/typedef合用,用于定义类型。
1 2 3 4 5 6 7 8 9 using size_t = decltype (sizeof (0 ));using ptrdiff_t = decltype ((int *)0 - (int *)0 );using nullptr_t = decltype (nullptr );vector <int >vec;typedef decltype (vec.begin()) vectype ;for (vectype i = vec.begin; i != vec.end(); i++){ }
这样和auto一样,也提高了代码的可读性。
重用匿名类型
在C++中,我们有时候会遇上一些匿名类型,如:
1 2 3 4 5 struct { int d ; doubel b; }anon_s;
而借助decltype,我们可以重新使用这个匿名的结构体:
配合auto
和decltype
,可以自动推导函数返回值类型:
1 2 3 4 5 template<class T, class U> auto add(T t, U u) -> decltype(t + u) // the return type is the type of operator+(T, U) { return t + u; }
decltype与typeid的区别
typeid
的作用与decltype
相似,都可以得到一个变量或者表达式的类型,不同的是,typeid
方法得到的类型不能用于定义变量,可以用来进行类型的比较。
1 2 3 4 5 6 7 8 9 int i = 10; decltype(i) x = 20; if (typeid(i) == typeid(x)) { cout << "ture" << endl; } else { cout << "false" << endl; } //使用typeid(x).name()还可以打印出这个类型 cout << typeid(x).name() << endl;
decltype与auto的区别
decltype
与auto
的一个重要的区别是:decltype
的结果类型与表达式形式密切相关。
如果变量名加上一对括号,得到的类型与不加括号时会有所不同,如果decltype
使用的是一个不加括号的变量,则得到的结果就是该变量的类型 ;如果给变量加上一层或多层括号,编译器会把它当成是一个表达式,变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype
就会得到引用类型。
1 2 3 int i = 10; decltype((i)) x; //错误,x是int &类型,必须要初始化 decltype(i) y; //正确,y是未初始化值的int类型
decltype处理const与引用类型
decltype
处理const
和引用的方式与auto
有些许不同。如果decltype
使用的表达式是一个变量,则decltype
返回该变量的类型
1 2 3 4 5 const int i = 0, &j = i; decltype(i) x = 0; //x的类型是const int decltype(j) y = x; //y的类型是const int&,y绑定变量x decltype(j) z //报错,因为z是const int&类型,必须被初始化 复制代码
如果decltype
使用的表达式不是一个变量,则decltype
则返回表达式结果对应的类型
1 2 3 4 5 6 7 int i = 1, *p = &i, &q = i; decltype(q + 0) x; //表达式q + 0的结果是int类型,所以b的类型是未初始化的int类型 decltype(*p) y; //报错,y是int &类型,必须被初始化 /*如果表达式的内容是解引用,则decltype将得到引用类型。 正如我们熟悉的那样,解引用指针可以得到指针所指的对象, 而且还能给这个对象赋值。因此decltype(*p)的类型就是int &,而非int */
在下面的代码示例中,myFunc
模板函数的后指定返回类型取决于 t
和 u
模板参数的类型。 作为最佳编码做法,该代码示例还使用右值引用和 forward
支持 完美转发 的函数模板。
1 2 3 4 5 6 7 8 9 //C++11 template<typename T, typename U> auto myFunc(T&& t, U&& u) -> decltype (forward<T>(t) + forward<U>(u)) { return forward<T>(t) + forward<U>(u); }; //C++14 template<typename T, typename U> decltype(auto) myFunc(T&& t, U&& u) { return forward<T>(t) + forward<U>(u); };
下面的代码示例声明模板函数 Plus()
的后指定返回类型。 Plus
函数通过重载处理两个操作数 operator+
。 因此,该函数的 (+
) 和函数的返回类型的解释 Plus
取决于函数参数的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <iostream> #include <string> #include <utility> #include <iomanip> using namespace std ;template <typename T1, typename T2>auto Plus(T1&& t1, T2&& t2) -> decltype (forward<T1>(t1) + forward<T2>(t2)) { return forward<T1>(t1) + forward<T2>(t2); } class X { friend X operator +(const X& x1, const X& x2) { return X(x1.m_data + x2.m_data); } public : X(int data) : m_data(data) {} int Dump () const { return m_data;} private : int m_data; }; int main () { int i = 4 ; cout << "Plus(i, 9) = " << Plus(i, 9 ) << endl ; float dx = 4.0 ; float dy = 9.5 ; cout << setprecision(3 ) << "Plus(dx, dy) = " << Plus(dx, dy) << endl ; string hello = "Hello, " ; string world = "world!" ; cout << Plus(hello, world) << endl ; X x1 (20 ) ; X x2 (22 ) ; X x3 = Plus(x1, x2); cout << "x3.Dump() = " << x3.Dump() << endl ; }
1 2 3 4 Plus(i, 9) = 13 Plus(dx, dy) = 13.5 Hello, world! x3.Dump() = 42
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> #include <utility> template <class T , class U >auto add (T t, U u) { return t + u; } template <class F , class ... Args >decltype (auto ) PerfectForward (F fun, Args&&... args) { return fun(std ::forward<Args>(args)...); } template <auto n> auto f() -> std::pair<decltype(n), decltype(n)> // auto 不能从花括号初始化器列表推导 { return {n, n}; } int main () { auto a = 1 + 2 ; auto b = add(1 , 1.2 ); static_assert (std ::is_same_v<decltype (a), int >); static_assert (std ::is_same_v<decltype (b), double >); auto c0 = a; decltype (auto ) c1 = a; decltype (auto ) c2 = (a); std ::cout << "a, before modification through c2 = " << a << '\n' ; ++c2; std ::cout << "a, after modification through c2 = " << a << '\n' ; auto [v, w] = f<0 >(); auto d = {1 , 2 }; auto n = {5 }; auto m{5 }; auto lambda = [](int x) { return x + 3 ; }; }
1 2 a, before modification through c2 = 3 a, after modification through c2 = 4
如果使用decltype(expression )获取类型,则可以在decltype括号上下文中执行其他操作,但是这些操作在decltype上下文之外没有副作用。
1 2 int i = 5 ;static const decltype (i++) j = 4 ;
此规则有例外。在以下示例中,由于赋予decltype的表达式必须有效,因此编译器必须执行模板实例化:
1 2 3 4 5 6 template <int N>struct Foo { static const int n=N; }; int i; decltype (Foo<101 >::n,i) var = i;
Reference
The decltype(expression) type specifier (C++11) - IBM Documentation
decltype specifier - cppreference.com
C++11特性:auto关键字 - melonstreet - 博客园 (cnblogs.com)