2016-04-21 5 views
12

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

#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(); неоднозначный.

+0

Это неоднозначно с помощью * «Если оператор доступа к члену класса, включая неявный« this-> », используется для доступа к нестационарному элементу данных или нестатической функции-члена, ссылка плохо сформирована, если левый операнд (рассматривается как указатель в ".«Операторский случай» не может быть неявно преобразован в указатель на класс именования правильного операнда «*, 11.2p6. Обратите внимание, что класс именования« A », но« D * »не может быть неявно преобразован в' A * '. –

+0

Семантика здесь заключается в том, что вы рассказываете, какую функцию вы хотите вызывать с помощью 'B :: A :: tell'. Вы помогаете компилятору с помощью' D :: tell', то есть с поиском имени. 't указать подобъект, который он должен использовать, - у него будет два варианта: спуститесь по дороге к 'A' над' B' или над 'C', и вы получите сообщение об ошибке. –

+0

Есть две« основные »проверки неоднозначности которые выполняются в контекстах, которые работают с объектами во время выполнения: один в 5.2.5p5, и тот, который укусит вас здесь в 11.2p6. Тот, который в 5.2.5p5 отклоняет 'd.tell()', если вы должны удалить все функции tell кроме класса «A», поскольку класс именования тогда «D», но 'tell' будет прямым членом' A', а 'A' ​​является двусмысленным. Если вы затем скажете' DB :: A :: tell() ', он хорошо сформирован 5.2.5p5 , но плохо сформировался на 11.2p6. Эти проверки дополняют друг друга и важны для правильной работы в системе типов. –

ответ

15

Why is it ambiguous? I explicitly specified that I want Top from Right and not from 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(); 
+0

Тогда почему он работает, если я использую 'Right :: print()' вместо 'Right :: Top :: печать() '? Ни один из них не работает с указателем-членом, но 'b.Right :: print()' будет работать. (конечно, я удалил 'print()' в классе 'Right'). – PcAF

+1

@PcAF Поскольку преобразование с 'b' в' Right' однозначно. Существует только один подобъект типа 'Right'. – Barry

+0

Благодарим за отзыв. Если преобразование из 'b' в' Right' однозначно, почему это: 'auto print = & Bottom :: Right :: print;' '(b. * Print)()' дает ошибку двусмысленной базы (справа - пустой класс)? – PcAF

4

Прицел левоассоциативной (хотя это не позволяет круглые скобки).

Так в то время как вы хотите обратиться к A::tell внутри B, ид-выражение относится к tell внутри B::A, который просто A, который является неоднозначным.

Обходной путь заключается в том, чтобы сначала отличить к однозначному основанию B, а затем снова добавить в A.

Language-адвокатская практика:

[basic.lookup.qual]/1 говорит,

The name of a class or namespace member or enumerator can be referred to after the :: scope resolution operator applied to a nested-name-specifier that denotes its class, namespace, or enumeration.

Соответствующая грамматика вложенным имя спецификатора есть

nested-name-specifier:

    type-name::

    nested-name-specifieridentifier::

Итак, первый вложенный имя-спецификатор - B:: и A - поиск в пределах Это. Затем B::A - это спецификатор вложенных имен, обозначающий A и tell.

По-видимому, MSVC принимает пример. Вероятно, он имеет нестандартное расширение, чтобы устранить двусмысленность, обратившись через такие спецификаторы.

+0

Пожалуйста, процитируйте соответствующий стандартизированный текст, иначе это будет предположение. –

+0

@ Cheersandhth.-Alf Тег [language-lawyer] был добавлен после того, как я опубликовал. Я посмотрю, могу ли я поднять хорошее объяснение, но заметьте, что просто невозможно выполнить поиск справа налево. – Potatoswatter

+0

@Potatoswatter +1, и см. Мои комментарии к вопросу о ссылках. –