Почему объявление using вводит не тот метод базового класса, который указан

169
14 марта 2019, 06:40
#include <iostream>
#include <string>
class A {
public:
virtual void print()const {
std::cout << "from A" << std::endl;
}
};
class B : public A {
public:
void print()const override{
std::cout << "from B" << std::endl;
}
};
class C : public A {
public:
void print()const override{
std::cout << "from C" << std::endl;
}
};
class D :public B, public C {
public:
using C::print;
};
int main()
{
D d{};
d.print(); //from B
return 0;
}

И почему нельзя использовать объявление using при виртуальном наследовании?

Answer 1

Если речь идет о Visual Studio, то это, по-видимому, баг компилятора.

Кодогенератор Visual Studio по какой-то причине принимает решение в такой ситуации реализовать вызов d.print() как полноценный виртуальный вызов через таблицу виртуальных функций класса D. (В этом, кстати, пока еще нет никакой ошибки.) Но для разрешения этого виртуального вызова он безусловно берет первую таблицу в объекте типа D (из первой базы в списке баз), по каковой причине такие виртуальные вызовы все время попадают в B::print. Если поменять местами базы в объявлении класса D, то вызываться все время будет C::print.

Если сделать функцию print невиртуальной, то проблема пропадает. Если явно сделать вызов невиртуальным, т.е. вместо d.print() вызывать d.D::print(), то проблема пропадает.

Это вообще-то интересная тема. Вызов d.print() - это с точки зрения языка виртуальный вызов и разрешаться он должен на основе динамического типа объекта d. Т.е. final overrider должен быть взят из класса D. А кто является final overrider в D? (Точнее, из какой цепочки баз он должен выбираться: A->B->D или A->С->D?). Using-declaration не может быть использована для разрешения этого вопроса. В стандарте есть показательный пример на эту тему

struct A {
  virtual void f();
};
struct B : virtual A {
  virtual void f();
};
struct C : B , virtual A {
  using A::f;
};
void foo() {
  C c;
  c.f();              // calls B​::​f, the final overrider
  c.C::f();           // calls A​::​f because of the using-declaration
}

Обратите внимание - c.f() должен вызывать B::f() несмотря на присутствие using A::f в C.

По-видимому в нашем примере все начинается c name lookup, который должен использовать using-declaration using C::print и локализовать рассмотрение в ветке наследования A->C, где final overrider однозначен.

READ ALSO
Инициализация двух массивов

Инициализация двух массивов

Почему при выполнении кода оба массива постоянно инициализируются (или, по крайней мере, выводятся на экран) одинаковыми числами? https://wwwonlinegdb

183
Как записать несколько чисел в одном рядке в массив [закрыт]

Как записать несколько чисел в одном рядке в массив [закрыт]

Дана задача, в которой надо ввести числа в массив, при этом, вводить их надо через пробел

160
Ошибка при подключении к БД jdbc

Ошибка при подключении к БД jdbc

Вот возникает ошибка при попытке подключения: commysql

170
Создание объектов JPanel с помощью Map

Создание объектов JPanel с помощью Map

подскажите, имеется следующий класс

155