Почему унифицированная инициализация в С++ 11 ведет себя странно с виртуальными базовыми классами?

В настоящее время я изучаю функции Inheritance на С++ и хочу проверить недавно изученную концепцию виртуальных базовых классов. Я попробовал следующий простой код:

#include <iostream>

using namespace std;

class A
{
private:
    int m_value;
    string m_caller;
public:
    A(int p_value, string p_caller) : m_value{p_value}, m_caller{p_caller}
    {
        cout<<"Instantiating A via "<<m_caller<<endl;
    }
};

class B : virtual public A
{
private:
    int m_value;
public:
    B(int p_value1,int p_value2) : A{p_value1,"B"}, m_value{p_value2}
    {
        cout<<"Instantiating B."<<endl;
    }
};

class C : public B
{
public:
    C(int p_value1,int p_value2) : A{p_value1,"C"}, B(p_value1, p_value2)
    {
        cout<<"Instantiating C."<<endl;
    }
};

int main()
{
    C c1(1,2);
    return 0;
}

Обратите внимание на B(p_value1, p_value2) в конструкторе класса C. Это дало мне желаемый результат:

Instantiating A via C
Instantiating B.
Instantiating C.

Но, как только я изменил его на B{p_value1, p_value2}, я получил следующий вывод:

Instantiating A via C
Instantiating A via B
Instantiating B.
Instantiating C.

Я попытался найти ответ, но все ответы, которые я получил, цитировали некоторые стандарты С++. Будучи новичком в ООП, я ищу более простое объяснение этого поведения. Большое спасибо!

P.S. Я использую C:: B в Windows с компилятором g++ 4.8.1.

Ответы

Ответ 1

Это ошибка компилятора в g++.

В разделе С++ 14 (N4140) [dcl.init.list] определение инициализации списка (отредактировано для краткости):

Список-инициализация объекта или ссылки типа T определяется следующим образом:

  • Если T является агрегатом, выполняется агрегатная инициализация
  • В противном случае, если в списке инициализаторов нет элементов, а T - это тип класса со стандартным конструктором, объект инициализируется значением.
  • В противном случае, если T является специализацией std:: initializer_list, [...]
  • В противном случае, если T - тип класса, рассматриваются конструкторы. Соответствующие конструкторы перечислены, и лучший выбирается с помощью разрешения перегрузки. Если для преобразования любого из аргументов требуется сужение преобразования, программа плохо сформирована.
  • [...]

Первые 3 точки не применяются: B не является агрегатом (агрегат не может иметь базовые классы), в списке инициализаторов есть элементы, B не является специализацией std::initializer_list.

Четвертая точка применяется, потому что разрешение перегрузки соответствует B{p_value1, p_value2} конструктору B(int, int) в соответствии с [over.match.list]/1.2:

Если не существует жизнеспособного конструктора-списка инициализаторов, разрешение перегрузки выполняется снова, где кандидатные функции являются всеми конструкторами класса T, а список аргументов состоит из элементов списка инициализаторов.

Из последней цитаты следует, что B(whatever) и B{whatever} должны вести себя одинаково.