web-dev-qa-db-ja.com

なぜこれがデフォルトのコンストラクタなしでコンパイルされないのですか?

私がすることができます:

#include <iostream>

int counter;

int main()
{
    struct Boo
    {
        Boo(int num)
        {
            ++counter;
            if (Rand() % num < 7) Boo(8);
        }
    };

    Boo(8);

    return 0;
}

これはうまくコンパイルされます、私の反対結果は 21 です。ただし、整数リテラルの代わりにコンストラクタ引数を渡してBooオブジェクトを作成しようとすると、コンパイルエラーが発生します。

#include <iostream>

int counter;

int main()
{
    struct Boo
    {
        Boo(int num)
        {
            ++counter;
            if (Rand() % num < 7) Boo(num); // No default constructor 
                                            // exists for Boo
        }
    };

    Boo(8);

    return 0;
}

2番目の例ではデフォルトのコンストラクタがどのように呼び出されますが、最初の例では呼び出されませんか。これはVisual Studio 2017で発生するエラーです。

オンラインC++コンパイラonlineGDBでは、次のようなエラーが表示されます。

error: no matching function for call to ‘main()::Boo::Boo()’
    if (Rand() % num < 7) Boo(num);

                           ^
note:   candidate expects 1 argument, 0 provided
71
Zebrafish

Clangは次の警告メッセージを表示します。

<source>:12:16: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]
            Boo(num); // No default constructor 
               ^~~~~

これは、最も厄介な解析の問題です。 Booはクラス型の名前であり、numは型名ではないため、Boo(num);Booを含むnum型の一時構造のいずれかです。 _ Booのコンストラクターへの引数、または宣言Boo num;宣言子num(宣言子は常に持っている可能性があります)の周りに余分な括弧がある場合両方が有効な解釈である場合、規格ではコンパイラーが宣言を引き受けることを要求しています。

宣言として解析される場合、Boo num;はデフォルトのコンストラクター(引数なしのコンストラクター)を呼び出します。これは、ユーザーによってまたは暗黙的に宣言されません(別のコンストラクターを宣言したため)。したがって、プログラムの形式は不適切です。

8は変数の識別子(宣言子ID)にできないため、これはBoo(8);の問題ではありません。したがって、8Booテンポラリを作成する呼び出しとして解析されます。コンストラクターの引数として、デフォルトのコンストラクター(宣言されていない)を呼び出すのではなく、手動で定義したコンストラクターを呼び出します。

Boo(num);の代わりにBoo{num};を使用して(宣言子の周りの{}は許可されないため)、一時変数を名前付き変数にすることで、これを宣言から明確にすることができます。 Boo temp(num);、または別の式のオペランドとして配置することにより、たとえば(Boo(num));(void)Boo(num);など.

デフォルトのコンストラクターが使用可能な場合、宣言は整形式になることに注意してください。これは、関数のブロックスコープではなくifの分岐ブロックスコープ内にあり、関数のパラメーターリスト内のnumを単純にシャドウするからです。

いずれにせよ、通常の(メンバー)関数呼び出しであるはずの何かに対して一時的なオブジェクト作成を誤用することは良い考えとは思えません。

カッコ内に単一の非タイプ名を持つこの特定のタイプのほとんどのベクシング解析は、意図が一時的なものを作成してすぐに破棄するため、または初期化子として直接使用される一時的なものを作成する場合などにのみ発生します。 Boo boo(Boo(num));(実際に関数booを宣言し、numという名前のパラメーターをBoo型で取り、Booを返します)。

通常、一時的なものをすぐに破棄することは意図されておらず、イニシャライザのケースは、ブレースの初期化または二重の括弧(Boo boo{Boo(num)}Boo boo(Boo{num})またはBoo boo((Boo(num)));を使用して回避できますが、Boo boo(Boo((num)));は使用できません)。

Booが型名でなかった場合、宣言ではなく、問題は発生しません。

また、Boo(8);Boo型の新しいテンポラリを、クラススコープとコンストラクター定義内でも作成していることを強調したいと思います。誤って考えるかもしれませんが、通常の非静的メンバー関数のように、呼び出し元のthisポインターを使用してコンストラクターを呼び出すことはありません。コンストラクター本体内でこの方法で別のコンストラクターを呼び出すことはできません。これは、コンストラクタのメンバー初期化子リストでのみ可能です。


[stmt.ambig]/ のため、コンストラクターが欠落しているために宣言の形式が正しくない場合でも、これが発生します。

明確化は純粋に構文的なものです。つまり、そのようなステートメントで発生する名前の意味は、それらがタイプ名であるかどうかを超えて、一般的に曖昧性解消で使用されたり変更されたりすることはありません。

[...]

曖昧性解消は構文解析に先行し、宣言として曖昧性が解消されたステートメントは不正な形式の宣言である場合があります。


編集で修正済み:関数パラメーターとは異なるスコープにある問題の宣言を見落としていたため、コンストラクターが使用可能な場合、宣言は整形式です。これは、いかなる場合でも曖昧性解消時に考慮されません。また、いくつかの詳細を拡大しました。

87
user10605163

これは最も厄介な構文解析として知られています(この用語はEffective STLでScott Meyersによって使用されました)

Boo(num)はコンストラクタを呼び出したり、一時的なものを作成したりしません。 Clangは(正しい名前W vexing-parseを使っても)見て良い警告を出します:

<source>:12:38: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]

したがって、コンパイラが見るものは以下と同等です。

Boo num;

これは可変宣言です。一時Booオブジェクトを作成したい場合でも、名前numでBoo変数を宣言しました。これにはデフォルトのコンストラクターが必要です。 c ++標準では、これが変数宣言であると見なすことをあなたの場合のコンパイラに要求しています。 「ねえ、numはintです。しないでください」しかし、 標準では :となっています。

曖昧性解消は純粋に構文上のものです。つまり、そのようなステートメントで使用される名前の意味は、それらがタイプ名であるかどうかを超えて、一般に使用されたり曖昧さ除去によって変更されたりすることはありません。クラステンプレートは、修飾名が型名かどうかを判断するために必要に応じてインスタンス化されます。曖昧さ除去は構文解析の前に行われ、宣言として曖昧さ除去されたステートメントは不適切な形式の宣言である可能性があります。解析中に、テンプレートパラメータ内の名前が試行解析中にバインドされるものとは異なる方法でバインドされている場合、プログラムは不正な形式です。診断は不要です。 [注:これは、名前が宣言の前半で宣言されている場合にのみ発生します。 - エンドノート]

だからこれ以上の方法はありません。

Boo(8)の場合、パーサはこれが宣言ではないことを確認でき(8は有効な識別子名ではない)、コンストラクタBoo(int)を呼び出すのでこれは起こり得ません。

ちなみに: 括弧で囲むと曖昧さを解消できます。

 if (Rand() % num < 7)  (Boo(num));

それとも私の考えでは、新しい統一初期化構文を使用する

if (Rand() % num < 7)  Boo{num};

これでコンパイルされます - こちらここ を参照してください。

33
user32434999

これがクランの警告です

truct_init.cpp:11:11:エラー:異なる型の 'num'の再定義: 'Boo'と 'int'

1
sbh