私はC ++ 98からC ++ 11に移行し、auto
キーワードに慣れてきました。auto
コンパイラが型を自動的に推測できるかどうかを明示的に宣言する必要があるのはなぜだろうと思っていました。C ++は強く型付けされた言語であり、これはルールですが、変数を明示的に宣言せずに同じ結果を達成することはできませんでしたauto
か?
私はC ++ 98からC ++ 11に移行し、auto
キーワードに慣れてきました。auto
コンパイラが型を自動的に推測できるかどうかを明示的に宣言する必要があるのはなぜだろうと思っていました。C ++は強く型付けされた言語であり、これはルールですが、変数を明示的に宣言せずに同じ結果を達成することはできませんでしたauto
か?
回答:
明示的なものauto
を削除すると、言語が壊れます。
例えば
int main()
{
int n;
{
auto n = 0; // this shadows the outer n.
}
}
ここで、ドロップしても外側が影にauto
ならないことがわかります。n
n := 0
新しい変数を導入するようなものを使うことができます。なぜauto
使用されるのかは意見に基づく質問です。
:=
)を導入する必要がなくなります。(d)すでに文法に適合しています。ここには意見の余地がほとんどないと思います。
x = f()
新しい変数を宣言する場合(まだ存在しない場合)、fの戻り値のタイプを取得する場合、新しいトークンは必要ありません...変数を明示的に宣言するために自動が必要ですが、新しい変数を宣言するリスクが軽減されます偶然(例えば、タイプミスのために...)。
あなたの質問は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のコメントを引用しました-ほのめかしてくれてありがとう)
auto
私の見解では、を必要としないことのさらに大きな危険は、関数から遠く離れた場所(ヘッダーファイルなど)にグローバル変数を追加すると、ローカルスコープの宣言であることが意図されていたものが変わる可能性があることを意味しますその関数内の変数をグローバル変数への割り当てに...悲惨な(そして確かに非常に混乱する)結果をもたらす可能性があります。
global <variable>
ステートメントを必要とせずにグローバル変数から読み取ることができます。)もちろん、C ++言語にさらに変更を加える必要があるため、おそらく実現可能ではありません。
MOV R6 R5
SUB #nnn R6
R5がフレームポインタとして使用され、R6がスタックポインタであると想定したPDP-11のIIRC 。nnnは、必要なストレージのバイト数です。
変数を明示的に宣言せずに同じ結果を達成することは不可能でした
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)
ますか?それは範囲ベースとは非常に異なることを言っているように見えるからですfor
。for
範囲ベースではなく、通常のループからの初期化ステートメントのようです。for
。また、演繹されていない場合とは構文が異なります。
また、コピー初期化(を使用=
)は、C ++では直接初期化(コンストラクター構文を使用)と同じではありません。そのname := value;
ため、ある場合にauto name(value)
は機能しない可能性があります。
もちろん、:=
直接初期化を使用することを宣言することはできますが、それはC ++の他の部分の動作とはまったく一致しません。
また、もう1つ、C ++ 14があります。それは私たちに1つの有用な控除機能を与えました:戻り値の型控除。ただし、これはプレースホルダーに基づいています。範囲ベースと非常によく似ていますfor
が、基本的には、特定の名前や式に適用される構文ではなく、コンパイラーによって入力される型名に基づいています。
ほら、これらの問題はすべて同じ原因から来ています。変数を宣言するためのまったく新しい構文を発明しているのです。プレースホルダーベースの宣言は、新しい構文を発明する必要はありませんでした。以前とまったく同じ構文を使用しています。タイプのように機能するが、特別な意味を持つ新しいキーワードを採用しているだけです。これにより、範囲ベースfor
および戻り値の型の推定で機能することができます。それはそれが複数の形を持つことを可能にするものです(auto
対decltype(auto)
)。などなど。
プレースホルダーは、問題の最も簡単な解決策であると同時に、実際の型名を使用することのすべての利点と一般性を保持しているため、機能します。プレースホルダーと同じように普遍的に機能する別の代替案を思いついた場合、それがプレースホルダーのように単純になる可能性はほとんどありません。
プレースホルダーを異なるキーワードや記号でつづるだけでない限り...
auto
宣言/戻り値の推定とはセマンティクスが異なるため、言及しませんでした。
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
あいまいな構文が許可されている場合、グローバル変数を宣言すると、ローカルの暗黙的な宣言が突然割り当てに変換される可能性があります。それらの変換を見つけるには、すべてをチェックする必要があります。また、衝突を回避するには、すべてのグローバルに一意の名前が必要になります。これにより、スコープの概念全体が破壊されます。だからそれは本当に悪いです。
構文は明確で、下位互換性も必要です。
autoが削除された場合、ステートメントと定義を区別する方法はありません。
auto n = 0; // fine
n=0; // statememt, n is undefined.
auto
はすでにキーワードであったため(ただし、意味は廃止されました)、名前として使用してコードを壊すことはありませんでした。これが理由ですが、var
またはのようなより良いキーワードlet
が代わりに選択されませんでした。
auto
は、実際にはこのための非常に優れたキーワードです。つまり、型名を「自動型」に置き換えるという意味を正確に表現しています。var
またはのようなキーワードを使用すると、タイプが明示的に指定されている場合でも、つまり、またはのようなlet
キーワードが必要になります。これは基本的にRustで行われている方法です。var int n = 0
var n:Int = 0
auto
既存の構文のコンテキストで間違いなく優れている、私のようなその何か言うvar int x = 42
と、基本的な変数の定義であることvar x = 42
やint x = 42
歴史的内容のうちと考えた場合、簡略表記としては、現在の構文よりも、より多くの意味になるだろう。しかし、それは主に好みの問題です。しかし、あなたは正しいです、私は私の元のコメントに「理由」の代わりに「理由の1つ」を書くべきでした:)
auto
は、自動タイプがあります(式に応じて異なるタイプ)。
以前の回答に加えて、古いオナラからの1つの追加のメモ:新しい変数を宣言せずに使用を開始できることは利点と見なされるようです。
変数の暗黙的な定義の可能性がある言語では、これは特に大規模なシステムでは大きな問題になる可能性があります。あなたは1つのタイプミスを作り、あなただけが意図せずにゼロ(またはより悪い)の値を持つ変数を導入見つけるために時間デバッグ-blue
対bleu
、label
対lable
結果は...あなたは本当に正確に徹底的にチェックせずに任意のコードを信頼することはできません変数名。
使用するだけでauto
、コンパイラとメンテナの両方に、新しい変数を宣言することが意図されていることがわかります。
この種の悪夢を回避できるようにするために、FORTRANで「implicitnone」ステートメントが導入されました。これは、今日のすべての深刻なFORTRANプログラムで使用されています。それを持っていないのは単に...怖いです。