Алмаз смерти и оператор разрешения масштаба (С++)
У меня есть этот код (проблема с алмазами):
#include <iostream>
using namespace std;
struct Top
{
void print() { cout << "Top::print()" << endl; }
};
struct Right : Top
{
void print() { cout << "Right::print()" << endl; }
};
struct Left : Top
{
void print() { cout << "Left::print()" << endl; }
};
struct Bottom: Right, Left{};
int main()
{
Bottom b;
b.Right::Top::print();
}
Я хочу вызвать print()
в классе Top
.
Когда я пытаюсь скомпилировать его, я получаю ошибку: 'Top' is an ambiguous base of 'Bottom'
в этой строке: b.Right::Top::print();
Почему это неоднозначно? Я явно указал, что хочу Top
от Right
, а не от Left
.
Я не хочу знать, КАК это сделать, да, это можно сделать со ссылками, виртуальным наследованием и т.д. Я просто хочу знать, почему b.Right::Top::print();
неоднозначно.
Ответы
Ответ 1
Почему это неоднозначно? Я явно указал, что хочу Top
от Right
, а не от Left
.
Это было ваше намерение, но это не то, что на самом деле происходит. Right::Top::print()
явно называет функцию-член, которую вы хотите вызвать, которая &Top::print
. Но он не указывает, на каком подобъекте b
мы вызываем эту функцию-член. Ваш код эквивалентен концептуально:
auto print = &Bottom::Right::Top::print; // ok
(b.*print)(); // error
Часть, которая выбирает print
, недвусмысленна. Это неявное преобразование из b
в Top
, которое неоднозначно. Вы должны явно устранить, в каком направлении вы входите, делая что-то вроде:
static_cast<Right&>(b).Top::print();
Ответ 2
Оператор разрешающей области является лево-ассоциативным (хотя он не позволяет скобки).
Итак, если вы хотите ссылаться на A::tell
внутри B
, выражение id ссылается на tell
внутри B::A
, что просто A
, что неоднозначно.
Обходной путь состоит в том, чтобы сначала применить к однозначной базе B
, а затем снова применить к A
.
Язык-адвокатская практика:
[basic.lookup.qual]/1 говорит,
Имя члена класса или пространства имен или перечислителя можно указать после оператора разрешения области видимости ::
, примененного к вложенному имени-спецификатору, который обозначает его класс, пространство имен или перечисление.
Соответствующая грамматика для вложенного имени-спецификатора,
вложен имя спецификатор:
type-name ::
Идентификатор идентификатора вложенного имени ::
Итак, первый вложенный имя-спецификатор B::
и A
просматривается внутри него. Затем B::A
представляет собой спецификатор вложенных имен, обозначающий A
и tell
, который просматривается внутри него.
По-видимому, MSVC принимает пример. Вероятно, он имеет нестандартное расширение, чтобы устранить двусмысленность, обратившись через такие спецификаторы.
Ответ 3
На самом деле, предоставление кода работает нормально, как я пробовал в Visual Studio 2019. Есть два способа решения проблемы Diamond; - Использование оператора разрешения Scope - Наследовать базовый класс как виртуальный
Вызов функции print с помощью b.Right::Top::print()
должен быть выполнен без ошибок. Но есть еще два объекта вашего базового класса (Top), ссылающиеся на ваш класс Bottom.
Вы можете найти дополнительные детали здесь