Loading... 一则简短的小知识: `decltype(auto)`是干嘛的? `auto`作为占位类型说明符自C++11就出现了,它本质上是个**语法糖**,能够让我们把`type var = expr;`简写成`auto var = expr;`,主要带来了两方面的好处,一是减少了代码的复杂程度,特别是`type = std::some_container<some_type>::some_iterator`这种情况,降低了编码的负担;另一方面提高了代码的统一程度。 <div class="tip inlineBlock info"> 那么既然`auto`已经足以推导类型了,我们又为什么需要`decltype(auto)`的存在? </div> 看起来`auto`和`decltype`都起到了推导的功效,为什么还要多此一举呢? 考虑一个最简单的例子: ```cpp template<typename T> auto f(T&& arg) { return std::forward<T>(arg); } ``` 我想要在模板代码中,将参数按照原本的值类别引用转发出来,然而这是做不到的,`auto`只会得到非引用的类型: ```cpp #include <bits/stdc++.h> template<typename T> T check(); // 一种奇特的编译期值类别检测技巧 int a = 1; template<typename T> auto f(T&& arg) { return std::forward<T>(arg); } int main() { check<decltype(f(a))>(); // undefined reference to `int c<int>()' check<decltype(f(1))>(); // undefined reference to `int c<int>()' return 0; } ``` 原因是`auto` 自身**不包含值类别和cv限定**,它的推导规则是依据**模板实参推导**来的: ![image.png](https://zclll.com/usr/uploads/2022/12/2398412434.png) 这样,当我们在写一些模板代码的时候,如果需要返回转发引用,似乎只能抛弃万能引用而分别实现了。或许有人想到了使用`auto&&`,但当我们需要处理在返回值中转发引用的时候,这种方法也失效了,因为它还可能包含非引用类型: ```cpp template<typename T> /* ref or non-ref */ g(T); template<typename T> auto&& f(T&& arg) { return std::forward<T>(arg); // 解决了 return g(std::forward<T>(arg)); // 不奏效 } ``` 也就是说,**我们需要在返回值上引入一个真正的完美转发**。为了解决这一问题,C++在14就引入了`decltype(auto)`这一特定搭配写法,用于**推导出带引用类别的值类型**。其推导结果将等价于`decltype(expr)`,其中`expr`为对应位置的初始化器(在这里是`return`的操作数)。 于是上面的代码就可以实现为: ```cpp #include <bits/stdc++.h> template<typename T> T check(); // 一种奇特的编译期值类别检测技巧 int a = 1; template<typename T> decltype(auto) f(T&& arg) { return std::forward<T>(arg); } int main() { check<decltype(f(a))>(); // undefined reference to `int& c<int&>()' check<decltype(f(1))>(); // undefined reference to `int&& c<int&&>()' return 0; } ``` <div class="tip inlineBlock share"> 有一点需要注意的是,对于一个名字进行`decltype`,为了能够正确区分引用和非引用,嵌套括号的`decltype((a))`将产生左值引用,而`decltype(a)`不会。 </div> 这种手法主要用处在于**完美转发**上: ```cpp template<class Fun, class... Args> decltype(auto) Example(Fun fun, Args&&... args) { return fun(std::forward<Args>(args)...); } ``` 通过`decltype(auto)`,如果`fun`返回引用,我们实现了正确地将引用转发出去,非引用的结果也是正确的(返回值的完美转发)。这种情形下,它是唯一的方法。 --- 其实,在理解上,这仍然可以看做是一个语法糖,`auto`就是起到了占位符的功能。例如上面的代码,其实就是通过`decltype(fun(std::forward<Args>(args)...))`,来得到返回值类型。 简单总结的话就是,在返回值中,`auto`只是类型,而`decltype(auto)`像完美转发一样带可能的引用。 © 允许规范转载 打赏 赞赏作者 赞 如果觉得我的文章对你有用,请随意赞赏