値の初期化での私の試みは関数宣言として解釈され、なぜa(())にならないのですか?解決しますか?


158

Stack Overflowが私に教えてくれた多くのことの1つは、「最も厄介な解析」として知られているものです。これは、次のような行で古典的に示されています。

A a(B()); //declares a function

これは、ほとんどの場合a、型のオブジェクトの宣言のように見えますがA、一時Bオブジェクトをコンストラクターのパラメーターとして受け取りますが、実際には、をa返す関数の宣言であり、A戻り値をB持ち、それ自体はパラメーターをとらない関数へのポインターを受け取ります。同様にライン

A a(); //declares a function

オブジェクトの代わりに関数を宣言しているため、同じカテゴリにも該当します。さて、最初のケースではB()、コンパイラがオブジェクトの宣言として解釈するため、この問題の通常の回避策は、ブラケット/括弧のセットを追加することです。

A a((B())); //declares an object

ただし、2番目のケースでは、同じことを行うとコンパイルエラーが発生します。

A a(()); //compile error

私の質問は、なぜですか?はい、私は正しい「回避策」がそれをに変更することであることを非常によく知っていますが、最初の例のコンパイラに対してA a;追加()が何をしてそれを再適用するときに機能しないのか知りたいです2番目の例。あるA a((B()));問題を回避するには、標準的に書き込まれた特定の例外?


20
(B())は単なるC ++式です。例外ではありません。唯一の違いは、型として解析できる可能性がないことです。そうではありません。
Pavel Minaev 2009

12
また、2番目のケースは同じカテゴリでA a();ないことに注意してください。コンパイラーの場合、それを解析する別の方法はありません。その場所の初期化子が空の括弧で構成されることはないため、これは常に関数宣言です。
Johannes Schaub-litb 2009

11
litbの優れた点は微妙ですが重要な点であり、強調する価値があります-この宣言に曖昧さが存在する理由 'A a(B())'は 'B()'の解析にあります->これは式&宣言&コンパイラーはdeclを介してdeclを「選択」する必要がある-したがって、B()がdeclの場合、「a」はfunc decl(変数declではなく)のみにすることができます。'()'が初期化子として許可されている場合、 'A a()'はあいまいになります-expr対declではなく、var declとfunc decl-あるdeclを他より優先するルールはないため、 '() 'はイニシャライザとしてここでは許可されていません-あいまいさは生じません。
ファイサルヴァリ09/09/15

6
A a();は、最も厄介な解析の例ではありません。それはCであると同じように、単に関数の宣言である
ピート・ベッカー

2
「正しい「回避策」はそれを変更することですA a;」は間違っています。それはあなたにPODタイプの初期化を与えません。初期化を取得するには、を書いてくださいA a{};
乾杯とhth。-Alf

回答:


70

賢明な答えはありません。それは、それがC ++言語によって有効な構文として定義されていないためです...つまり、言語の定義によってそうです。

中に式がある場合、それは有効です。例えば:

 ((0));//compiles

さらに簡単に言う(x)と、は有効なC ++式ですが、そうで()はありません。

言語がどのように定義されているか、コンパイラーがどのように機能しているかについて詳しく知るには、形式言語理論、具体的にはContext Free Grammars(CFG)および有限状態機械などの関連資料について学ぶ必要があります。ウィキペディアのページでは十分ではないが、それに興味がある場合は、本を入手する必要があります。


45
さらに簡単に言う(x)と、は有効なC ++式ですが、そうで()はありません。
Pavel Minaev 2009

私はこの回答を受け入れました。さらに、最初の質問に対するPavelのコメントがとても
GRB


29

C関数宣言子

まず、CがA a()あります。Cには関数宣言があります。たとえばputchar、次の宣言があります。通常、そのような宣言はヘッダーファイルに格納されますが、関数の宣言がどのように見えるかを知っていれば、それらを手動で記述することを妨げるものはありません。引数名は宣言ではオプションであるため、この例では省略しています。

int putchar(int);

これにより、このようなコードを記述できます。

int puts(const char *);
int main() {
    puts("Hello, world!");
}

Cでは、関数を引数として取る関数を定義することもできます。関数呼び出しのように見える読みやすい構文を使用できます(関数へのポインターを返さない限り、読みやすいです)。

#include <stdio.h>

int eighty_four() {
    return 84;
}

int output_result(int callback()) {
    printf("Returned: %d\n", callback());
    return 0;
}

int main() {
    return output_result(eighty_four);
}

先に述べたように、Cではヘッダーファイルで引数名を省略できるため、ヘッダーファイルでoutput_resultは次のようになります。

int output_result(int());

コンストラクターの1つの引数

分かりませんか?さて、思い出させてください。

A a(B());

はい、それはまったく同じ関数宣言です。Ais intais output_resultBis intです。

CとC ++の新機能との競合に簡単に気付くでしょう。正確には、コンストラクタはクラス名と括弧であり、代わりにを使用した宣言構文()です=。設計上、C ++はCコードとの互換性を維持しようとするため、実際には誰も気にしない場合でも、このケースに対処する必要があります。したがって、古いC機能が新しいC ++機能よりも優先されます。宣言の文法は、()失敗した場合に新しい構文に戻る前に、名前を関数として照合しようとします。

これらの機能の1つが存在しないか、または異なる構文({}C ++ 11など)があった場合、この問題は1つの引数を持つ構文では発生しませんでした。

次に、なぜA a((B()))機能するのかを尋ねます。さて、output_result無駄な括弧で宣言しましょう。

int output_result((int()));

動作しません。文法では、変数が括弧で囲まれていないことが必要です。

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

ただし、C ++はここでは標準式を想定しています。C ++では、次のコードを記述できます。

int value = int();

そして次のコード。

int value = ((((int()))));

C ++は、Cが期待する型とは対照的に、括弧内の式が...表現であると期待します。括弧はここでは何も意味しません。ただし、無用な括弧を挿入することにより、C関数宣言は一致せず、新しい構文を適切に一致させることができます(これは、などの式を期待しているだけです2 + 2)。

コンストラクターの引数が増えました

確かに1つの議論はいいですが、2つはどうですか?コンストラクタに引数が1つしかないことはありません。2つの引数を取る組み込みクラスの1つはstd::string

std::string hundred_dots(100, '.');

これはすべて問題なく機能します(技術的には、と記述した場合、最も厄介な構文解析になりますstd::string wat(int(), char())が、正直に言うと誰がそれを記述しますか?しかし、このコードには厄介な問題があると仮定しましょう。括弧内のすべて。

std::string hundred_dots((100, '.'));

まったくそうではありません。

<stdin>:2:36: error: invalid conversion from char to const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
                 from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^

G ++試行を変換するために、なぜ私はわからないcharconst char *。どちらの方法でも、コンストラクターはtypeの値を1つだけ使用して呼び出されましたchar。typeの1つの引数を持つオーバーロードはないcharため、コンパイラーは混乱します。あなたは尋ねるかもしれません-なぜ引数はchar型ですか?

(100, '.')

はい、,ここにコンマ演算子があります。コンマ演算子は2つの引数を取り、右側の引数を指定します。実用的ではありませんが、私の説明で知っておくべきことです。

代わりに、最も厄介な解析を解決するには、次のコードが必要です。

std::string hundred_dots((100), ('.'));

引数は括弧内にあり、式全体ではありません。実際、C ++機能を使用するには、Cの文法から少し外れるだけで十分なので、式の1つだけを括弧で囲む必要があります。物事は議論のゼロのポイントに私たちをもたらします。

コンストラクターの引数がゼロ

eighty_four私の説明でその機能に気づいたかもしれません。

int eighty_four();

はい、これは最も厄介な解析の影響も受けます。これは有効な定義であり、ヘッダーファイルを作成した場合(および作成した場合)に見た可能性が最も高い定義です。括弧を追加しても修正されません。

int eighty_four(());

どうしてこんなことに?まあ、()表現ではありません。C ++では、括弧の間に式を入れる必要があります。auto value = ()C ++で書くことはでき()ません。何も意味しないからです(そして、空のタプル(Pythonを参照)のようにそうしたとしても、ゼロではなく1つの引数になります)。実際には{}、括弧に入れる式がないため、C ++ 11の構文を使用せずに省略構文を使用できず、関数宣言のC文法が常に適用されます。


12

代わりに

A a(());

使用する

A a=A();

32
「より良い回避策」は同等ではありません。int a = int();初期化a0で、int a;葉がa未初期化。正しい回避策は、デフォルトの初期化が必要なことを行う場合、または他のすべての場合に、A a = {};集計に使用するA a;か、A a = A();またはA a = A();一貫して使用することです。C ++ 11では、ちょうど使用A a {};
Richard Smith

6

例の最も内側の括弧は式であり、C ++では、文法はexpressionを、assignment-expressionまたはのexpression後にコンマともう1 つが続くと定義していますassignment-expression(付録A.4-文法の要約/式)。

文法はさらに、assignment-expression他のいくつかのタイプの式の1つとしてを定義しますが、どれも何も(または空白のみ)にすることはできません。

だから、あなたが持つことができない理由A a(())は、文法がそれを許可していないからです。ただし、C ++を作成した人々がこのような空の括弧の使用をある種の特別なケースとして許可しなかった理由に答えることはできません-ある場合、彼らはそのような特別なケースには入れなかったと思います合理的な選択肢。

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