Ошибка списка инициализации в gcc?

Рассмотрим следующий код, где B - виртуальный базовый класс, унаследованный от D до B1 и B2:

#include <iostream>

class B
{
protected:
    int x;

protected:

    B(int x) : x{x}{std::cout << x << std::endl;}
};

class B1 : virtual public B
{
protected:

    B1() : B(0){}
};

class B2 : virtual public B
{
protected:

    B2() : B(10){}
};

class D : public B1, public B2
{
public:

    D() : B(99), B1(), B2() {}
    void print() {std::cout << "Final: " << x << std::endl;}
};

int main() {
    D d;
    d.print();
    return 0;
}

См. рабочий пример здесь. Я использую выходы в конструкторе B и после того, как D был полностью сконструирован, чтобы отслеживать, что происходит. Все работает нормально, когда я скомпилировал приведенный выше пример с g++ - 4.8.1. Он печатает

99
Final: 99

потому что конструктор B вызывается один раз из самого производного класса (D), а также определяет окончательное значение x.

Теперь появляется странная часть: если я меняю строку

D() : B(99), B1(), B2() {}

к новому равномерному синтаксису инициализации, то есть

D() : B{99}, B1{}, B2{} {}

странные вещи случаются. Во-первых, он больше не компилируется с ошибкой

prog.cpp: In constructor ‘D::D()’:
prog.cpp:17:5: error: ‘B1::B1()’ is protected
     B1() : B(0){}
     ^
prog.cpp:31:27: error: within this context
     D() : B{99}, B1{}, B2{} {}

(и то же самое для B2, см. здесь), что не имеет смысла, потому что я использую его в производном классе, поэтому protected должно быть в порядке. Если я исправлю это и сделаю конструкторы B1 и B2 общедоступными, а не защищенными, все будет полностью испорчено (см. здесь), так как выход становится

99
0
10
Final: 10

Итак, на самом деле части конструкторов B1 и B2, которые инициализируют B, все еще выполняются и даже изменяют значение x. Это не должно иметь место для виртуального наследования. И помните, единственное, что я изменил, - это

  • public вместо защищенных конструкторов в B1 и B2
  • используйте classname{} синтаксис в списке инициализации членов D вместо classname().

Я не могу поверить, что такая базовая вещь идет не так, как в gcc. Но я протестировал его с помощью clang на моей локальной машине, и там все три случая скомпилируются и запускаются по назначению (т.е. Как первый пример выше). Если это не ошибка, может кто-то, пожалуйста, указать мне, чего я не вижу?

EDIT: мой первый поиск каким-то образом не вызвал его, но теперь я нашел этот другой вопрос, указав хотя бы защищенную/общедоступную ошибку. Однако это было gcc-4.7, поэтому я ожидал, что это будет рассмотрено в gcc-4.8. Итак, должен ли я заключить, что списки инициализаторов просто испорчены в gcc!?

Ответы

Ответ 1

Я не знаю, слишком ли поздно ответить на этот вопрос, но ваш код компилируется в GCC 4.9.2!

~$g++ -std=c++11 test.cpp 
~$./a.out 
99
Final: 99

~$gcc --version
gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Ответ 2

Что касается нескольких вызовов конструктора виртуального базового класса: я мог бы воспроизвести проблему со следующим кодом (с GCC 5.1.0).

#include <iostream>

struct V {
    V(){std::cout << "V()\n";}
};

struct A : virtual V {
    A() : V{} {std::cout << "A()\n";}
};

struct B : A {
    B(): V{}, A{} {std::cout << "B()\n";}
};

int main(int argc, char **argv) {
    B b{};
}

В результате получается следующий результат:

V()
V()
A()
B()

Я не думаю, что это верно, учитывая стандарт С++:

[class.base.init]/7

... Инициализация, выполняемая каждым mem-инициализатором, представляет собой полное выражение. Любое выражение в mem-инициализаторе оценивается как часть полного выражения, которое выполняет инициализацию. Мем-инициализатор где идентификатор mem-initializer обозначает виртуальный базовый класс, игнорируется во время выполнения конструктора любого класса, который не является самым производным классом.

Когда вызов конструктора A изменяется для использования скобок вместо скобок, результирующий исполняемый файл работает как ожидалось и только вызывает только V().

Я создал отчет об ошибке для GCC по этой проблеме: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70818

Изменить: я пропустил, что уже был отчет об ошибке: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55922