dyp 是正确的,我想详细说明一下。
首先,结论是来自dyp:
定义了变量声明中auto推导的类型
通过模板参数推导规则,见[dcl.spec.auto]/6;
有一个例外:如果初始化程序是一个花括号初始化列表,则
推导出的类型是std::initializer_list。
我会解释的。
首先,
auto s = expr;
这与从expr 推导出T 相同,
template<class T>
void f(T s);
f(expr);
模板参数推导的规则相当复杂,因为你只关心左值和右值的东西,让我们专注于此。
模板实参推导是通过比较模板形参类型(称为P,本例中P为T)和对应实参(称为A,本例中为expr)。
从 14.8.2.1 开始,
如果 P 不是引用类型:
——如果 A 是数组类型,则数组到指针标准转换(4.2)产生的指针类型是
用于代替A进行类型扣除;否则,
——如果 A 是函数类型,则函数到指针标准转换(4.3)产生的指针类型
用于代替A进行类型扣除;否则,
— 如果 A 是 cv 限定类型,则忽略 A 类型的顶级 cv 限定符进行类型推导。
因此,如果expr 是数组或函数,它将被视为指针,如果 expr 具有 cv-qualification(const 等),它们将被忽略。
如果 P 是 cv 限定类型,则 P 类型的顶级 cv 限定符将被忽略以进行类型推导。
这实际上是说:
const auto s = expr;
s 是一个const 变量,但对于auto 的类型推断,const 将被删除。
因此,根据上述规则,auto 将被推导出为expr 的类型(经过上述一些类型转换)。
注意,当一个表达式引用T时,会先调整为T再进行分析。
所以无论expr 是什么——右值、左值或左值/右值引用类型——auto 的类型将始终是expr 的类型,没有引用。
auto s1 = 1; //int
int &x = s1;
auto s2 = x; //int
int &&y = 2;
auto s3 = y; //int
其次,我们来看看
auto &s = expr;
这将与
相同
template<class T>
void f(T &s);
f(expr);
标准中的额外规则如下:
如果P是引用类型,则使用P所引用的类型进行类型推导。
所以auto的扣除会和没有&完全一样,但是在扣除auto类型后,&会被添加到auto的末尾。
//auto &s1 = 1; //auto is deducted to int, int &s1 = 1, error!
const auto &s1 = 1; //auto is deducted to int, const int &s1 = 1; ok!
const int &x = s1;
auto &s2 = x; //auto is int, int &s2 = x; ok!
int &&y = 2;
auto &s3 = y; //auto is int, int &s3 = y; ok!
请注意,最后一个 y 是一个左值。 C++的规则是:命名右值引用是一个左值。
最后:
auto &&s = expr;
这无疑是一样的
template<class T>
void f(T &&s);
f(expr);
标准中的一个附加规则:
如果P 是对 cv 非限定模板参数的右值引用,并且
参数是一个左值,类型“对 A 的左值引用”用于
A 用于类型推导的地方。
这实际上是说,如果expr 是右值,则规则将与第二种情况(左值情况)相同,但如果expr 是左值,则A 的类型将是左值引用到A。
前面解释的注意,A 永远不是引用,因为表达式的类型永远不是引用。但对于这种特殊情况(auto &&,而A 是左值),必须使用对A 的引用,而不管expr 本身是否是引用类型。
例子:
auto &&s1 = 1; //auto is deducted to int, int &&s1 = 1, ok!
int x = 1;
auto &&s2 = x; //x is lvalue, so A is int &, auto is deducted to int &, int & &&s2 = x; ok!
int &&y = 2;
auto &&s3 = y; //y is lvalue, auto is int &, int & &&s3 = y; ok!