C++ / a working model

47 / 80   ·   C++14   ·   约 9 分钟

auto 与 decltype(auto) 的推导边界

先记住这句话

auto 让编译器推导静态类型,并不引入动态类型。是否保留引用与 const 取决于声明形式;decltype(auto) 则直接采用 decltype 规则,括号也能改变结果,尤其影响函数返回值与生命周期。

本篇内容
  1. 先决定值还是借用
  2. decltype 的括号具有语义
  3. 返回推导不能替你检查生命期
  4. 运行示例
  5. 动手练习

先决定值还是借用

C++11 的 auto 借助类似模板实参推导的规则确定类型。auto x = source 通常得到一个新值:忽略源对象的顶层 const 与引用;auto& x = source 则声明引用,保留被引用对象的 const。const auto& 适合只读借用,但仍需要检查源对象的生命周期。

这不是“自动选择最省拷贝的类型”。源表达式返回大对象引用时,裸 auto 仍可能复制;源类型本身是代理对象时,裸 auto 也可能复制代理而继续引用底层存储。读声明时应先判断所有权意图,再看推导出的具体类型。

decltype 的括号具有语义

对未加括号的变量名,decltype(name) 返回该实体的声明类型;对一般表达式则根据值类别决定:左值得到 T&,将亡值得到 T&&,纯右值得到 T。所以普通整数 x 的 decltype(x) 是 int,而 decltype((x)) 是 int&。

C++14 的 decltype(auto) 将初始化表达式或返回表达式直接交给这套规则。它不能像 auto 那样再随意拼接 & 修饰。示例中的两个访问函数只有一处括号不同,却分别返回整数副本与原整数的引用,这种差异应在接口设计中有明确理由。

返回推导不能替你检查生命期

包装已有函数、必须准确保留其引用返回值时,decltype(auto) 很有用;只想返回独立结果时,auto 或明确的值类型通常更易审查。返回局部变量的括号表达式可能推出局部引用,函数退出后立即悬空,编译通过不代表安全。

示例让引用只绑定 main 中仍存活的 n,并用类型断言说明每次推导。另一个容易误判的地方是花括号:auto a = {1, 2} 推导 initializer_list,而不是 vector;现代规则下 auto b{1} 是 int。选择括号形式前,应先确定自己要的是单个值还是列表。

容易答错的地方

  • decltype(auto) 的 return (local) 会保留局部变量引用;不能通过给 return 随手加括号来“统一风格”。
  • 裸 auto 不保证脱离源存储,例如 vector<bool> 的元素访问可能返回代理;要独立 bool 值就显式转换为 bool。

运行一个例子

最低标准 C++14 · 完整程序 · 下载 .cpp

#include <cassert>
#include <iostream>
#include <type_traits>

struct Box { int value; };
decltype(auto) copy_value(Box& b) { return b.value; }
decltype(auto) borrow_value(Box& b) { return (b.value); }

int main() {
    const int source = 3;
    auto copy = source;
    auto& ref = source;
    static_assert(std::is_same<decltype(copy), int>::value, "value");
    static_assert(std::is_same<decltype(ref), const int&>::value, "borrow");
    Box b{4};
    static_assert(std::is_same<decltype(copy_value(b)), int>::value, "copy return");
    static_assert(std::is_same<decltype(borrow_value(b)), int&>::value, "reference return");
    auto detached = copy_value(b);
    borrow_value(b) = 9;
    assert(detached == 4 && b.value == 9);
    std::cout << detached << ' ' << b.value << '\n';
}

在本地编译

g++ -std=c++14 -Wall -Wextra -Wpedantic -pthread modern-auto.cpp -o example && ./example

预期结果

4 9

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

若把 auto detached = copy_value(b) 改成 decltype(auto) detached = borrow_value(b),最后 detached 是多少?如何保留数值副本?

查看参考答案

detached 成为 int&,给 borrow_value(b) 赋值后它也观察到 9。写 auto detached = borrow_value(b)int detached = borrow_value(b) 可复制当时的数值 4。区别在调用者声明,不在函数是否具有“自动推导”字样。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录