占位类型说明符 (C++11 起)

来自cppreference.com
< cpp‎ | language

对于变量,指定要从它的初始化器自动推导出它的类型。

对于函数,指定要从它的 return 语句推导出它的返回类型。

(C++14 起)

对于非类型模板形参,指定要从实参推导出它的类型。

(C++17 起)

语法

类型约束(可选) auto (1) (C++11 起)
类型约束(可选) decltype(auto) (2) (C++14 起)
类型约束 - (C++20 起) 概念名,可以有限定或后随被 <> 包围的模板实参列表
1)模板实参推导的规则推导类型。
2) 类型是 decltype(expr),其中 expr 是初始化器。

占位符 auto 可伴随如 const& 这样的修饰符,它们参与类型推导。占位符 decltype(auto) 必须是被声明类型的唯一组分。 (C++14 起)

解释

占位类型说明符可以在下列语境出现:

  • 变量的类型说明符中:auto x = expr;。从初始化器推导类型。
    如果占位类型说明符是 auto 类型约束 auto (C++20 起),那么采用从函数调用进行模板实参推导的规则,从初始化器推导变量的类型(细节见其他语境)。
    例如,给定 const auto& i = expr;,那么 i 的类型恰是某个虚构模板 template<class U> void f(const U& u) 中参数 u 的类型(假如函数调用 f(expr) 通过编译)。因此,取决于初始化器,auto&& 可被推导成左值引用或右值引用类型,这被用于基于范围的 for 循环。

    如果占位类型说明符是 decltype(auto) 类型约束 decltype(auto) (C++20 起),那么推导出的类型是 decltype(expr),其中 expr 是初始化器。

    (C++14 起)

    如果用占位类型说明符声明多个变量,那么推导出的类型必须互相匹配。例如,声明 auto i = 0, d = 0.0; 非良构,而声明 auto i = 0, *p = &i; 良构并将 auto 推导为 int

  • new 表达式中的类型标识。从初始化器推导类型。对于 new T init(其中 T 含占位符类型,而 init 是带括号的初始化器或带花括号的初始化器列表),如同在虚设的声明 T x init; 中对变量 x 一般推导 T 的类型。
  • (C++14 起) 函数或 lambda 表达式的返回类型中:auto& f();。从其舍弃 (C++17 起) return 语句的操作数推导返回类型。
    返回类型推导
  • (C++17 起) 非类型模板形参的形参声明中:template<auto I> struct A;。从对应的实参推导它的类型。

auto 说明符也可以在显式类型转换的简单类型说明符中出现:auto(expr)auto{expr} 。从表达式推导它的类型。

(C++23 起)

此外,auto 类型约束 auto (C++20 起) 还可以在以下地方出现:

(C++14 起)

类型约束

如果 类型约束 存在,令 T 作为该占位符的被推导类型,那么 T 必须满足 类型约束 的立即声明的约束。即,

  • 如果 类型约束Concept<A1, ..., An>,那么约束表达式 Concept<T, A1, ..., An> 必须合法并返回 true
  • 否则(类型约束 是无实参列表的 Concept),约束表达式 Concept<T> 必须合法并返回 true
(C++20 起)

注解

C++11 之前,auto 具有存储期说明符的语义。

不允许在一个声明中混合 auto 的变量和函数,如 auto f() -> int, i = 0;

auto 说明符也可以用于后随尾随返回类型的函数声明符,此时返回类型是它的尾随返回类型(它也可以是占位符类型)。

auto (*p)() -> int; // 声明指向【返回 int 的函数】的指针
auto (*q)() -> auto = p; // 声明 q 为指向【返回 T 的函数】的指针
                         // 其中 T 从 p 的类型推导

auto 说明符也可以用于结构化绑定声明。

(C++17 起)

auto 关键词也可以用于嵌套名说明符。形如 auto:: 的嵌套名说明符是一个占位符,它会遵循受约束类型占位符的推导规则被替换为某个类或枚举类型。

(概念 TS)
功能特性测试宏 标准 备注
__cpp_decltype_auto 201304L (C++14) decltype(auto)

示例

#include <iostream>
#include <utility>
 
template<class T, class U>
auto add(T t, U u) { return t + u; } // 返回类型是 operator+(T, U) 的类型
 
// 在它调用的函数返回引用的情况下
// 函数调用的完美转发必须用 decltype(auto)
template<class F, class... Args>
decltype(auto) PerfectForward(F fun, Args&&... args) 
{ 
    return fun(std::forward<Args>(args)...); 
}
 
template<auto n> // C++17 auto 形参声明
auto f() -> std::pair<decltype(n), decltype(n)> // auto 不能从花括号初始化器列表推导
{
    return {n, n};
}
 
int main()
{
    auto a = 1 + 2;          // a 的类型是 int
    auto b = add(1, 1.2);    // b 的类型是 double
    static_assert(std::is_same_v<decltype(a), int>);
    static_assert(std::is_same_v<decltype(b), double>);
 
    auto c0 = a;             // c0 的类型是 int,保有 a 的副本
    decltype(auto) c1 = a;   // c1 的类型是 int,保有 a 的副本
    decltype(auto) c2 = (a); // c2 的类型是 int&,它是 a 的别名
    std::cout << "通过 c2 修改前,a = " << a << '\n';
    ++c2;
    std::cout << "通过 c2 修改后,a = " << a << '\n';
 
    auto [v, w] = f<0>(); // 结构化绑定声明
 
    auto d = {1, 2}; // OK:d 的类型是 std::initializer_list<int>
    auto n = {5};    // OK:n 的类型是 std::initializer_list<int>
//  auto e{1, 2};    // C++17 起错误,之前是 std::initializer_list<int>
    auto m{5};       // OK:DR N3922 起 m 的类型是 int,之前是 initializer_list<int>
//  decltype(auto) z = { 1, 2 } // 错误:{1, 2} 不是表达式
 
    // auto 常用于无名类型,例如 lambda 表达式的类型
    auto lambda = [](int x) { return x + 3; };
 
//  auto int x; // 在 C++98 合法,C++11 起错误
//  auto x;     // 在 C 合法,在 C++ 错误
 
    [](...){}(c0, c1, v, w, d, n, m, lambda); // 阻止“变量未使用”警告
}

可能的输出:

通过 c2 修改前,a = 3
通过 c2 修改后,a = 4

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 1265 C++11 auto 说明符可以用来在一条声明语句中同时声明有尾随返回类型的函数和定义变量 禁止这种用法
CWG 1346 C++11 不能将被括号包围的表达式列表赋给 auto 变量 允许这种用法
CWG 1347 C++11 一个带 auto 说明符的声明可以同时声明两个类型
分别是 Tstd::initializer_list<T> 的变量
禁止这种用法
CWG 1852 C++14 decltype(auto) 中的 auto 说明符也是占位符 此时它不是占位符