为什么我需要显式写 'auto' 关键字?

Why do I need to explicitly write the 'auto' keyword?

我正在从 C++98 转向 C++11,并且已经熟悉 auto 关键字。我想知道如果编译器能够自动推断类型,为什么我们需要显式声明 auto 。我知道 C++ 是一种强类型语言,这是一条规则,但如果不显式声明变量就不可能实现相同的结果 auto?

删除显式 auto 会破坏语言:

例如

int main()
{
    int n;
    {
        auto n = 0; // this shadows the outer n.
    }
}

你可以看到删除 auto 不会 shadow 外部 n.

语法必须明确且向后兼容。

如果删除 auto,将无法区分语句和定义。

auto n = 0; // fine
n=0; // statememt, n is undefined.

auto 是一个关键字,您可以在通常需要指定 type.

的地方使用它
  int x = some_function();

可以通过自动推导 int 类型来变得更通用:

  auto x = some_function();

所以这是对语言的保守扩展;它适合现有的语法。没有它 x = some_function() 变成赋值语句,不再是声明。

你的问题有两种解释:

  • 为什么我们需要 'auto'?我们不能简单地放弃它吗?
  • 为什么我们必须使用汽车?如果没有给定,我们不能只隐含它吗?

Bathsheba 第一种解释很好,对于第二种解释,请考虑以下内容(假设到目前为止不存在其他声明;假设 有效的 C++):

int f();
double g();

n = f(); // declares a new variable, type is int;
d = g(); // another new variable, type is double

if(n == d)
{
    n = 7; // reassigns n
    auto d = 2.0; // new d, shadowing the outer one
}

是可能的,其他语言可以很好地解决(好吧,除了阴影问题之外)......但在 C++ 中并非如此,并且现在的问题(在第二种解释的意义上)是:为什么?

这一次,答案不像第一种解释那样明显。不过有一点很明显:对关键字的明确要求使语言更安全(我不知道这是否是促使语言委员会做出决定的原因,但仍然是一个观点):

grummel = f();

// ...

if(true)
{
    brummel = f();
  //^ uh, oh, a typo...
}

我们可以就此达成一致,不需要任何进一步的解释吗?

The even bigger danger in not requiring auto, [however], is that it means that adding a global variable in a place far away from a function (e.g. in a header file) can turn what was intended to be the declaration of a locally-scoped variable in that function into an assignment to the global variable... with potentially disastrous (and certainly very confusing) consequences.

(引用 psmears' 评论因其重要性 - 感谢您的提示)

简而言之:在某些情况下可以删除 auto,但这会导致不一致。

首先,如前所述,C++中的声明语法是<type> <varname>。显式声明需要某种类型或至少需要一个声明关键字。所以我们可以使用 var <varname>declare <varname> 或其他东西,但是 auto 是 C++ 中长期存在的关键字,并且是自动类型推导关键字的良好候选者。

是否可以在不破坏一切的情况下通过赋值隐式声明变量?

有时是的。您不能在函数外部执行赋值,因此您可以在其中使用赋值语法进行声明。但是这种做法会给语言带来不一致,可能导致人为错误。

a = 0; // Error. Could be parsed as auto declaration instead.
int main() {
  return 0;
}

当涉及到任何类型的局部变量时,显式声明是控制变量范围的方式。

a = 1; // use a variable declared before or outside
auto b = 2; // declare a variable here

如果允许有歧义的语法,声明全局变量可能会突然将局部隐式声明转换为赋值。查找这些转化需要检查所有内容。为了避免冲突,您需要为所有全局变量起一个唯一的名称,这会破坏范围界定的整个想法。所以真的很糟糕。

was it not possible to achieve the same outcome without explicitly declaring a variable auto?

我将以一种有助于您理解为什么需要 auto:

的方式稍微改写您的问题

Was it not possible to achieve the same outcome without explicitly using a type placeholder?

不是可能吗?当然是 "possible"。问题是这样做是否值得。

大多数其他语言中没有类型名的语法以两种方式之一工作。有一种类似 Go 的方式,其中 name := value; 声明一个变量。还有一种类似于 Python 的方式,其中如果 name 之前未声明过,则 name = value; 声明一个新变量。

让我们假设将这两种语法应用于 C++ 都没有语法问题(尽管我已经可以看到 identifier 后跟 : 在 C++ 中表示 "make a label")。那么,与占位符相比,你失去了什么?

好吧,我不能再这样做了:

auto &name = get<0>(some_tuple);

看,auto 总是表示 "value"。如果你想得到一个引用,你需要显式地使用一个&。如果赋值表达式是纯右值,它肯定会编译失败。这两种基于赋值的语法都无法区分引用和值。

现在,如果给定值是引用,您可以使此类赋值语法推导出引用。但这意味着你不能这样做:

auto name = get<0>(some_tuple);

从元组复制 ,创建一个独立于 some_tuple 的对象。有时,这正是您想要的。如果您想从 auto name = get<0>(std::move(some_tuple));.

的元组中移动,这会更有用

好的,也许我们可以稍微扩展这些语法来解释这种区别。也许 &name := value;&name = value; 意味着推断出像 auto&.

这样的引用

好的,好的。这个怎么样:

decltype(auto) name = some_thing();

哦,对了; C++其实有two placeholders: auto and decltype(auto)。这种推论的基本思想是,它的工作原理就好像你已经完成了 decltype(expr) name = expr;。所以在我们的例子中,如果 some_thing() 是一个对象,它会推导出一个对象。如果some_thing()是一个引用,它会推导出一个引用。

当您使用模板代码并且不确定函数的 return 值究竟是什么时,这非常有用。这对转发来说很棒,是必备工具,即使它没有被广泛使用。

所以现在我们需要在语法中添加更多内容。 name ::= value; 表示 "do what decltype(auto) does"。我没有 Pythonic 变体的等效项。

看看这个语法,是不是很容易不小心打错?不仅如此,它几乎没有自我记录。即使您以前从未见过 decltype(auto),它也足够大且足够明显,您至少可以轻松地判断出正在发生一些特别的事情。 ::=:= 之间的视觉差异很小。

但那是意见;还有更多的实质性问题。看,所有这些都是基于使用赋值语法。嗯...那些你不能使用赋值语法的地方呢?像这样:

for(auto &x : container)

我们是否将其更改为 for(&x := container)?因为这似乎与基于范围的 for 非常不同 。它看起来像是来自常规 for 循环的初始化语句,而不是基于范围的 for。它也将是与非推导案例不同的语法。

此外,C++ 中的复制初始化(使用 =)与直接初始化(使用构造函数语法)不同。因此 name := value;auto name(value) 可能有效的情况下可能无效。

当然,您可以声明 := 将使用直接初始化,但这与 C++ 的其余部分的行为方式完全不一致。

此外,还有一件事:C++14。它给了我们一个有用的推导特性:return 类型推导。但这是基于占位符的。与基于范围的 for 非常相似,它基本上是基于由编译器填充的类型名称,而不是通过应用于特定名称和表达式的某些语法。

看,所有这些问题都来自同一个源头:您正在发明全新的语法来声明变量。基于占位符的声明不必发明新的 语法 。他们使用与以前完全相同的语法;他们只是使用了一个新的关键字,它的作用类似于一种类型,但具有特殊的含义。这就是允许它在基于范围的 for 和 return 类型推导中工作的原因。它允许它具有多种形式(autodecltype(auto))。等等。

占位符之所以有效,是因为它们是解决问题的最简单方法,同时保留了使用实际类型名称的所有好处和通用性。如果您想出了另一种像占位符一样通用的替代方法,那么它不太可能像占位符一样简单。

除非它只是用不同的关键字或符号拼写占位符...

添加到以前的答案,一个老屁的额外说明:看起来你可能认为能够开始使用新变量而不以任何方式声明它是一种优势。

在可能隐式定义变量的语言中,这可能是个大问题,尤其是在较大的系统中。你犯了一个错字,你调试了几个小时才发现你无意中引入了一个值为零(或更糟)的变量 - blue vs bleulabel vs lable ...结果是,如果不彻底检查精确的变量名称,您就无法真正信任任何代码。

只需使用 auto 即可告诉编译器和维护者您打算声明一个新变量。

想一想,为了避免这种噩梦,FORTRAN 中引入了 'implicit none' 语句 - 您会发现它在当今所有严肃的 FORTRAN 程序中都被使用。没有它简直是……可怕。