【问题标题】:How does auto deduce type?自动推断类型如何?
【发布时间】:2014-01-12 01:03:15
【问题描述】:

我有一些auto的使用案例:

auto s = expr;          //s is always lvalue
auto & s = expr;        //s is always lvalue reference? What if expr is rvalue?
auto && s = expr;       //s is perfectly forwarded

他们是真的吗?如果不是,为什么?

【问题讨论】:

  • 变量声明中auto推导的类型是通过模板实参推导规则定义的,参见[dcl.spec.auto]/6;有一个例外:如果初始化程序是一个花括号初始化列表,则推导的类型是 std::initializer_list
  • 顺便说一句,s 作为表达式 总是 一个左值,因为 s 是一个标识符(并且没有枚举数)。左值/右值是表达式的类别。
  • P.S.这Scott Meyer's Universal Reference article 覆盖;)
  • @dyp 他提到Widget&& var1 = somewidget; Widget& var2 = var1; 将使var2 成为Widget& 的一种类型。但是var2 引用的是哪个对象? var1 不是右值引用吗?左值引用如何绑定到右值?
  • @dyp 我想我现在明白了。 var1 被认为是左值...

标签: c++ c++11


【解决方案1】:

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,本例中PT)和对应实参(称为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的扣除会和没有&amp;完全一样,但是在扣除auto类型后,&amp;会被添加到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 &amp;&amp;,而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!

【讨论】:

  • 我一直混淆的最后一点:auto&amp; = makeSomething();,与sometype makeSomething();:如果sometype 是非常量非引用,这将失败,因为auto 被推导出为@ 987654377@ 和 sometype&amp; = makeSomething(); 尝试将 prvalue 绑定到左值 ref。如果sometype const (no ref),但是non-class non-array,constness 将根据[expr]/6 被丢弃,同样的情况。如果 sometype 是 const 类类型(using sometype = myclass const;),那么这将起作用,因为 auto 被推导出为 sometype const(并且 sometype const&amp; s = makeSomething(); 是合法的)。
  • using 和 typedef 不会定义新类型,您的示例将不合法,因为 auto 将被推断为 myclass,而不是 sometype。
  • 糟糕,打错了 :) 应该是:如果 sometype 是 const 类类型,例如using sometype = myclass const;,那么这将起作用,因为auto 被推导出为myclass const(并且myclass const&amp; s = makeSomething(); 是合法的)。 (顺便说一句:sometype const 只是多余的,相当于sometype
【解决方案2】:

由于没有人给出答案而且我现在有点理解(感谢@dyp),所以我会自己发布答案。如有错误请指出:

auto s = expr;

如果expr 是左值、纯右值或任何引用,则s 始终是左值并进行复制。

auto& s = expr;

如果expr 是左值、左值引用或右值引用,则s 是左值引用。 如果expr 是prvalue,则为错误且不合法。

auto&& s = expr;

s 被完美转发(右值会变成右值引用并崩溃)。

【讨论】:

    猜你喜欢
    • 2022-11-24
    • 1970-01-01
    • 2023-02-05
    • 1970-01-01
    • 2016-04-14
    • 2015-08-07
    • 2020-10-12
    • 2013-04-14
    • 1970-01-01
    相关资源
    最近更新 更多