int a [] = {1,2、}; 奇妙なカンマが許可されています。何か特別な理由は?


334

多分私はこの惑星の出身ではないかもしれませんが、以下は構文エラーであるように思えます:

int a[] = {1,2,}; //extra comma in the end

しかし、そうではありません。このコードがVisual Studioでコンパイルされたときは驚きましたが、C ++のルールに関してはMSVCコンパイラを信頼しないことを学んだので、標準を確認しましたが、標準で許可されています。あなたが私を信じないなら、あなたは文法規則のために8.5.1を見ることができます。

ここに画像の説明を入力してください

なぜこれが許可されるのですか?これは愚かな役に立たない質問かもしれませんが、なぜ私が尋ねているのか理解して欲しいのです。それが一般的な文法規則のサブケースである場合、私は理解するだろう-彼らは、初期化子リストの末尾に冗長なコンマを許可しないために、一般的な文法をこれ以上難しくしないことに決めました。ただし、追加のコンマは明示的に許可されています。たとえば、関数呼び出しの引数リストの最後に冗長なコンマを含めることはできません(関数がを受け取る場合...)。これは正常です。

繰り返しますが、この冗長なコンマが明示的に許可されている特別な理由はありますか?


10
誰もが「新しい行を追加するのが簡単」に同意しているようですが、言語仕様を定義している人々は本当にそのようなことについて悩んでいますか?彼らが本当に理解しているのなら、;次のトークンが実際には次のステートメントであることが明らかな場合、欠落を無視しないのはなぜですか。
YetAnotherUser 2011

35
@YetAnotherUser:はい、言語デザイナーはそのようなことを考慮します。セミコロンをドロップできるようにすることは、はるかに大きな影響を及ぼし、言語の多くの部分で非常にあいまいになります(Cでは空白は意味論ではないことに注意してください)。余分なコンマは、このケースがあいまいではありません。余分なセミコロンがあいまいになることはほとんどなく、そのため許可されています。あいまいな場合(for()たとえばの後)に追加すると、コンパイラ警告がスローされます。
Rob Napier

5
@Tomalak:人間の読者にはあいまいであり、多くの場合間違いです。これが警告をスローする理由です。同様if (x = 1)に、文法ではあいまいではありませんが、人間にとってはあいまいであるため、警告がスローされます。
Rob Napier、

12
@Rob:あなたのif例も曖昧ではありません。「あいまい」とは、あなたの考えていることだとは思いません!
オービットでの軽さのレース

5
コンパイラーが私たちを保護するのに役立つものであることに同意する限り、配列宣言の末尾のコンマは、コンパイラーが私たちを保護するのに役立つものではありません。
Rob Napier

回答:


435

これにより、ソースコードの生成が簡単になり、後日簡単に拡張できるコードを作成することもできます。追加のエントリを追加するために何が必要かを検討します。

int a[] = {
   1,
   2,
   3
};

...あなたは、既存の行にカンマを追加する必要がありますし、新しい行を追加します。これを、3つの行の後にコンマが既にあり、行を追加する必要がある場合と比較してください。同様に、行を削除したい場合は、それが最後の行であるかどうかを気にすることなく削除できます。また、コンマをいじることなく行を並べ替えることができます。基本的には、線の扱い方に統一性があることを意味します。

次に、コードの生成について考えます。(疑似コード)のようなもの:

output("int a[] = {");
for (int i = 0; i < items.length; i++) {
    output("%s, ", items[i]);
}
output("};");

現在書いているアイテムが最初か最後かを気にする必要はありません。はるかに簡単です。


89
また、VCSを使用する場合、項目が追加または削除されたときに変更されるのは1行だけなので、2つのバージョン間の「差分」がより明確になります。
ケビンパンコ

47
@ネストール:なぜ「不幸」なのですか?ここの欠点は何ですか?言語のごく一部についてコード生成(および簡単な操作)が考慮されているからといって、それが言語のすべての決定の背後にある主要な動機である必要があるわけではありません。型推論、セミコロンの削除などは、言語に大きな影響を与えます。ここで、IMOという誤った二分法を設定しています。
Jon Skeet、2013

18
@Néstor:それは実用主義が独断主義に勝るところです:なぜそれが完全に一方または完全に他方でなければならないのに、両方の混合であることがより有用であるのですか?実際にどのように邪魔をして、最後にカンマを追加できるのですか?これは、ある意味であなたを妨げてきた矛盾ですか?そうでない場合は、関係のない優雅さと、最後にカンマを付けることの実際的な利点を比較検討してください。
Jon Skeet

8
@Mrchief:入力速度の問題ではありません-アイテムをコピー、削除、または並べ替えるときの単純さの問題です。昨日、私の生活がシンプルになりました。欠点がないので、生活を楽にしてみませんか?MSを指さそうとすることに関しては、Microsoftが存在する前からCであったと強く思います...この正当化は奇妙に思われるかもしれませんが、毎日何百もの企業の何千もの開発者に利益をもたらすと思います。それは、コンパイラー作成者に利益をもたらす何かを探すよりも良い説明ではありませんか?
Jon Skeet、2011

6
これは、K&R Cであった
フェルッチョ

126

次のようなことをすると便利です:

int a[] = {
  1,
  2,
  3, //You can delete this line and it's still valid
};

6
JavaScriptはこの構文をサポートしています:var a = [1, 2,];なので、私が知っている他のほとんどの言語もそうです... ActionScript、Python、PHP。
藤原ショーン

14
@SeanこれはIE JavaScriptで解析エラーを引き起こすので、注意してください!
Skilldrick、2011

10
IE9では私には適していません。しかし、それは奇妙なことをします... null要素を作成します。私は用心します。
藤原ショーン

5
@Sean申し訳ありませんが、正解です-IEの解析エラーではありませんが、に設定された追加の要素挿入されundefinedます。
Skilldrick 2011

3
最も苛立たしいことに、JSONはこの構文をサポートしていません。
Timmmm 2017年

38

開発者にとって使いやすさだと思います。

int a[] = {
            1,
            2,
            2,
            2,
            2,
            2, /*line I could comment out easily without having to remove the previous comma*/
          }

さらに、何らかの理由でコードを生成するツールがあった場合。ツールは、それが初期化の最後の項目であるかどうかを気にする必要はありません。


32

私はいつもそれが余分な要素を追加することをより簡単にすることを仮定しました:

int a[] = {
            5,
            6,
          };

単に:

int a[] = { 
            5,
            6,
            7,
          };

後日。


3
編集を少し速くすることは、構文をめちゃくちゃにする良い理由だとは思いません。私見これは単なる別の奇妙なC ++機能です。
Giorgio

3
@ジョルジオ:まあ、それはCから継承されたものです。たまたま、元の言語仕様の見落としであり、たまたま有用な副作用がある可能性は十分にあります。
オリバーチャールズワース2011

OK、それがCからのものであることを知りませんでした。Javaでも許可されていることを確認しました。それはちょっと奇妙な感じがします。私の直感では、コンマは区切り文字ではなく区切り文字です。さらに、最後のコンマを省略することができます。それで、ターミネーター、セパレーター、またはその両方ですか?しかし、OK、この機能は利用可能であり、知っておくと便利です。
Giorgio

11
@Giorgio-ソースコードは人間用であり、マシン用ではありません。単純な転置エラーを防ぐためのこのような小さなことは、見落としではなく恵みです。参考までに、これはPHPとECMAScript(したがってJavaScriptとActionScript)でもこのように機能しますが、JavaScriptオブジェクト表記(JSON)では無効[1,2,3,]です(たとえば、大丈夫ですが、そうで{a:1, b:2, c:3,}はありません)。
2011

1
@Groky:考えれば考えるほど、プログラミング言語の構文は可能な限り単純で一貫性があり、できるだけ例外がないことが必要だと確信します。これにより、言語の学習が容易になります(覚えるルールが少なくなります) )。リストにアイテムを追加/リストから削除するときに1つまたは2つのキーストロークを保存することの利点(ちなみに、コーディングに費やす合計時間と比較すると、それほど頻繁には行いません)は、明確に定義された構文を持っている。
ジョルジョ

21

行の追加/削除/生成の容易さについて誰もが言っていることはすべて正しいですが、この構文が優れているのは、ソースファイルをマージするときです。次の配列があるとします。

int ints[] = {
    3,
    9
};

そして、このコードをリポジトリにチェックインしたと仮定します。

次に、バディが編集して、最後に追加します。

int ints[] = {
    3,
    9,
    12
};

そして同時に編集して、最初に追加します:

int ints[] = {
    1,
    3,
    9
};

意味的に、これらの種類の操作(最初に追加、最後に追加)は完全に安全にマージでき、バージョン管理ソフトウェア(うまくいけばgit)は自動マージできるはずです。悲しいことに、あなたのバージョンには9の後にカンマがなく、あなたのバージョンにはコンマがあるので、これはそうではありません。一方、元のバージョンの末尾が9だった場合、それらは自動マージされていました。

したがって、私の経験則は、リストが複数行にわたる場合は末尾のコンマを使用し、リストが1行にある場合は使用しないでください。


15

末尾のコンマは、下位互換性の理由で許可されていると思います。多くの既存のコードがあり、主に自動生成され、末尾にコンマが挿入されます。最後に特別な条件なしでループを記述しやすくなります。例えば

for_each(my_inits.begin(), my_inits.end(),
[](const std::string& value) { std::cout << value << ",\n"; });

プログラマにとっては何の利点もありません。

PSこの方法でコードを自動生成する方が簡単ですが、実際には常に末尾にコンマを付けないように注意しました。労力は最小限で、読みやすさが向上し、それがより重要です。あなたは一度コードを書いて、何度もそれを読みます。


5
私は完全に同意しません。[私の意見では]プログラマーが配列の内容を移動したり、行をコメントアウトしたりできるので、Cのずっと後に作成された多くの言語に採用されています。ばかげた転置による構文エラーを心配する必要はありません。もうストレスは足りませんか?
2011

12
@Dereleased -同じロジックで、なぜ(何も)末尾にはならないかについて、許可するint a = b + c +;か、if(a && b &&);それだけでコピーアンドペースト終わりに何かをしやすくし、書き込みコードジェネレータに容易になります。この問題は取るに足らないものでも主観的なものでもあります。そのような場合、コードリーダーにとって最善の方法を実行することは常に良いことです。
ジーンブシュエフ2011

1
@ジーンブシュエフ:その通り!+または&&を含む長い式を使用することが多く、演算子が行末にあります。もちろん、式の最後のオペランドを削除する場合は、余分な時間を費やす必要があります。このコンマ構文は本当に奇妙だと思います!
ジョルジョ

2
@GeneBushuyev-私はそれらに同意しません。配列などで末尾のコンマを許可することはバグを削除する機能であり、プログラマーとしての生活を楽にしますが、読みやすさのために、末尾のAND(&&)ステートメント、プラス記号、その他の演算子を条件付きから削除する対策を講じますステートメント。それは単に醜い、IMOです。
Sune Rasmussen、

2
&&演算子に関しては、if (true \n && b1 \n && b2)必要に応じて行を追加したり削除したりできるように、時々条件文を実行します。
クリスチャンマン

12

私が知る限りこれが許可されている理由の1つは、コードを自動的に生成するのが簡単であることです。最後の要素には特別な処理は必要ありません。


11

これにより、配列または列挙型を出力するコードジェネレーターが簡単になります。

想像してみてください:

std::cout << "enum Items {\n";
for(Items::iterator i(items.begin()), j(items.end); i != j; ++i)
    std::cout << *i << ",\n";
std::cout << "};\n";

つまり、末尾のコンマを吐き出さないようにするために、最初または最後のアイテムを特別に処理する必要はありません。

たとえば、コードジェネレーターがPythonで記述されている場合、次のstr.join()関数を使用すると、末尾のコンマを吐き出すのを簡単に回避できます。

print("enum Items {")
print(",\n".join(items))
print("}")

10

いつも、Annotated C ++ Reference ManualARM)を引用した人がいないことに驚いています。[dcl.init]について次のように強調しています:

初期化には明らかに多すぎる表記法がありますが、それぞれの表記法は特定のスタイルの使用に適しているようです。= {initializer_list、OPT}表記は、Cから継承されたデータ構造および配列の初期化のためによく働きます。[...]

ARMが書かれてから文法は進化しましたが、起源は残っています。

C99の根拠に移動して、これがCで許可された理由を確認できます。

K&Rでは、初期化子リストの最後にある初期化子の末尾にコンマを使用できます。標準は、初期化リストからメンバーを追加または削除する柔軟性を提供し、そのようなリストのマシン生成を簡素化するため、この構文を保持してい ます。


1
文献による最も裏付けられた回答と、この機能の真の情報源に賛成票を投じてください。
マルコ

10

他の回答で言及されていない1つの使用例、お気に入りのマクロが表示されます。

int a [] = {
#ifdef A
    1, //this can be last if B and C is undefined
#endif
#ifdef B
    2,
#endif
#ifdef C
    3,
#endif
};

最後に処理するマクロを追加するのは大変,なことです。構文のこの小さな変更により、これを管理するのは簡単です。そして、これは機械で生成されたコードよりも重要です。通常、チューリング完全言語で実行するのは、非常に限定されたプリプロセッサよりもはるかに簡単です。


7

実際には*許可されていない唯一の言語はJavascriptであり、無数の問題を引き起こします。たとえば、配列の中央から行をコピーして貼り付け、最後に貼り付け、コンマを削除するのを忘れた場合、IE訪問者にとってサイトは完全に壊れてしまいます。

*理論的には許可されていますが、Internet Explorerは標準に準拠しておらず、エラーとして扱います


JavaScriptの "配列"(これは魔法の長さのプロパティを持つ単なるオブジェクトです)はとにかく珍しいです:var x = [,,,]IE(9未満は例外ですが、仕様では合法とされています)
Peter C

ECMAScript仕様によると、それは完全に有効です。理論的には、上記の仕様に従ってJavaScriptを実装する任意のブラウザで動作するはずです。特に、ここにある仕様の一部です
2011

1
残念ながら、JavaScriptは一般向けのアプリの作成に関するものです。ですから、ユーザーの50%がアプリの使用に問題を抱えている場合は、完全に有効ではありません。そして、はい、IE <9を禁止することができれば- 良いコードをここで動作させるために費やす時間は多すぎる...
kgadek

@Dere:はい、私は私の答えで同じように言った=)
Thomas Bonini

@Dereleased microsoftは独自の仕様を発明し、他の人が少なくともその考え方が変化していることを遵守するように命じています(神に感謝)
Chris McGrath

7

これは、マシンの方が簡単です。つまり、コードの解析と生成です。また、人間にとっては、変更、コメントアウト、一貫性による視覚的な優雅さも容易です。

Cを想定して、次のように記述しますか?

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    puts("Line 1");
    puts("Line 2");
    puts("Line 3");

    return EXIT_SUCCESS
}

いいえ。最後のステートメントがエラーであるだけでなく、一貫性がないためでもあります。では、なぜコレクションでも同じことをするのでしょうか?最後のセミコロンとカンマを省略できる言語であっても、コミュニティは通常それを好まない。たとえば、Perlコミュニティは、セミコロンを省略したり、ワンライナーを省略したりするのを好まないようです。彼らはそれをカンマにも適用します。

複数行のコードブロックでセミコロンを省略しないのと同じ理由で、複数行のコレクションでカンマを省略しないでください。つまり、言語が許すとしても、あなたはそれをしないでしょう?正しい?


それを可能にする言語(例えばPascal)があります。つまり、あなたはどちらかを選ばなければなりません。ターミネーター(C)またはセパレーター(Pascal)として。「、」も同様です。'、'がターミネータである場合は問題ありませんが、{1、2、3}は構文エラーである必要があります。
ジョルジョ

6

その理由はささいなことです:行の追加/削除が簡単です。

次のコードを想像してください:

int a[] = {
   1,
   2,
   //3, // - not needed any more
};

これで、末尾のカンマを追加/削除しなくても、項目をリストに簡単に追加/削除できます。

他の回答とは対照的に、リストを簡単に生成できることが正当な理由だとは私は思いません。結局のところ、コードが最後(または最初)の行を特殊なケースにするのは簡単です。コードジェネレーターは1回記述され、何度も使用されます。


6

すべての行が同じフォームに従うことができます。まず、これにより、新しい行の追加が簡単になり、バージョン管理システムで変更を有意義に追跡できるようになり、コードをより簡単に分析できるようになります。技術的な理由は考えられません。


5

これは、長いリスト内で要素を移動することによって引き起こされる間違いから保護するために許可されています。

たとえば、次のようなコードがあるとします。

#include <iostream>
#include <string>
#include <cstddef>
#define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array))
int main() {
    std::string messages[] = {
        "Stack Overflow",
        "Super User",
        "Server Fault"
    };
    size_t i;
    for (i = 0; i < ARRAY_SIZE(messages); i++) {
        std::cout << messages[i] << std::endl;
    }
}

Stack Exchangeサイトの元の3部作を示しているので、すばらしいことです。

Stack Overflow
Super User
Server Fault

しかし、それには1つの問題があります。ご覧のとおり、このWebサイトのフッターには、スーパーユーザーの前にサーバー障害が表示されています。だれかに気付かれる前に修正してください。

#include <iostream>
#include <string>
#include <cstddef>
#define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array))
int main() {
    std::string messages[] = {
        "Stack Overflow",
        "Server Fault"
        "Super User",
    };
    size_t i;
    for (i = 0; i < ARRAY_SIZE(messages); i++) {
        std::cout << messages[i] << std::endl;
    }
}

結局のところ、線の移動はそれほど難しくありませんでしたか?

Stack Overflow
Server FaultSuper User

「Server FaultSuper User」というWebサイトはありませんが、コンパイラーはそれが存在すると主張しています。ここでの問題は、Cに文字列連結機能があり、2つの二重引用符で囲まれた文字列を記述し、何も使用せずに連結できることです(同様の問題は整数でも発生する可能性があります-。符号には複数の意味があるためです)。

では、元の配列の最後に無用なコンマがあったとしたらどうでしょう。まあ、行は移動されますが、そのようなバグは発生しなかったでしょう。コンマほどの小さなものを見逃しがちです。すべての配列要素の後にカンマを付けることを忘れない場合、そのようなバグは起こり得ません。あなたは、あなたがコンマはあなたの問題の原因である見つけるだろうまで、何かをデバッグする4時間を無駄にしたくないでしょう


4

多くのものと同様に、配列初期化子の末尾のコンマは、C ++がCから継承したものの1つです(これまでもサポートする必要があります)。ここに掲載されているものとはまったく異なる見解は、本「ディープCシークレット」に記載されています。

そこでは、複数の「コンマパラドックス」を使用した例を示します。

char *available_resources[] = {
"color monitor"           ,
"big disk"                ,
"Cray"                      /* whoa! no comma! */
"on-line drawing routines",
"mouse"                   ,
"keyboard"                ,
"power cables"            , /* and what's this extra comma? */
};

私たちは読んだ :

...最後の初期化子の後に続くコンマはタイプミスではなく、先住民のCから引き継がれた構文の一時的なものです。その存在または不在は許可されますが、意味はありません。ANSI Cの根拠で主張されている正当化は、Cの自動生成を容易にすることです。enum宣言や単一の宣言での複数の変数宣言など、すべてのカンマ区切りのリスト末尾のカンマが許可されている場合、主張はより信頼できるでしょう。ではない。

...私にはこれがもっと理にかなっています


2
enumケースでのコンマの禁止はいくぶん興味深いものです。それは、欠けているコンマが曖昧さを最小限に抑えるケースだからです。与えられたstruct foo arr[] = {{1,2,3,4,5}, {3,4,5,6,7}, }; 言語が割り当てることができる2つの実用的な意味があります。2要素の配列を作成するか、最後の項目にデフォルト値がある3要素の配列を作成します。Cがenum foo {moe, larry, curly, };
後者の

1
...それが合理的に重要な意味を割り当てられたかもしれない(しかしそうではなかった)場合にCがカンマを無視しても構わないとすると(それをそこで禁止することを支持する強力な議論になるでしょう)、それが不思議ですカンマが意味を持たない場合、「とのenum foo {moe,,larry,curly,};間の数値をスキップすると解釈されても、通常、末尾のカンマが処理されたか無視されたかは問題になりません。問題になる可能性がある唯一のケースは、最後の項目が宣言された型の最大値であるかどうかであり、それが...moelarry
supercat

1
...最後に割り当てられた列挙値の後に発生するオーバーフローは無視する必要があると単に言って処理できます。
スーパーキャット2015

@supercat C#のような言語があり、その言語を開発するときにIDEの機能と統合を考慮した先験的な設計調査が行われます。Cはこれらの言語の1つではありませんでした。
Nikos Athanasiou 2015

C#のような言語を使用しても、設計目標を変更すると、設計にかなり深刻な矛盾が生じます。たとえば、この言語は、基本的なフレームワークがそれをサポートしていても、通常のメソッドおよび演算子の戻り値型のオーバーロードの形式をサポートすることを控えていました。ラムダ評価には、解決がNP完全な型推論規則が含まれます。新しい方法/演算子(私は良いルールは、このような危険性を最小限に抑えることができると思いますが)既存のコードを壊すかもしれないルールをオーバーロード...追加
supercat

2

コード生成と編集の容易さに加えて、パーサーを実装したい場合、このタイプの文法は実装が簡単で簡単です。C#は、enum定義内のアイテムのように、コンマ区切りのアイテムのリストがあるいくつかの場所でこのルールに従います。


1

1行を追加するだけで、最後のエントリの追加を特別な場合のように扱う必要がないため、コードの生成が簡単になります。これは、マクロを使用してコードを生成する場合に特に当てはまります。言語からマクロの必要性を排除しようとする努力がありますが、多くの言語はマクロが利用できるようになって発展してきました。余分なコンマを使用すると、次のようなマクロを定義して使用できます。

#define LIST_BEGIN int a[] = {
#define LIST_ENTRY(x) x,
#define LIST_END };

使用法:

LIST_BEGIN
   LIST_ENTRY(1)
   LIST_ENTRY(2)
LIST_END

これは非常に単純な例ですが、このパターンは、ディスパッチ、メッセージ、イベント、変換のマップやテーブルなどを定義するためにマクロでよく使用されます。最後にカンマが許可されていない場合は、特別なものが必要です。

#define LIST_LAST_ENTRY(x) x

そして、それは非常に使いにくいでしょう。


0

したがって、2人のユーザーが別々のブランチのリストに新しいアイテムを追加すると、Gitは行単位で機能するため、変更を適切にマージできます。


-4

長さを指定せずに配列を使用すると、VC ++ 6.0はその長さを自動的に識別できるため、「int a [] = {1,2、};」を使用すると、aの長さは3ですが、最後の1つは持っていません。初期化されているため、「cout <


これは規格に準拠していないVC6のバグですか?
トムソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.