Неоднозначность при множественном наследовании
Отвечал на вопрос об одинаковых именах при наследовании. Написал такой код (см. ниже). Visual C++ его компилирует и выполняет на ура. Но масса прочих компиляторов ругается на
d.C::A::x = 2;
d.B::A::x = 3;
и вызовы out(), утверждая, что тут неоднозначность в выборе A::x. Откровенно говоря, я ее не вижу; вывод у VC++ соответствует ожидаемому.
Кто-то может доказать правоту того или иного компилятора явной ссылкой на пункт стандарта? Откуда тут неоднозначность — при явном указании пути через B или C? Как ее устранить в таких компиляторах, кто недоволен?
#include <iostream>
using namespace std;
struct A
{
int x;
void out() { cout << x << endl; }
};
struct B: public A
{
int x;
void out() { cout << x << endl; }
};
struct C: public A
{
int x;
void out() { cout << x << endl; }
};
struct D: public B, public C
{
int x;
void out() { cout << x << endl; }
};
int main()
{
D d;
d.C::x = 0;
d.B::x = 1;
d.C::A::x = 2; // неоднозначность в не-MSVC
d.B::A::x = 3; // неоднозначность в не-MSVC
d.B::A::out(); // неоднозначность в не-MSVC
d.C::A::out(); // неоднозначность в не-MSVC
d.B::out();
d.C::out();
}
Ответы (1 шт):
Когда вы пишете B::A (причем неважно, что это после . - работает везде одинаково) - это то же самое, что просто A (если B - наследник A), поэтому d.C::A::x превращается в d.A::x - а это явно неоднозначность и ошибка.
Почему
B::A- то же самое, чтоA?Из-за injected-class-name. Injected-class-name - это что-то вроде скрытого
using ИмяКласса = ИмяКласса;, который создается автоматически в каждом классе, и наследуется точно так же, как и все остальные члены класса.То есть
Bнаследует изAчто-то вродеusing A = A;, поэтомуB::AозначаетA.(Из этого также должно следовать, что
A::AозначаетA, но вот это уже работает не везде:struct B : A::A {};работает, аA::A a;уже не работает - из-за[class.qual]/1(во втором случае компилятор считаетA::Aименем конструктораA- как при наследовании конструктора -using A::A;).)Как это
::справа от.работает по тем же правилам, что и в любом другом месте? Ведь казалось бы,A::x(гдеx- нестатический член) работает только после.!А вот и нет,
A::xдля нестатических членов работает много где. Везде, где результат выражения не вычисляется, например вsizeofиdecltype:#include <type_traits> struct A {int x;}; static_assert(sizeof(A::x) == sizeof(int)); static_assert(std::is_same_v<decltype(A::x), int>);А как вам такое:
static_assert(sizeof(A::x + 42) == sizeof(int)); static_assert(std::is_same_v<decltype(A::x + 42), int>);
В целом, в стандарте нигде не описывается поведение :: конкретно справа от . Он работает везде одинаково. То есть нет никакого процесса "последовательного захода в родителей родителей"; единственная причина, почему у вас получилось указать нескольких родителей после в d.C::A::x - это injected-class-name.
Явно про ваш случай говорит [expr.ref]/7:
[в выражении вида
E1.E2...]If
E2is a non-static member, the program is ill-formed if the class of whichE2is directly a member is an ambiguous base ([class.member.lookup]) of the naming class ([class.access.base]) ofE2.
Здесь "naming class" ("именующий/называющий/обращающийся класс"?) - это тип объекта слева от точки.
Есть другой способ обратиться к полю родителя - кастануть объект в ссылку на родителя:
static_cast<A &>(static_cast<C &>(d)).x
Или так:
static_cast<C &>(d).A::x