占位符类型指定符 (C++11 起)

来自cppreference.com
< cpp‎ | language

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

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

(C++14 起)

对于非类型模板形参,指定要从参数推导出其类型。

(C++17 起)

目录

[编辑] 语法

auto (1) (C++11 起)
decltype(auto) (2) (C++14 起)
type-constraint auto (3) (C++20 起)
type-constraint decltype(auto) (4) (C++20 起)
type-constraint - 概念名,可以有限定,可以有后随的环绕于 <> 的模板实参列表
1,3)模板实参推导的规则推导类型。
2,4) 类型为 decltype(e) ,其中 e 是初始化器。

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

[编辑] 解释

占位符类型可出现于下列语境:

  1. 变量的类型指定符中: auto x = expr; 。从初始化器推导类型。
    若占位符类型指定符为 auto type-constraint auto (C++20 起),则 从初始化器,用来自函数调用的模板实参推导规则,推导变量的类型(细节见其他语境)。
    例如,给定 const auto& i = expr; ,则 i 的类型恰是虚构模板 template<class U> void f(const U& u) 中参数 u 的类型,假如函数调用 f(expr) 通过编译。从而根据初始化器, auto&& 可被推导成左值引用或右值引用类型,这被用于基于范围的 for 循环。

    若占位符类型指定符为 decltype(auto) type-constraint decltype(auto) (C++20 起),则推出的类型为 decltype(e) ,其中 e 是初始化器。

    (C++14 起)

    若用占位符类型指定符声明多个变量,则推出的类型必须匹配。例如,声明 auto i = 0, d = 0.0; 为病式,而声明 auto i = 0, *p = &i; 为良式并推导 autoint

  2. new 表达式中的 type-id 。从初始化器推导类型。对于 new T init (其中 T 含占位符类型,而 init 是有括号初始化器或花括号环绕的初始化器列表),如同在虚设声明 T x init; 中对变量 x 一般推导 T 的类型。
  3. (C++14 起) 函数或 lambda 表达式的返回类型中: auto& f(); 。从其被舍弃 (C++17 起) return 语句的运算数推导返回类型。
    返回类型推导
  4. (C++17 起) 非类型模板形参的形参声明中: template<auto I> struct A; 。从对应实参推导其类型。 </ul>

    另外, auto type-constraint auto (C++20 起) 能出现于:

    (C++14 起)

    type-constraint 存在,令 T 为该占位符的被推导类型,则 T 必须满足 type-constraint 的立即声明制约。即,

    • type-constraintConcept<A1, ..., An> ,则制约表达式 Concept<T, A1, ..., An> 必须合法并返回 true
    • 否则( type-constraint 为无实参列表的 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 的函数的指针
                             // 其中从 p 的类型推导 T

    auto 指定符亦可用于结构化绑定声明。

    (C++17 起)

    auto 关键字亦可用于 nested-name-specifier 。形如 auto:: 的 nested-name-specifier 会被遵循有制约类型占位符推导规则的类或枚举类型替换。

    (概念 TS)

    [编辑] 示例

    #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 << "a, before modification through c2 = " << a << '\n';
        ++c2;
        std::cout << "a, after modification through c2 = " << 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 : C++17 起 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++ 错误
    }

    可能的输出:

    a, before modification through c2 = 3
    a, after modification through c2 = 4