条款3:理解decltype
返回表达式的精确类型,保留引用和const属性,变量名返回声明类型,复杂表达式左值推导为引用。
条款3:理解decltype
条款3:理解 decltype
decltype(expr)
会返回表达式expr
的类型(精确地,不做修改)。- 它不像
auto
使用模板类型推导规则,不会剥去引用、cv 限定等信息。
decltype 的常见行为
1
2
3
4
5
6
7
8
9
10
11
const int i = 0;
decltype(i) // => const int
bool f(const Widget& w);
decltype(w) // => const Widget&
decltype(f) // => bool(const Widget&)
decltype(f(w)) // => bool
vector<int> v;
decltype(v) // => vector<int>
decltype(v[0]) // => int&(如果是 vector<int>)
decltype 的用途:函数模板返回值推导
目标:写一个模板函数 authAndAccess
,能返回容器 c[i]
的值,并带有认证操作。
C++11 尾置返回类型
1
2
3
4
5
6
7
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
-> decltype(c[i]) // 使用 decltype 精确推导返回类型
{
authenticateUser();
return c[i];
}
C++14 简化写法(错误版本)
1
2
3
4
5
6
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i]; // 使用 auto 推导类型,但会丢掉引用属性!
}
auto
使用 模板类型推导规则。- 会导致
decltype(c[i])
是T&
,而auto
推导为T
。 - 结果是返回值变为右值(非引用),无法进行赋值等操作。
C++14 正确写法:decltype(auto)
1
2
3
4
5
6
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i]; // 推导为真正的 decltype(c[i])
}
进一步支持右值容器:使用通用引用 + std::forward
1
2
3
4
5
6
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i]; // 完美转发
}
Container&&
是通用引用:既支持左值也支持右值。std::forward
保持值类别(左值/右值)不变。
支持以下场景:
1
2
std::deque<std::string> makeDeque();
auto s = authAndAccess(makeDeque(), 2); // 合法:返回副本而不是悬垂引用
悬垂引用指的是:一个引用(或指针)指向了一块已经被释放或销毁的内存区域。一旦再去访问它,行为是未定义的(Undefined Behavior, UB)。
makeDeque()
是一个函数,返回一个临时的 std::deque<std::string>
:这是一个右值容器,即返回的是一个临时对象。
把这个右值传给 authAndAccess
:
1
2
3
4
5
6
7
template<typename Container, typename Index>
decltype(auto)
authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i];
}
Container&&
是 通用引用(可绑定左值或右值)。std::forward<Container>(c)[i]
会在右值情况下调用std::move(c)[i]
,把c
显式转换为右值。operator[]
会返回一个 值对象或引用,根据容器的定义。
对于 std::deque<std::string>
,operator[]
返回 std::string&
。
重点问题:authAndAccess
返回了 c[i]
的结果,但 c
是一个临时对象。
如果返回的是 std::string&
,那么会得到一个 悬垂引用(Dangling Reference)——因为 makeDeque()
生成的容器在 authAndAccess
返回后就被销毁了!
解决方案:使用 decltype(auto)
+ 通用引用的模板组合,它能根据调用上下文决定是否返回引用还是值。
在这行代码中:
1
auto s = authAndAccess(makeDeque(), 2);
s
是用auto
定义的变量。- 因为
s
是值类型(不是引用),所以authAndAccess
最终会返回一个std::string
(值拷贝),而不是std::string&
。 - 拷贝是安全的,即便容器是临时的,也不会产生悬垂引用。
换句话说:
- 函数
authAndAccess
返回的是const T&
(引用) - 但外面
auto s
不绑定引用,是直接创建了副本 - 因为
s
是局部变量,拷贝了临时容器元素的值,所以是安全的
decltype(auto) 与表达式细节
在 C++14 中,decltype(auto)
会根据返回语句使用 decltype 的规则 来推导类型。
1
2
3
4
5
6
7
8
9
decltype(auto) f1() {
int x = 0;
return x; // decltype(x) 是 int
}
decltype(auto) f2() {
int x = 0;
return (x); // decltype((x)) 是 int&,⚠️ 悬垂引用!
}
注意:在 return (x);
中,加了括号后变成左值表达式,decltype
推导结果变为引用类型。
decltype 推导规则总结
表达式或名字 | 推导类型 |
---|---|
int x = 0; decltype(x) | int |
decltype((x)) | int& (左值表达式) |
const Widget& cw = ...; decltype(cw) | const Widget& |
auto w = cw; | Widget (auto 去引用) |
decltype(auto) w = cw; | const Widget& (保持引用) |
decltype(expr)
给出表达式的精确类型(包括引用和const
)。- 对于非变量名的左值表达式,
decltype
会推导为 T&。 decltype(auto)
使用decltype
规则进行自动推导,和auto
不一样。(x)
和x
在decltype
中是不一样的,前者返回引用类型。- 返回值使用
decltype(auto)
时需避免返回局部变量的引用。
本文由作者按照 CC BY 4.0 进行授权