1. ホーム
  2. c++

[解決済み] 非静的データメンバのクラス内初期化とネストしたクラスコンストラクタの使用時のエラー

2023-03-15 05:16:21

質問

以下のコードは非常に些細なもので、うまくコンパイルできるものと思っていました。

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がこのコードでセグメンテーションを行うという事実を除けば ( 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
};

このコードは本当に間違っているのでしょうか、それともコンパイラが間違っているのでしょうか?

どのように解決するのですか?

このコードは本当に間違っているのでしょうか、それともコンパイラが間違っているのでしょうか?

まあ、どちらでもありません。この規格には欠陥があります。 A のイニシャライザーをパースしている間は、完全であると見なされます。 B::i のイニシャライザーをパースしている間は完了とみなされ、その B::B() (のイニシャライザを使用する)。 B::i のイニシャライザーを使用します) は A . これは明らかに循環的です。これを考えてみましょう。

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

これには矛盾があります。 B::B() は暗黙のうちに noexcept とすれば A() は投げない、そして A() は投げません。 B::B() ではない noexcept . この他にも様々なサイクルや矛盾があります。

これは、コア・イシューによって追跡されます 1360 および 1397 . 特に core issue 1397 のこのメモに注意してください。

おそらく、これに対処する最も良い方法は、非静的データメンバーイニシャライザーがそのクラスのデフォルトのコンストラクタを使用することを不正にすることでしょう。

これは、私がこの問題を解決するためにClangに実装したルールの特殊なケースです。Clang のルールは、クラスのデフォルトのコンストラクタは、そのクラスの非静的データ メンバ イニシャライザが解析される前に使用することはできない、というものです。したがって、Clang はここで診断を発行します。

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

... なぜなら Clang はデフォルトの初期化子を解析する前にデフォルトの引数を解析し、このデフォルトの引数には B のデフォルト初期化子がすでにパースされている必要があるからです(暗黙のうちに B::B() ).