web-dev-qa-db-ja.com

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

私はC++ 98からC++ 11に向かって動いていて、autoキーワードに慣れてきました。私は、コンパイラが自動的に型を推測できるのであれば、なぜautoを明示的に宣言する必要があるのか​​疑問に思いました。 C++は厳密に型指定された言語であり、これは規則ですが、変数autoを明示的に宣言せずに同じ結果を達成することは不可能でしたか。

77
Farsan Rashid

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

例えば.

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

autoを削除してもshadow外側のnにはならないことがわかります。

154
Bathsheba

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

  • なぜ 'auto'が必要なのですか?単純に落とすことはできませんか?
  • なぜautoを使わなければならないのですか?それが与えられていないのであれば、それを暗黙のうちに持つことはできませんか?

Bathsheba 回答 最初の解釈はうまくいき、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番目の解釈という意味での)問題は、次のとおりです。なぜですか?

今回は、答えは最初の解釈ほど明白ではありません。ただし、キーワードに対する明示的な要件により、言語の安全性が高まります(これが言語委員会の決定につながったかどうかはわかりませんが、それでもやはり要点です)。

grummel = f();

// ...

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

これ以上の説明を必要としないことに同意できるでしょうか。

Autoを必要としないことのさらに大きな危険は、関数から遠く離れた場所(例えば、ヘッダファイル)にグローバル変数を追加することは、ローカル変数の宣言を意図したものに変えることができるということです。その関数のスコープ付き変数は、グローバル変数への代入に...潜在的に悲惨な(そして確かに非常に紛らわしい)結果になります。

(引用された psmears ' 重要性によるコメント - ヒントしてくれてありがとう)

38
Aconcagua

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

autoが必要な理由を理解できるように、質問を少し言い換えます。

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

possibleではありませんでしたか?もちろん、それは「可能」でした。問題は、それを行う努力に値するかどうかです。

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

C++にどちらの構文を適用しても構文上の問題はないと仮定しましょう(C++でidentifierに続いて:が続くことは既にわかっていますが)。では、プレースホルダーと比較して何を失うのでしょうか?

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

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

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

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

auto name = get<0>(some_Tuple);

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

さて、この区別を説明するためにこれらの構文を少し拡張することができます。 &name := value;または&name = value;は、auto&などの参照を推測することを意味する場合があります。

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

decltype(auto) name = some_thing();

そうそう。 C++には実際に twoプレースホルダー: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)に変更しますか?それは、範囲ベースのforと何かvery differentを言っているようだからです。範囲ベースのforではなく、通常のforループからの初期化ステートメントのようです。また、非推定の場合とは異なる構文になります。

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

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

また、もう1つあります。C++ 14です。これにより、1つの便利な演feature機能が得られました。戻り値のタイプ演ductionです。しかし、これはプレースホルダーに基づいています。範囲ベースのforと同様に、基本的には、特定の名前と式に適用される構文ではなく、コンパイラーによって入力される型名に基づいています。

参照してください、これらの問題はすべて同じソースに由来します。変数を宣言するためのまったく新しい構文を発明しています。プレースホルダーベースの宣言では、新しいsyntaxを作成する必要はありませんでした。以前とまったく同じ構文を使用しています。タイプのように機能するが、特別な意味を持つ新しいキーワードを使用しているだけです。これにより、範囲ベースのforで動作し、戻り値の型を推測できます。複数のフォーム(auto vs. decltype(auto))を持つことができるのです。などなど。

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

異なるキーワードまたは記号を使用してプレースホルダーをつづるだけではない場合...

14
Nicol Bolas

一言で言えば:autoはいくつかのケースで削除されるかもしれませんが、それは矛盾につながるでしょう。

まず第一に、指摘したように、C++の宣言構文は<type> <varname>です。明示的な宣言には、何らかの型、または少なくともその代わりに宣言キーワードが必要です。 var <varname>declare <varname>などを使うこともできますが、C++ではautoが古くから使われているキーワードであり、自動型推論キーワードには適しています。

すべてを壊すことなく、代入によって変数を暗黙的に宣言することは可能ですか?

時々はい。関数の外で代入を実行することはできないので、そこで宣言に代入構文を使用することができます。しかし、そのようなアプローチは言語に矛盾をもたらし、人的ミスを招く可能性があります。

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

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

12

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

  int x = some_function();

int型を自動的に推定することで、より一般的なものにすることができます。

  auto x = some_function();

だからそれは言語への保守的な拡張です。それは既存の構文に適合します。それがなければx = some_function()は代入文になり、もはや宣言にはなりません。

11
rustyx

構文は明確で後方互換性がなければなりません。

Autoが削除された場合、文と定義を区別する方法はありません。

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

以前の答えに加えて、古いおならからの1つの追加メモ:それを宣言することなく新しい変数を使い始めることができることが利点としてそれを見るかもしれないようです。

暗黙的に変数を定義できる可能性がある言語では、これは特に大きなシステムでは大きな問題になる可能性があります。 1つの入力ミスをして、何時間もデバッグして、意図せずに値がゼロ(またはそれより悪い)の変数が導入されたことを発見した場合 - blue vs bleu vs label vs lable ...正確な変数名を徹底的にチェックしてください。

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

この種の悪夢を避けるために、FORTRANで '暗黙の' none 'ステートメントが導入されたことを考えてみてください - そして今日あなたはそれがすべての深刻なFORTRANプログラムで使われているのを見ます。それを持っていないのは単純です...怖いです。

3
Bert Bril