c++11 之 decltype

C++中,decltype作为操作符,用于查询表达式数据类型。decltype在C++11标准制定时引入,主要是为泛型编程而设计,以解决泛型编程中,由于有些类型由模板参数决定,而难以(甚至不可能)表示之的问题。

泛型编程在整个1990年代越发流行,对实现类型推导机制的需求也应运而生。为此,许多编译器厂商都基于程序语言现有的功能,自行实现了这类操作符,其实现如typeof,以及一些功能有限,但更易移植的实现。2002年间,比雅尼·斯特劳斯特鲁普提议在C++内标准化这类操作符,并将之加入C++;且建议命之为“decltype”,以反映其具有获取表达式的“声明类型”(Declared Type)的功能。

从语义上说,decltype的设计适合于通用库编写者与编程新手。总体上说,对于目标对象或函数,由decltype推导出的类型与源码中的定义可精确匹配。而正如sizeof操作符一样,decltype亦不需对操作数求值。

中文名

操作符

外文名

decltype

用    途

查询表达式

实    质

数据类型

目录


1目录


2设计构想


3语义


4可用性


5例子

1目录编辑

  • 1 设计构想
  • 2 语义
  • 3 可用性

2设计构想编辑

随着C++引入模板,以及由标准模板库引领的泛型编程逐渐兴起,实现一个能获取表达式类型的机制的需求便由此出现,而这一机制常称为typeof。在泛型编程中,若类型由函数参数决定,则获知之常非易事,在需要获取函数模板实例化的返回类型时尤然。

许多厂商以编译器扩展的形式,提供了typeof操作符,以满足这一需求。早在C++还未完全标准化的1997年,布莱恩·帕克(Brian Parker)就基于sizeof操作符,提出了一种可移植的解决方案。对此,比尔·吉本斯(Bill Gibbons)则提出,这一方案仍有诸多限制,而且通常来说,直接引入typeof机制效果都更好。2000年10月,安德烈·亚历山德雷斯库在IT技术杂志《Dr. Dobb‘s Journal》上评论道:“(若)有typeof(操作符),撰写和理解模板代码就会便易许多。”他也提到“typeof和sizeof(操作符)有相同的后端,(这是)因为sizeof无论如何必须去计算类型。”安德鲁·克尼格与芭芭拉·E·摩(Barbara E. Moo)也谈到内建于程序语言中的typeof功能非常有用,但也提醒道“使用时常会引入一些难以发觉的程序错误,且尚有无法解决的问题(即并非万用)。”并提出可以利用类型转换(如使用标准模板库所提供的typedef),更有效、更通用地实现这一功能。但是,史蒂夫·丹斯特(Steve Dewhurst)则称如此转换“在设计与发布上花费巨大”,而且“采用直接提取表达式类型的方法更简单。”(大意)2011年间,在一片关于C++0x的文章中,克尼格和摩预言道:“decltype将会广泛用于为每日的程序编写提供便利。”

2002年间, 比雅尼·斯特劳斯特鲁普提议扩充C++程序语言,为之引入查询表达式类型,以及不必指明类型便可初始化对象的机制。斯特劳斯特鲁普注意到,在GCCEDG编译器中,typeof所提供的“引用丢弃”(reference-dropping)语义可能存在问题;另一方面,若使用基于表达式左值性、返回一个引用类型操作符实现之,又难以理解。于是,在呈交给C++标准委员会的初始提案中,便将两种实现方法杂糅起来:只有当表达式的声明类型包含一个引用时,操作符才会返回一个引用类型。为强调推导出的类型能确实反映表达式的声明类型,提案中提议将此操作符命名为decltype。提案还提及了decltype的一项主要设计初衷,也即让编写完美的转发函数成为可能。在编程时,程序员有时需要编写一个泛型转发函数,使之不论以何种类型实例化,都能返回同于包装函数的类型,而若无decltype操作符,就几乎不可能做到这一点。decltype的样例代码如下所示,其中利用了C++11标准中的“返回类型后置”(trailing-return-type)语法。

int& foo(int& i);float foo(float& f); template <class T> auto transparent_forwarder(T& t) −> decltype(foo(t)) { return foo(t);}

decltype便是本段代码的核心部分,用于保存“包装函数是否返回一个引用类型”这一信息 。

3语义编辑

类似于sizeof操作符,decltype也不需对其操作数求值。粗略来说,decltype(e)返回类型前,进行了如下推导:

  1. 若表达式e指向一个局部变量命名空间作用域变量、静态成员变量或函数参数,那么返回类型即为该变量(或参数)的“声明类型”;
  2. 若e是一个左值(lvalue,即“可寻址值”),则decltype(e)将返回T&,其中T为e的类型;
  3. 若e是一个x值(xvalue),则返回值为T&&;
  4. 若e是一个纯右值(prvalue),则返回值为T。

这些语义是为满足通用库编写者的需求而设计,但由于decltype的返回类型总与对象(或函数)的定义类型相匹配,这对编程新手来说也更为直观。更正式地说,规则1适用于不带括号的标识符表达式(id-expression)与类成员访问表达式。示例如下:

const int&& foo();const int bar();int i;struct A { double x; };

const A* a = new A();

decltype(foo()) x1; // 类型为const int&&

decltype(bar()) x2; // 类型为int

decltype(i) x3; // 类型为int

decltype(a->x) x4; // 类型为double

decltype((a->x)) x5; // 类型为const double&

由上可见,最后两个对decltype的调用,返回结果有所不同。这是因为,带括号的表达式(a->x)既非“标识符表达式”,亦非类访问表达式,因而未指向一个命名对象,而是一个左值,于是推导类型便为“指向表达式类型的引用”,亦即const double&。

在2008年12月,雅克·雅尔维(Jaakko Järvi)向标准委员会指出一个问题:在C++中,“带限定标识符”(qualified-id)无法由decltype作成,而这正与“decltype(e)可作‘类型定义名’(typedef-name)看待”的设计初衷不一致。在评论标准委员会为C++0xC++11前名)制定的正式草案时,日本ISO会员成员提到,“一个定义域操作符(::)不适用于decltype,但本应适用才对。(若能解决这一问题,则)这在需要从实例中获取成员类型(嵌套类型)很有用,如下所示”:

vector<int> v;decltype(v)::value_type i = 0; // int i = 0;

这一问题,以及其他相似问题(关于decltype无法在派生类声明和析构函数调用中使用),都交由大卫·范德沃德(David Vandevoorde)处理,并在2010年3月投票纳入工作日程表。

4可用性编辑

decltype包含于当前的C++标准C++11中,并由许多编译器以扩展的形式提供:微软在Visual C++ 2010编译器中提供了decltype操作符,基本实现了标准委员会提案中所描述的语义,并且在托管代码或原生代码中都可使用。据其文档称,这一实现“主要对编写模板库的开发者有用。”从2008年3月5日发布的4.3版开始,GCCC++编译器也加入了decltype操作符。这一操作符也已纳入了Codegear的C++ Builder 2009、Intel C++编译器Clang。[1]

5例子编辑

#include <algorithm>

#include <iostream>

#include <iterator>

#include <ostream>

#include <string>

#include <utility>

#include <vector>

using namespace std;

struct Plus {

template <typename T, typename U>

auto operator()(T&& t, U&& u) const

-> decltype(forward<T>(t) + forward<U>(u)) {

return forward<T>(t) + forward<U>(u);

}

};

int main() {

vector<int> i;

i.push_back(1);

i.push_back(2);

i.push_back(3);

vector<int> j;

j.push_back(40);

j.push_back(50);

j.push_back(60);

vector<int> k;

vector<string> s;

s.push_back("cut");

s.push_back("flu");

s.push_back("kit");

vector<string> t;

t.push_back("e");

t.push_back("ffy");

t.push_back("tens");

vector<string> u;

transform(i.begin(), i.end(), j.begin(), back_inserter(k), Plus());

transform(s.begin(), s.end(), t.begin(), back_inserter(u), Plus());

for_each(k.begin(), k.end(), [](int n) { cout << n << " "; });

cout << endl;

for_each(u.begin(), u.end(), [](const string& r) { cout << r << " "; });

cout << endl;

}

结果:

41 52 63

cute fluffy kittens

如果在C++98,你不得不传递模板参数类型来调用plus<int>() 和 plus<string>(),重复声明一次元素类型。[2]

参考资料
时间: 2024-08-02 11:09:05

c++11 之 decltype的相关文章

c++11——auto,decltype类型推导

c++11中引入了auto和decltype关键字实现类型推导,通过这两个关键字不仅能够方便的获取复杂的类型,而且还能简化书写,提高编码效率.     auto和decltype的类型推导都是编译器在编译的时候完成的,auto是通过定义auto变量时候给出的表达式的值推导出实际类型,并且在声明auto变量时必须马上初始化:decltype通过表达式的值推导出实际的类型,但是可以只声明变量,而不赋值. auto类型推导 1. auto推导 auto x = 5; //被编译器推导为int类型 au

C++11之decltype

使用场景 在C++中常常要用到非常长的变量名.假设已经有变量和你将使用的变量是一个类型.就可以使用decltypekeyword 来申明一样的类型变量. decltype原理      返回现有变量类型.decltype是一个keyword,而不是一个函数,这有啥差别呢?decltype在编译阶段返回变量类 型,而不是在执行阶段传递不同变量返回不同值. decltype使用范例 1.复杂已知变量类型 map<string, vector<string>> str_map; decl

c++11 auto 和 decltype

一. auto简介编程时候常常需要把表达式的值付给变量,需要在声明变量的时候清楚的知道变量是什么类型.然而做到这一点并非那么容易(特别是模板中),有时候根本做不到.为了解决这个问题,C++11新标准就引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型.和原来那些只对应某种特定的类型说明符(例如 int)不同.auto 让编译器通过初始值来进行类型推演.从而获得定义变量的类型,所以说 auto 定义的变量必须有初始值. 1 //由val_1 和val_2相加的结果可以推断出it

C++ 11 vlearning

1.新增算术类型     longlong,最小不比long小,一般为64位. 2.列表初始化      int units_sold = {0};或者 int units_sold{0};非11标准下的C++中,只有特定的情况下才能使用该形式.  比如数组的初始化,类构造函数的初始化,结构体的填充.相比传统的赋值初始化,如果右侧的数值类型相对于左侧类型更大的话,侧对于这种窄化现象,编译器会 报错.如:int k = {3.14};一个double是8个字节,int一般是4个字节,这时编译器就会

C++11老关键字的新含义(auto, using,extern)

http://blog.csdn.net/cnsword/article/details/8034947 公司可以使用c++11.看大牛的代码模仿使用,所以现在已经不知道什么使用的是c++的语法还是c++11的语法了...不知道算不算是一种悲哀 C++11对关键字进行了修订,加入了nullptr.constexpr.decltype.default.static_assert等,同时原有的关键字(auto,using,extern)含义和用途进行了修订.在这里主要了解一下对auto.using.

逐个使用C++11新特性

C++11 auto & decltype auto:根据变量初始值来推导变量类型,并用右值初始化变量. decltype:从表达式推导出类型,并将变量定义为该类型,但不用表达式的值初始化该变量. 这里要注意下:decltype(i)--是i的类型,而decltype((i))就是引用了.就像上面例子中的x 就是int &x; 右值引用 新标准在拷贝构造函数.拷贝赋值运算符和析构函数之外,还增加了移动构造函数和移动赋值运算符,而这二位就需要右值引用的支持. 1. 延长将亡值的生命. 1 /

C++_bool

1 0 1 #include <iostream> 2 using namespace std; 3 4 void main() 5 { 6 bool bl = 1 && 1 || 2 || -1 && 0; 7 8 std::cout << bl << std::endl;//1 9 10 std::cout << typeid(bl).name() << std::endl;//bool,获取变量bl的数据类型

C++ Primer

再次重温下C++ Primer 第一章.开始 1.GNU编译器使用g++ $ g++ -o prog1 prog1.cc 2. while(std::cin >> value) 使用一个istream作为条件时,如果遇到Eof,或无效输入(不匹配类型)则判断为假,跳出 第二章:变量和基本类型 一.基本内置类型 1.如何选择类型 明确知道不可能为负,定义为unsigned型 整数运算使用int型 浮点运算使用double型 如果需要一个不大的整数,要明确是signed char 或者unsign

从RPC开始(二)、序列化

在C++的世界里构建一个序列化框架:并非一件困难的事情,但也并非简单.因此,需要分成两部分来完成这项任务: 1.序列化容器. 2.序列化方式. 前者,很容易理解:但也决定着我们将要存储数据的方式:二进制抑或其他.二进制方式,很容易想到和使用的方式:但也最容易以极不安全的方式去使用:因为,为了各种原因,在存储时我们极易丢掉原本的类型信息,使得一切都靠"人工约定"这种很不靠谱的方式.而其他方式,如文本,我们则可以相对地在其中保留很多信息:即使最后的成品并非是让人类来阅读的,但构建过程中,为