'auto'キーワードを明示的に記述する必要があるのはなぜですか?


80

私はC ++ 98からC ++ 11に移行し、autoキーワードに慣れてきました。autoコンパイラが型を自動的に推測できるかどうかを明示的に宣言する必要があるのはなぜだろうと思っていました。C ++は強く型付けされた言語であり、これはルールですが、変数を明示的に宣言せずに同じ結果を達成することはできませんでしたautoか?


Cファミリでは大文字と小文字が区別されることに注意してください。作成者が「var」を省略し、「Bob」、「bob」、「boB」などの名前の個別の変数を使用するJSコードをデバッグします。うーん。
PTwr 2018年

46
それが可能であったとしても、それは適度にひどい考えでしょう。おそらく、Python(および同様の言語)の最大の弱点は、宣言構文がないことです。単純な割り当てで新しい変数が作成されるのは、バグの主な原因です。
コンラッドルドルフ

@KonradRudolph:JSは、宣言構文が優れているわけではありません。彼らが言っているのは、変数のスコープをきめ細かく制限できないということだと思います。
user541686 2018年

4
@Mehrdadは「セマンティクスを変更します」≠「必須」です。問題は、JavaScript暗黙の宣言を受け入れることです。はい、それらは意味的に異なりますが、それは少しでも役に立ちません。
コンラッドルドルフ

1
「実際のPerlユーザーが「my」キーワードを使用する理由」という二重の質問も参照してください。stackoverflow.com/ questions / 8023959
warnings /

回答:


156

明示的なものautoを削除すると、言語が壊れます。

例えば

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

ここで、ドロップしても外側が影にautoならないことがわかります。n


8
まったく同じことを入力していました。割り当てと初期化を区別するには、標準側で任意の選択が必要になります。「宣言になり得るものはすべて宣言である」というルールがすでにあるので、非常に濁った水に足を踏み入れます。
StoryTeller-Unslander Monica 2018年

4
これは問題ではありません。golangのように、あなたは明らかにn := 0新しい変数を導入するようなものを使うことができます。なぜauto使用されるのかは意見に基づく質問です。
llllllllll 2018年

23
@ liliscent-意見に基づいていますか?(a)それはすでに予約されたキーワードでした。(b)意味は非常に明確です。(c)新しいトークン(のような:=)を導入する必要がなくなります。(d)すでに文法に適合しています。ここには意見の余地がほとんどないと思います。
StoryTeller-Unslander Monica 2018年

2
@StoryTellerx = f()新しい変数を宣言する場合(まだ存在しない場合)、fの戻り値のタイプを取得する場合、新しいトークンは必要ありません...変数を明示的に宣言するために自動が必要ですが、新しい変数を宣言するリスクが軽減されます偶然(例えば、タイプミスのために...)。
アコンカグア

34
@ Aconcagua- 「新しい変数を宣言します(まだ存在しない場合)」しかし、シャドウイング言語の一部であり、Bathshebaが示すように機能する必要があります。それは想像以上に大きな問題です。それはゼロからの言語デザインではなく、生きている呼吸言語を変えることです。行うのははるかに難しい。スピード違反の車のホイールを変えるようなものです。
StoryTeller-Unslander Monica 2018年

40

あなたの質問は2つの解釈を可能にします:

  • なぜ「自動」が必要なのですか?単純に落とせませんか?
  • なぜ自動車を使わなければならないのですか?それが与えられていない場合、私たちはそれを暗黙的にすることはできませんか?

バトシェバは最初の解釈にうまく答えました。2番目の解釈については、次のことを考慮してください(これまでに他の宣言が存在しないと仮定します。仮想的に有効な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 ++にはそうではない、と(第2の解釈の意味での)質問は以下のようになります。なぜ?

今回は、最初の解釈ほど明確ではありません。ただし、明らかなことが1つあります。キーワードの明示的な要件により、言語がより安全になります(これが言語委員会を決定に導いた理由かどうかはわかりませんが、それでも問題はありません)。

grummel = f();

// ...

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

これ以上の説明を必要としないことに同意できますか?

自動を必要としないことのさらに大きな危険は、[ただし]、関数から遠く離れた場所(ヘッダーファイルなど)にグローバル変数を追加すると、ローカルでの宣言を意図したものが変わる可能性があることを意味します。その関数内のスコープ変数をグローバル変数への割り当てに...悲惨な(そして確かに非常に混乱する)結果をもたらす可能性があります。

(その重要性のためにpsmearsのコメントを引用しました-ほのめかしてくれてありがとう)


24
auto私の見解では、を必要としないことのさらに大きな危険は、関数から遠く離れた場所(ヘッダーファイルなど)にグローバル変数を追加すると、ローカルスコープの宣言であることが意図されていたものが変わる可能性があることを意味しますその関数内の変数をグローバル変数への割り当てに...悲惨な(そして確かに非常に混乱する)結果をもたらす可能性があります。
psmears 2018年

1
@psmears Pythonのような言語は、割り当てに対してグローバル/非ローカルとして変数を明示的に指定することを要求することにより、それを回避します。デフォルトでは、その名前で新しいローカル変数を作成するだけです。(もちろん、global <variable>ステートメントを必要とせずにグローバル変数から読み取ることができます。)もちろん、C ++言語にさらに変更を加える必要があるため、おそらく実現可能ではありません。
JAB 2018年

@ JAB-うん、私はそれを知っている...あなたが言うように、それは言語にさらに多くの修正を必要とするので、私はそれについて言及しなかった:)
psmears 2018年

FWIW、言語委員会を動かしたのはおそらく歴史です。AFAIK、Cが最初に記述されたとき、ローカル変数はスタックに保存され、Cはすべての変数をブロック内で最初に明示的に宣言する必要がありました。これにより、コンパイラは残りのコードをコンパイルする前にそのブロックのストレージ要件を決定し、スタックにスペースを割り当てるための正しい命令シーケンスを発行できるようになりました。MOV R6 R5 SUB #nnn R6R5がフレームポインタとして使用され、R6がスタックポインタであると想定したPDP-11のIIRC 。nnnは、必要なストレージのバイト数です。
dgnuff 2018年

2
人々はPythonをなんとか使用しています。Pythonは、割り当ての左側に新しい名前が表示されるたびに(その名前がタイプミスであっても)変数を喜んで宣言します。しかし、私はそれを言語のより深刻な欠陥の1つと考えています。
hobbs 2018年

15

変数を明示的に宣言せずに同じ結果を達成することは不可能でしたautoか?

なぜ必要なのかを理解するのに役立つ方法で、質問を少し言い換えますauto

タイププレースホルダーを明示的に使用せずに同じ結果を達成することはできませんでしたか?

それは不可能でしたか?もちろんそれは「可能」でした。問題は、それを行うために努力する価値があるかどうかです。

タイプ名を使用しない他の言語のほとんどの構文は、2つの方法のいずれかで機能します。name := value;変数を宣言するGoのような方法があります。また、Pythonのような方法があり、以前に宣言されていないname = value;場合nameは新しい変数を宣言します。

どちらの構文もC ++に適用しても構文上の問題はないと仮定しましょう(C ++でのidentifier後に続くの:は「ラベルを作成する」ことを意味します)。では、プレースホルダーと比較して何を失いますか?

まあ、私はもうこれを行うことはできません:

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

ほら、auto常に「価値」を意味します。参照を取得する場合は、明示的にを使用する必要があります&。そして、代入式がprvalueの場合、コンパイルに失敗するのは当然です。割り当てベースの構文には、参照と値を区別する方法がありません。

これで、指定された値が参照である場合、そのような割り当て構文で参照を推測することができます。しかし、それはあなたができないことを意味します:

auto name = get<0>(some_tuple);

これはタプルからコピーし、独立したオブジェクトを作成しますsome_tuple。時々、それはまさにあなたが望むものです。これは、auto name = get<0>(std::move(some_tuple));。を使用してタプルから移動する場合にさらに便利です。

OK、それで、この区別を説明するために、これらの構文を少し拡張できるかもしれません。たぶん、&name := value;またはの&name = value;ような参照を推測することを意味しauto&ます。

いいよ。これはどうですか:

decltype(auto) name = some_thing();

そうです。C ++には、実際には2つのプレースホルダーがautoありdecltype(auto)ます。この控除の基本的な考え方は、あなたが行ったかのように正確に機能するということですdecltype(expr) name = expr;。したがって、この場合、some_thing()がオブジェクトの場合、オブジェクトを推測します。some_thing()が参照の場合、参照を推測します。

これは、テンプレートコードで作業していて、関数の戻り値が正確にわからない場合に非常に便利です。これは転送に最適であり、広く使用されていなくても不可欠なツールです。

したがって、構文にさらに追加する必要があります。name ::= value;「何をするか」を意味decltype(auto)します。Pythonicバリアントに相当するものはありません。

この構文を見ると、誤ってタイプミスするのは簡単ではありませんか?それだけでなく、自己文書化することはほとんどありません。これまでに見たことがない場合でも、decltype(auto)何か特別なことが起こっていることが少なくとも簡単にわかるほど大きくて明白です。視覚的な違い一方::=:=最小です。

しかし、それは意見です。もっと実質的な問題があります。ほら、これはすべて割り当て構文の使用に基づいています。さて...割り当て構文を使用できない場所はどうですか?このような:

for(auto &x : container)

それをに変更しfor(&x := container)ますか?それは範囲ベース非常に異なることを言っているように見えるからですforfor範囲ベースではなく、通常のループからの初期化ステートメントのようです。for。また、演繹されていない場合とは構文が異なります。

また、コピー初期化(を使用=)は、C ++では直接初期化(コンストラクター構文を使用)と同じではありません。そのname := value;ため、ある場合にauto name(value)は機能しない可能性があります。

もちろん、:=直接初期化を使用することを宣言することはできますが、それはC ++の他の部分の動作とはまったく一致しません。

また、もう1つ、C ++ 14があります。それは私たちに1つの有用な控除機能を与えました:戻り値の型控除。ただし、これはプレースホルダーに基づいています。範囲ベースと非常によく似ていますforが、基本的には、特定の名前や式に適用される構文ではなく、コンパイラーによって入力される型名に基づいています。

ほら、これらの問題はすべて同じ原因から来ています。変数を宣言するためのまったく新しい構文を発明しているのです。プレースホルダーベースの宣言は、新しい構文を発明する必要はありませんでした。以前とまったく同じ構文を使用しています。タイプのように機能するが、特別な意味を持つ新しいキーワードを採用しているだけです。これにより、範囲ベースforおよび戻り値の型の推定で機能することができます。それはそれが複数の形を持つことを可能にするものです(autodecltype(auto))。などなど。

プレースホルダーは、問題の最も簡単な解決策であると同時に、実際の型名を使用することのすべての利点と一般性を保持しているため、機能します。プレースホルダーと同じように普遍的に機能する別の代替案を思いついた場合、それがプレースホルダーのように単純になる可能性はほとんどありません。

プレースホルダーを異なるキーワードや記号でつづるだけでない限り...


2
私見、これはプレースホルダーの選択の背後にあるいくつかの実質的な論理的根拠に対処する唯一の答えです。ジェネリックラムダでの型推論は別の例かもしれません。それは...それは少し遅れて掲載されたという理由だけで、この答えは非常に少ないupvotesを得たことは残念だ
llllllllll

@liliscent:「ジェネリックラムダでの型の推定は別の例かもしれません。auto宣言/戻り値の推定とはセマンティクスが異なるため、言及しませんでした。
ニコルボーラス2018年

@liliscent:確かに、この答えはパーティーに遅れています。アップ。(ただし、1つの改善点は、何か宣言である可能性がある場合、それ宣言であるというC ++のアイデアについての言及です。)
バトシェバ

12

つまりauto、場合によっては削除される可能性がありますが、それは不整合につながる可能性があります。

まず、指摘したように、C ++の宣言構文は<type> <varname>です。明示的な宣言には、その場所に何らかの型または少なくとも宣言キーワードが必要です。したがって、var <varname>ordeclare <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

あいまいな構文が許可されている場合、グローバル変数を宣言すると、ローカルの暗黙的な宣言が突然割り当てに変換される可能性があります。それらの変換を見つけるには、すべてをチェックする必要があります。また、衝突を回避するには、すべてのグローバルに一意の名前が必要になります。これにより、スコープの概念全体が破壊されます。だからそれは本当に悪いです。


11

autoは、通常タイプを指定する必要がある場所で使用できるキーワードです。

  int x = some_function();

intタイプを自動的に推定することで、より一般的にすることができます。

  auto x = some_function();

つまり、これは言語の控えめな拡張です。これは既存の構文に適合します。それx = some_function()がなければ、割り当てステートメントになり、宣言ではなくなります。


9

構文は明確で、下位互換性も必要です。

autoが削除された場合、ステートメントと定義を区別する方法はありません。

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

3
重要な点は、それautoはすでにキーワードであったため(ただし、意味は廃止されました)、名前として使用してコードを壊すことはありませんでした。これが理由ですが、varまたはのようなより良いキーワードletが代わりに選択されませんでした。
Frax 2018年

1
@Frax IMOautoは、実際にはこのための非常に優れたキーワードです。つまり、型名を「自動型」に置き換えるという意味を正確に表現しています。varまたはのようなキーワードを使用すると、タイプが明示的に指定されている場合でも、つまり、またはのようなletキーワードが必要になります。これは基本的にRustで行われている方法です。var int n = 0var n:Int = 0
左回り

1
一方では、@leftaroundaboutauto既存の構文のコンテキストで間違いなく優れている、私のようなその何か言うvar int x = 42と、基本的な変数の定義であることvar x = 42int x = 42歴史的内容のうちと考えた場合、簡略表記としては、現在の構文よりも、より多くの意味になるだろう。しかし、それは主に好みの問題です。しかし、あなたは正しいです、私は私の元のコメントに「理由」の代わりに「理由の1つ」を書くべきでした:)
Frax 2018年

@leftaroundabout:"autoは、実際にはこのための非常に優れたキーワードです。これは、それが表すものを正確に表現します。つまり、型名を「自動型」に置き換えます。これは正しくありません。「自動タイプ」はありません。
軌道上のライトネスレース

@LightnessRacesinOrbitを使用できる任意のコンテキストでautoは、自動タイプがあります(式に応じて異なるタイプ)。
18年

3

以前の回答に加えて、古いオナラからの1つの追加のメモ:新しい変数を宣言せずに使用を開始できることは利点と見なされるようです。

変数の暗黙的な定義の可能性がある言語では、これは特に大規模なシステムでは大きな問題になる可能性があります。あなたは1つのタイプミスを作り、あなただけが意図せずにゼロ(またはより悪い)の値を持つ変数を導入見つけるために時間デバッグ-bluebleulabellable結果は...あなたは本当に正確に徹底的にチェックせずに任意のコードを信頼することはできません変数名。

使用するだけでauto、コンパイラとメンテナの両方に、新しい変数を宣言することが意図されていることがわかります。

この種の悪夢を回避できるようにするために、FORTRANで「implicitnone」ステートメントが導入されました。これは、今日のすべての深刻なFORTRANプログラムで使用されています。それを持っていないのは単に...怖いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.