Ошибка при использовании инициализации класса нестатического элемента данных и вложенного конструктора классов

Следующий код довольно тривиален, и я ожидал, что он должен скомпилироваться.

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 segfaults на этот код (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())
                    ^

Возможность компиляции этого кода возможна и, похоже, не имеет значения. Существует два варианта:

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

или

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

Является ли этот код действительно неправильным или неверны компиляторы?

Ответ 1

Является ли этот код действительно неправильным или неверны компиляторы?

Хорошо. Стандарт имеет дефект - он говорит, что A считается полным при анализе инициализатора для B::i и что B::B() (который использует инициализатор для B::i) может использоваться в определении A. Это ясно циклично. Рассмотрим это:

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

Это имеет противоречие: B::B() неявно noexcept iff A() не бросает, а A() не бросает iff B::B() не noexcept. В этой области существует ряд других циклов и противоречий.

Отслеживаются основные проблемы 1360 и 1397. Обратите внимание, в частности, на эту заметку в основной проблеме 1397:

Возможно, наилучшим способом решения этой проблемы было бы сделать его неправильным для нестатического элемента инициализатора элементов данных использовать конструктор по умолчанию своего класса.

Это специальный случай правила, которое я применил в Clang для решения этой проблемы. Правило Clang заключается в том, что стандартный конструктор по умолчанию для класса не может использоваться до того, как инициализаторы элементов нестатического элемента для этого класса будут проанализированы. Следовательно, Clang выдает диагностику здесь:

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

... потому что Clang анализирует аргументы по умолчанию перед тем, как он проанализирует инициализаторы по умолчанию, и этот аргумент по умолчанию потребует, чтобы инициализаторы по умолчанию B были уже проанализированы (чтобы неявно определить B::B()).

Ответ 2

Возможно, это проблема:

§12.1 5. Конструктор по умолчанию, который по умолчанию и не определен как удаленный, неявно определяется, когда он является odr- использовал (3.2) для создания объекта своего типа класса (1.8) или когда он явно дефолт после его первого объявления

Таким образом, конструктор по умолчанию генерируется при первом просмотре, но поиск не сработает, потому что A не полностью определен и B внутри A. поэтому не будет найден.