非静的データメンバーとネストされたクラスコンストラクターのクラス内初期化を使用するときのエラー


90

次のコードは非常に些細なものであり、正常にコンパイルされるはずです。

struct A
{
    struct B
    {
        int i = 0;
    };

    B b;

    A(const B& _b = B())
        : b(_b)
    {}
};

このコードをg ++バージョン4.7.2、4.8.1、clang ++ 3.2および3.3でテストしました。このコードでg ++ 4.7.2 segfaultが発生するという事実(http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57770)を除いて、テスト済みの他のコンパイラーは、あまり説明のないエラーメッセージを出力します。

g ++ 4.8.1:

test.cpp: In constructor constexpr A::B::B()’:
test.cpp:3:12: error: constructor required before non-static data member for A::B::i has been parsed
     struct B
            ^
test.cpp: At global scope:
test.cpp:11:23: note: synthesized method constexpr A::B::B()’ first required here 
     A(const B& _b = B())
                       ^

clang ++ 3.2および3.3:

test.cpp:11:21: error: defaulted default constructor of 'B' cannot be used by non-static data member initializer which appears before end of class definition
    A(const B& _b = B())
                    ^

このコードをコンパイル可能にすることは可能で、違いはないようです。2つのオプションがあります。

struct B
{
    int i = 0;
    B(){} // using B()=default; works only for clang++
};

または

struct B
{
    int i;
    B() : i(0) {} // classic c++98 initialization
};

このコードは本当に間違っていますか、それともコンパイラが間違っていますか?


3
私のG ++ 4.7.3はinternal compiler error: Segmentation faultこのコードに次のように言っています...
Fred Foo

2
(エラーC2864: 'A :: B :: i':静的const整数データメンバーのみがクラス内で初期化できます)とVC2010は述べています。その出力はg ++と一致します。Clangもそれを言っていますが、あまり意味がありません。でint i = 0ない限り、構造体の変数をデフォルトにすることはできませんstatic const int i = 0
Chris Cooper

@Borgleader:ちなみに、式B()をコンストラクタへの関数呼び出しと考えたくはありません。コンストラクタを直接「呼び出す」ことはありません。これは、一時的なB... を作成する特別な構文と考えてください。コンストラクターは、そのプロセスの一部として呼び出され、その後に続くメカニズム内に深く入ります。
2013

2
うーん、コンストラクタを追加するBと、でこれが機能するようgcc 4.7です。
Shafik Yaghmour 2013

7
興味深いことに、Aのコンストラクターの定義をAの外に移動すると、それが機能するようになります(g ++ 4.7)。「デフォルトのデフォルトコンストラクタは使用できません...クラス定義の終了前に」というチャイム。
moonshadow 2013

回答:


84

このコードは本当に間違っていますか、それともコンパイラが間違っていますか?

まあ、どちらも。標準欠陥た-それはその両方言うAために初期の解析中に完了したと見なされB::i、それはB::B()(のための初期化を使用するB::i)の定義に使用することができますA。それは明らかに循環的です。このことを考慮:

struct A {
  struct B {
    int i = (A(), 0);
  };
  A() noexcept(!noexcept(B()));
};

これには矛盾があります。つまり、B::B()暗黙的にnoexceptiff A()がスローA()せず、スローしない場合B::B()はです。この領域には、他にも多くのサイクルと矛盾があります。 noexcept

これは、コア問題1360および1397によって追跡されます。特にコア問題1397のこのメモに注意してください:

おそらくこれに対処する最善の方法は、非静的データメンバー初期化子がそのクラスのデフォルトのコンストラクターを使用するように不正な形式にすることです。

これは、この問題を解決するためにClangで実装したルールの特別なケースです。Clangの規則では、クラスのデフォルトでないデフォルトコンストラクターは、そのクラスの非静的データメンバー初期化子が解析されるまで使用できません。したがって、Clangはここで診断を発行します。

    A(const B& _b = B())
                    ^

... Clangはデフォルトの初期化子を解析する前にデフォルトの引数を解析し、このデフォルトの引数は、B(暗黙的に定義するためにB::B())のデフォルトの初期化子がすでに解析されている必要があるためです。


知ってよかった。ただし、実際にはコンストラクターが「非静的データメンバー初期化子によって使用される」わけではないため、エラーメッセージは誤解を招く可能性があります。
アシェプラー2013

この問題の特定の過去の経験のために、または標準(および欠陥のリスト)を注意深く読むことによってこれを知っていますか?また、+ 1。
Cornstalks 2013

この詳細な回答の+1。では、どうすればよいでしょうか?内部クラスに依存する外部クラスメンバーの解析が、内部クラスが完全に形成されるまで遅延される、ある種の「2フェーズクラス解析」。
TemplateRex

4
@ascheplerはい、ここでの診断はあまり良くありません。そのためにllvm.org/PR16550を提出しました。
Richard Smith

@Cornstalks Clangで非静的データメンバーの初期化子を実装しているときにこの問題を発見しました。
Richard Smith

0

多分これが問題です:

§12.15.デフォルトであり、削除済みとして定義されていないデフォルトのコンストラクターは、そのクラス型のオブジェクトを作成するために使用されるとき(3.2)、または最初の宣言後に明示的にデフォルトになるときに暗黙的に定義されます。

したがって、デフォルトのコンストラクターは最初に検索されたときに生成されますが、Aが完全に定義されておらず、A内のBが見つからないため、検索は失敗します。


「したがって」についてはわかりません。明らかにB b問題ではなく、明示的なメソッド/で明示的に宣言されたコンストラクタを見つけることBは問題ではありません。したがって、この理由でコードを違法であると宣言する前に、ここでは検索が別の方法で行われる必要がある理由の定義を確認して、この1つのケースだけで「B内部Aが見つからない」ようにするとよいでしょう。
moonshadow 2013

ネストされたクラス内を含め、クラス定義がクラス内初期化中に完全であると見なされるという単語が標準で見つかりました。参照は関連性がないようであったので、参照を記録することはありませんでした。
マークB

@moonshadow:このステートメントは、odr-を使用すると暗黙的にデフォルトのコンストラクターが定義されることを示しています。最初の宣言の後に明示的に定義されます。また、B bはコンストラクタを呼び出さず、AのコンストラクタはBのコンストラクタを呼び出します
fscan

このようなことが問題だった場合は削除する場合、コードはまだ無効になります=0からi = 0;。しかし、それがなければ=0、コードは有効でありB()、の定義内での使用について不満を言う単一のコンパイラーは見つかりませんA
アシェプラー2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.