占位符类型说明
对变量而言,指定要声明的变量的类型将从其初始值中自动推导出来。
对于函数而言,指定的返回类型将从其返回语句中推导出来(从C++14开始)
对于非模版类型的参数而言,指定的类型将会从参数中推导出来(从C++17开始)
语法
受约束的类型(可选)auto(从C++11开始)
受约束的类型(可选)decltype(auto)(从C++14开始)
受约束的类型-(从C++20开始),是一个概念名称,可选资格的、可选地跟在一组模版参数列表<>后面
1)使用模版参数推导规则出类型;
2)类型是decltype(expr),其中expr是初始值;
占位符可能伴随着修饰符,例如const,或者,占位符将参与类型推导。&占位符decltype(auto)必须是声明类型的唯一组成部分。(从C++14开始)
解释
占位符类型说明符可能出现在以下背景中:
(1)在一个变量的类型说明符序列中:auto x=expr;作为一个类型说明符。该类型是从初始值中推导出来的。如果占位符类型说明符是或类型约束auto(从C++20开始),则使用函数调用中模板参数推导的规则从初始值中推导变量类型(有关详细信息,请参见模板参数推导-其他上下文)。例如,给定 const auto& i = expr;,那么 i 的类型恰是某个虚构模板 template<class U> void f(const U& u) 中参数 u 的类型(假如函数调用 f(expr) 通过编译)。因此,取决于初始化器,auto&& 可被推导成左值引用或右值引用类型,这被用于基于范围的 for 循环(for循环也适用)。
这个例子的意思是:假如有一个可以编译通过的模版template<class U> void f(const U& u),然后f被调用,实参是expr,即f(expr);再将expr赋给i,即const auto& i = expr;根据expr是左值还右值,auto&&可以被推导为左值引用或右值引用(如果expr是左值,auto&就是auto&,如果expr是右值,auto&则会被推导成auto&&)
如果占位类型说明符是 decltype(auto) 或者是受类型约束 decltype(auto) (从C++20 开始),那么推导出的类型是 decltype(expr),其中 expr 是初始值。(从C++14开始)。
如果占位符类型说明符用于声明多个变量,则推导的类型必须匹配。例如,声明auto i=0,d=0.0;格式不正确,而声明auto i=0,*p=&i;格式匹配,自动推导为int。
(2)new 表达式中的类型标识。从初始推导类型。对于 new T init(其中 T 含占位符类型,而 init 是带括号的初始化器或带花括号的初始化器列表),如同在虚设的声明 T x init; 中对变量 x 一般推导 T 的类型。
解释一下:假如T是一个Base类型,则new T init等价new Base (a)或者new Base(a, b);(a、b的类型都是Base),还等同于Base x(a)或者Base x(a,b),利用x推导a、b的类型。
(3)(从C++14 开始) 函数或 lambda 表达式的返回类型中:auto& f();。从其未舍弃的 (从C++17 开始) return 语句的操作数推导返回类型。
见返回类型推导:Function declaration - cppreference.com
(4)(从C++17开始) 非类型模板形参的形参声明中:template<auto I> struct A;。从对应的实参推导它的类型。
auto 说明符也可以在显式类型转换的简单类型说明符中出现:auto(expr) 与 auto{expr} 。从表达式推导它的类型。
(从C++23 开始)此外,auto 与 类型约束 auto (从C++20开始) 还可以在以下地方出现:
- lambda 表达式的形参声明:[](auto&&){}。这种 lambda 表达式是泛型 lambda。(C++20 起)
- 函数形参声明:void f(auto);。这种函数声明引入简写函数模板。(C++14 起)
类型约束
如果存在类型约束,则设T为占位符推导的类型,类型约束将引入如下约束表达式:
- 如果类型约束为Concept<A1,…,An>,则约束表达式为Concept<T,A1,……,An>;
- 否则(类型约束是没有参数列表的Concept),约束表达式是Concept<T>。
如果约束表达式无效或返回false,则推理失败。
注解
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:: 的嵌套名说明符是一个占位符,它会遵循受约束类型占位符的推导规则被替换为某个类或枚举类型。
示例
#include <iostream>
#include <utility>
// 返回类型是 operator+(T, U) 的类型
template<class T, class U>
auto add(T t, U u) { return 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); // 阻止“变量未使用”警告
}
这个代码可能要在c++19环境下跑,我在C++17下没有跑成功
总结
1 auto是根据初始值来确定变量类型的,再进一步,根据初始值的左值/右值之分,适当的添加占位符;
2 如果占位符类型说明符用于声明多个变量,则推导的类型必须匹配;
具体用法,参考给出的这段代码
#include <iostream>
#include <utility>
// 返回类型是 operator+(T, U) 的类型
// 理解
template<class T, class U>
auto add(T t, U u) { return 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); // 阻止“变量未使用”警告
}
学习:Placeholder type specifiers (since C++11) - cppreference.com