Алмаз смерти и оператор разрешения масштаба (С++)

У меня есть этот код (проблема с алмазами):

#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.

Вы можете найти дополнительные детали здесь