C++11,14,17中auto和decltype相关知识及拓展

2022/2/9 14:13:33

本文主要是介绍C++11,14,17中auto和decltype相关知识及拓展,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

C++11,14,17auto和decltype相关知识及拓展

  • 前言
  • 从初始化器和表达式中推导( Deduction from Initializers and Expressions)
  • auto类型说明符
    • 复合类型,常量和auto
    • 进一步探讨auto类型说明符
      • auto与右值引用
      • 推导返回类型 c++14
      • 可推导的非类型参数(Deducible Nontype Parameter)until c++17
  • 用decltype表示表达式的类型(Expressing the Type of an Expression with decltype)
    • prvalue,xvalue和lvalue与decltype的关系:
  • decltype(auto) c++14
    • 在递归模板中延迟返回类型推断([Delaying return type deduction in recursive templates](https://stackoverflow.com/questions/24109737/what-are-some-uses-of-decltypeauto#Delaying-return-type-deduction-in-recursive-templates))


前言

本篇文章为笔者的读书笔记,未经允许请勿转载。如果对你有帮助记得点个赞(●’◡’●)
这篇文章是上篇文章《C++prvalue,xvalue和lvalue的相关知识》的续作,上次我们已经把prvalue,xvalue和lvalue说清楚了,本篇文章就来探讨一下prvalue,xvalue和lvalue与decltype之间的联系。顺便咱们也把auto类型说明符也都拓展一下。


从初始化器和表达式中推导( Deduction from Initializers and Expressions)

C++11包括声明一个类型是从其初始化器推导出变量类型的能力(auto)。它还提供了一种机制来表示已命名对象(变量或函数)或表达式的类型(decltype)。这些设施原来非常方便,而C++14和C++17在这个主题上增加了额外的变体。


auto类型说明符

编程时常常需要把表达式的值赋给变量,这就要求在声明变量的时候清楚地知道表达式的类型。c++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。

//由val1和val2相加的结果可以推断出item的类型
auto item = val1 + val2; // item初始化为val1和val2相加的结果

此处编译器将根据val1val2相加的结果来推断item的类型。如果val1val2是类sales_item的对象,则item的类型就是sales_item;如果这两个变量的类型是double,则item的类型就是double,以此类推。


复合类型,常量和auto

编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。

首先,正如我们所熟知的,使用引用其实是使用引用的对象,特别是当引用被用作初始值时,真正参与初始化的其实是引用对象的值。此时编译器以引用对象的类型作为auto的类型:

int i = 0,&r = i;
auto a = r;
// a的类型为int(r是i的别名,而i是一个整数)

其次,auto一般会忽略掉顶层const,同时底层const则会保留下来,比如当初始值是一个指向常量的指针时:

const int ci = i, &cr = ci;
auto b = ci;// b的类型为int(ci的顶层const 特性被忽略掉了)
auto c = cr;// c的类型为int( cr是ci的别名,ci本身是一个顶层const )
auto d = &i;// d是一个整型指针(int *)
auto e = &ci;// e是一个指向整数常量的指针(const int *)(对常量对象取完地址后,常量对象的顶层const在auto处转变为底层const)

如果希望推断出的auto类型是一个顶层const,需要明确指出:

const auto f = ci;		// ci的推演类型是int,f是const int

还可以将引用的类型设为auto,此时原来的初始化规则仍然适用:

auto &g = ci;		// g是一个整型常量引用,绑定到ci
auto &h = 42;	//错误:不能为非常量引用绑定字面值(42的类型为int,左值引用不能绑定到右值)
const auto &j=42;	//正确:可以为常量引用绑定字面值(42发生临时物化,产生一个xvalue的临时对象让常量引用绑定)

设置一个类型为auto的引用时,初始值中的顶层常量属性仍然保留。和往常一样,如果我们给初始值绑定一个引用(auto &g = ci;),则此时的const就不是顶层const了(g的类型为const int &,此处的const为底层const)。
要在一条语句中定义多个变量,切记,符号&*只从属于某个声明符,而非基本数据类型的一部分,因此初始值必须是同一种类型:

auto k = ci, &l = i;// k是int,l是int&
auto &m = ci,*p = &ci;// m是对整型常量的引用(const int &),p是指向整型常量的指针(const int *)。
auto &n= i, *p2 = &ci;//错误:i的类型是int而&ci的类型是const int

以上部分为auto类型说明符的基础部分,大部分例子为《c++primer》p61页的,我在其基础上添加了注释以及对其进行部分修改,以便读者能快速领悟其含义。其他基础部分若有不懂请自行补充,不再赘述。


进一步探讨auto类型说明符

auto类型说明符可用于许多地方(主要是namespace作用域和local作用域),以从其初始化器推导变量的类型。在这种情况下,auto被称为占位符类型(a placeholder type)(另一种占位符类型deltype(auto),文章后面将会描述)。例如,您可以使用:

template<typename Container> 
void useContainer(Container const& container) 
{ 
		auto pos = container.begin(); //auto示例一
		while (pos != >container.end()) 
			{ 
				auto& element = *pos++; //auto示例二
				… // operate on the element 
			} 
}

上面示例中auto的两种使用消除了编写两种长且可能复杂的类型,即容器的迭代器类型和迭代器的值类型:

typename Container::const_iterator pos = container.begin();
…
typename std::iterator_traits<typename Container::iterator>::reference element = *pos++;

自动推导使用与模板参数推导相同的机制。auto类型说明符可以被一个虚构的模板类型参数T取代,然后推导继续进行,就好像该变量是一个函数形参,它的初始化器相当于函数实参。对于第一个auto示例,它对应于以下情况:

template<typename T> 
void deducePos(T pos); 
deducePos(container.begin());

这里T看作auto,是要被推导出的类型。这样做的直接后果之一是,auto类型的变量永远不会是引用类型。
在第二个auto示例中使用auto&说明了如何生成一个引用类型的推导。其推导相当于以下函数模板和调用:

template<typename T> 	deduceElement(T& element);
deduceElement(*pos++);

在这里,element总是引用类型,并且它的初始化器不能生成一个临时对象。


auto与右值引用

也可以将auto与右值引用组合起来,但这样做使其行为像一个转发的引用(a forwarding reference),例如:
auto&& fr = …;
我们还是基于函数模板来看待它:

template<typename T> void f(T&& fr);// auto replaced by template parameter T

解释如下:

int x; 
auto&& rr = 42; // OK: rvalue reference binds to an rvalue 
个(auto = int)
auto&& lr = x; // Also OK:reference collapsing makes. lr an lvalue reference
个(auto = int&)

这种技术经常用于代码中绑定函数或操作符调用的结果对象,且不知道结果对象的值类别(lvalue vs.rvarue),进而不必复制该结果对象。
例如,它通常是在基于范围的循环中声明迭代值的首选方法:

template<typename Container> 
void g(Container c)
{ 
		for (auto&& x: c) { … } 
}

这里我们不知道容器迭代接口的签名( the signatures of the container’s iteration interfaces),但是通过使用auto&&,我们可以确信我们正在遍历的值没有产生额外的副本。如果需要完美转发绑定值,则可以像往常一样在变量上调用std::forward<T>()。这使得一种“延迟”的完美转发成为可能。有关示例,请参见《c++ template 2nd》p167。
除了引用之外,还可以组合auto说明符来创建const对象、指针、成员指针等等,但auto必须声明成“main”类型说明符(基本数据类型)。它不能嵌套在模板参数中或跟在基本数据类型后面的声明部分中( part of the declarator that follows the type specifier)。具体请看下面的示例:

template<typename T> struct X { T const m; }; 
auto const N = 400u; // OK: constant of type 

auto* gp = (void*)nullptr; // OK: gp has type void* 
auto const S::*pm = &X<int>::m; // OK: pm has type int const

X<auto> xa = X<int>(); // ERROR: auto in template 

int const auto::*pm2 = &X<int>::m; // ERROR: auto is >part of the “declarator”

最后两个例子不让通过的原因在于C++委员会认为,额外的实施成本和滥用潜力超过了好处



这篇关于C++11,14,17中auto和decltype相关知识及拓展的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程