decltype()

作者: 部洪波 | 来源:发表于2022-03-11 00:16 被阅读0次

摘要

本文是关于 decltype() 的学习笔记。各种版权都属于原作者。如果存在侵权行为,请通知本人删除。

decltype()

这是个什么东西?对于指定的名字或者表达式decltype() 能给出它的类型,就仿佛 sizeof() 能告诉我对象的尺寸。

名字或表达式(举例) decltype 的用法 结果类型
const int i = 0; decltype(i) //普通变量 const int // 变量的类型
bool f(const Widget& w) decltype(w) //函数形参
decltype(f) //函数名称
const Widget& //形参的类型
bool(const Widget&) //函数的类型
struct point {
int x, y;
};
decltype(Point::x) //结构体成员
decltype(Point::y)
int
int
Widget w decltype(w) //类的实例对象 Widget //对象所属的类
if ( f(w) ) ... decltype(f(w)) //函数调用表达式 bool //函数的返回类型
template<typename T>
class vector {
public:
...
T& operator[](std::size_t index);
...
};
vector<int> v;
...
if (v[0]==0)...
std::vector 的简化版
decltype(v)//模板类的实例划对象名字

decltype(v[0])// 取元素的表达式
vector<int>

int&

从这张表大概可以感受到decltype()的能力和推导规则,基本上就是有一说一。

貌似可以翻译为拓印类型或者叫类型拓印,类似于中国传统的拓印技术。

最佳实践:代码案例研究

假设我们想要撰写一个函数,其形参包括一个容器,支持方括号下标语法(即[])和一个下标,并会在返回下标操作结果前进行用户验证。函数的返回值型别必须与下标操作结果的返回值型别相同。

下标操作返回的是对容器内元素的引用。

版本一 C++11 (v1.0)

template<typename Container, typename Index>
auto authAndAccess( Container& c, Index i)
  -> decltype(c[i]) // 学生以为这种写法着实难看, 不是C++的传统风格。
{
    authenticateUser();
    return c[i];
}

这里的auto与类型推导没有任何关系,它仅仅是C++11中的函数的返回类型尾序语法的指示(trailing return type syntax),即 返回类型放在形参列表之后(在 -> 之后)。尾序返回值的好处是可以使用形参。

版本二 C++14(有缺陷 v0.1)

template <typename Container, typename Index)
auto authAndAccess( Container &c, Index i ) // c++14
{
    authenticationUser();
    return c[i];  // 函数的返回类型根据 c[i] 推导,有点问题
}

auto 这里不再是个简单的指示了,而是要求编译器推导函数的返回类型,即c[i]类型。

还有点儿问题,在哪儿呢?看一段使用它的代码:

std::deque<int> d;
// ...
authAndAccess(d, 5) = 10; 
// 验证用户,并返回d[5],然后将其赋值为10(因为
// 从逻辑上函数返回的似乎是int&引用,所以使用者认为可以赋值修改),
// 但这个代码无法通过编译

原来,在模板类型推导规则中,如果是在初始化表达式中,那推导结果类型中的引用修饰就被去掉了。编译时会爆出如下的错误:

[ ~/workspace/temp ] %  g++ -std=c++14 -Wall -o decltype decltype.cc
decltype.cc:20:23: error: expression is not assignable
  authAndAccess(d, 5) = 10;
  ~~~~~~~~~~~~~~~~~~~ ^

剥掉了引用属性的返回类型是一个int型右值,无法被赋值修改。

如果我把函数调用放到赋值的右边,并用一个引用型变量接收,看看编译器报告的错误信息:

int & v = authAndAccess(d, 5)
//           ^^^^^^^^^^^^^^^^
// error: non-const lvalue reference to type 'int' 
// cannot bind to a temporary of type 'int'

从消息中可以清晰看到,函数返回类型是int,它的引用属性被干掉了。

版本三 用 C++14 实现(v1.0)

如何才能同时做到:

  • 让编译器自动推导函数返回类型。
  • 让编译器使用decltype()的推导规则(而不是auto的推导规则)。
    答案好像也很简单:鱼和熊掌一锅炖了……
template<typename Container, typename Index>
decltype(auto) // C++14, 现在函数的返回类型真的跟c[i]一样了!
authAndAccess( Container& c, Index i)
{
    authenticateUser();
    return c[i];
}

作者终于端出来了一盘美味!

decltype(auto)

它不光可以用在函数返回类型上,也可以用在变量上。

Widget w;
const Widget& cw = w;
auto myWidget1 = cw;  // 使用 auto 类型推导规则,
                      // 结果myWidget1的类型是Widget
decltype(auto) myWidget2 = cw; // 亦是类型推导,但应用的是decltype()
                     // 结果myWidget2的类型就是 Widget&

怎么来理解 decltype(auto) 呢?总而言之就是告诉编译器这里需要做自动类型推导,并且推导规则就用 decltype()的规则。

版本四:C++11/14 v2.0 面对右值引用的代码缺陷

缺陷与如何使用这个 authAndAccess() 时的实参有关系。

第(1)种情况:在这个函数调用表达式之前,第1个入参即容器已经存在,并且在表达式执行完毕后还继续存在。例如:

std::deque<int> d;
// ... 一些处理
auto s = authAndAccess(d, 5);
s = 10;

之前的版本处理的很好。

第(2)种情况:容器入参是在函数调用表达式执行时产生的,表达式执行完毕后对象就析构了。例如:

std::deque<std::string> makeStringDeque();  // 工厂函数

auto s = authAndAccess(makeStringDeque(), 5);
s += "ok"; 

第2行的函数调用表达式种,第1个参数是由工厂函数生成的一个临时对象;表达式执行完毕后,这个临时对象就析构了。像这种对临时对象的引用,一般叫右值引用。

第3行代码现在还正确吗?注意,s 现在成了一个悬空引用。为什么?当右值对象析构时,会一并析构其元素对象(即 std::string)。

解决方法有两种:(1)利用函数重载,一个版本用于左值引用,一个版本用于右值引用。(2)使用“万能引用”并配合“完美转发”。

C++14版本

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

C++11版本

template<typename Container, typename Index>
auto authAndAccess( Container&& c, Index i )
-> decltype(std::forward<Container>(c)[i])
{
    authenticateUser();
    return std::forward<Container>(c)[i];
}

关于万能引用和完美转发,另有学习专章。

一种神奇的坑

一个看似无关紧要的返回值写法上的小改动,就会影响道函数的类型推导结果:

decltype(auto) f1()
{
  int x = 0;
  // ...
  return x; // decltype(x)是int,因此f1返回的是int
}

decltype(auto) f2()
{
  int x = 0;
  // ...
  return (x); // decltype((x))是int&,所以f2返回的是int&
}

对于f2,其返回的是对局部变量的引用,这是一种未定义行为。

教训:使用 decltype(auto) 要极其小心翼翼。

查看类型推导结果的方法

(一)利用 IDE 编辑器和编译器,实现静态的类型查看。

(二)利用 std::type_info::name 结果有时不太准确。。

(三)利用 Boost.TypeIndex 组件。

#include <boost/type_index.hpp>

template<typename T>
void f( const T& param )
{
    using std::cout;
    using boost::typeindex::type_id_with_cvr;
    
    // 显示 T 的类型
    cout << "T =    "
         << type_id_with_cvr<T>().pretty_name()
         << '\n';
         
   cout << "param = "
        << type_id_with_cvr<decltype(param)>().pretty_name()
        << '\n';
}

再看看用户端代码:

std::vector<Widget> createVec();  // 工厂函数

const auto vw = createVec();  // 使用工厂函数返回值初始化 vw

if ( !vw.empty() ) {
    f(&vw[0]);
    // ...
}

输出是:

T =     Widget const*
param = Widget const* const&

本文内容来自多种资料,包括但不限于《Effective Modern C++》,《C++ Primer》(第5版),《C++程序设计语言》(第四版)等。

相关文章

  • decltype

    decltype是用于进行类型推导的。typeid用于运行时动态获取类型信息。返回相应变量对应的type_info...

  • decltype()

    摘要 本文是关于 decltype() 的学习笔记。各种版权都属于原作者。如果存在侵权行为,请通知本人删除。 de...

  • C++11中auto和decltype

    C++11中auto和decltype auto和decltype都是C++11中引进来用于自动推断类型的关键字,...

  • auto && decltype

    auto:C++11标准引入的类型说明符,编译器通过初始值来推算变量的类型。因此,auto定义的变量必须有初始值a...

  • auto & decltype

    使用auto和decltype来实现实参推断时,遇到下面case: 执行结果为: 如果把sum中的注释的cout打...

  • C++11之decltype

    decltype是一种新的操作符,用于获取表达式的数据类型。使用方法和sizeof操作符类似。 decltype(...

  • decltype关键字

    C++11新标准学习:decltype关键字 1. decltype的意义 有时我们希望从表达式的类型推断出要定义...

  • Item 3 Understand decltype

    引子 decltype可以用来获取表达式类型,且通常就是我们想要的类型。 正文 首先,从最基本的情况开始,decl...

  • C++11拾穗

    C++11新关键字 alignas:指定对齐大小 alignof:获取对齐大小 decltype auto(重新定...

  • 模板函数返回类型的演进

    提纲 c++03: trick 方法 c++11: auto->decltype 组合 c++14: 自动推导模板...

网友评论

    本文标题:decltype()

    本文链接:https://www.haomeiwen.com/subject/skaqdrtx.html