Почему два предложения с использованием одного и того же типа рассматриваются как неоднозначные в gcc

32

У меня есть два базовых класса с использованием предложений

 class MultiCmdQueueCallback {
  using NetworkPacket  = Networking::NetworkPacket;
  ....
 }


 class PlcMsgFactoryImplCallback {
   using NetworkPacket = Networking::NetworkPacket;
  ....
 }

Затем я объявляю класс

class PlcNetwork : 
  public RouterCallback, 
  public PlcMsgFactoryImplCallback, 
  public MultiCmdQueueCallback {
  private:
    void sendNetworkPacket(const NetworkPacket &pdu);
}

затем компилятор помечает ссылку на ошибку «NetworkPacket» неоднозначно «sendNetworkPacket (NetworkPacket & ...»

Теперь оба «использующих предложения» относятся к одному базовому классу Networking: NetworkPacket

и фактически, если я заменю объявление метода на:

 void sendNetworkPacket(const Networking::NetworkPacket &pdu);

он компилируется нормально.

Почему компилятор рассматривает каждое предложение using как отдельный тип, даже если они оба указывают на один и тот же базовый тип. Это предписано стандартом или у нас есть ошибка компилятора?

Эндрю Гёдхарт
источник
Кажется, компилятор недостаточно умен
idris
Дело в том, что компилятор на данный момент просто знает, что существует три NetworkPacket- в MultiCmdQueueCallback, в PlcMsgFactoryImplCallback, в сети. Какой из них использовать, следует указать. И я не думаю, что virtualздесь может помочь помощь.
theWiseBro
@idris: вместо этого вы имели в виду, что стандарт недостаточно разрешительный. составители имеют право следовать стандарту.
Jarod42
@ Jarod42 Ниже ответьте «синоним для типа, обозначенного type-id», поэтому, если у них одинаковый type-id, можно разрешить использовать оба. будь то стандарт или компилятор, просто кажется, что кто-то на самом деле не достаточно умен.
Идрис
одна из проблем множественного наследования
eagle275

Ответы:

28

Перед просмотром псевдонима результирующий тип (и доступность)

мы смотрим на имена

и действительно,

NetworkPacket возможно

  • MultiCmdQueueCallback::NetworkPacket
  • или PlcMsgFactoryImplCallback::NetworkPacket

То, на что они оба указывают, не Networking::NetworkPacketимеет значения.

Мы делаем разрешение имени, что приводит к неоднозначности.

Jarod42
источник
На самом деле это только частично верно, если я добавлю использование в PlcNetwork: | using NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Я получаю ошибку компилятора, потому что предыдущее условие использования было закрытым.
Эндрю Гедхарт
@ AndrewGoedhart Не противоречие. Поиск имени начинается сначала в своем классе. Поскольку компилятор находит там уже уникальное имя, он удовлетворен.
Аконкагуа
Моя проблема здесь заключается в том, почему имя распространяется из предложения частного именования в базовом классе. Если я удаляю одно из закрытых объявлений, т. Е. Один из базовых классов имеет предложение private using, а другое нет, ошибка изменится на «Сетевой пакет не называет тип»
Эндрю Гоедхарт,
1
@AndrewGoedhart Поиск имени (очевидно) не учитывает доступность. Вы получите ту же ошибку, если сделаете одно общедоступным, а другое частным. Это первая обнаруженная ошибка, поэтому будет напечатана первая ошибка. Если вы удалите один псевдоним, проблема двусмысленности исчезнет, ​​но проблема недоступности останется, поэтому вы получите следующую ошибку. Кстати, не очень хорошее сообщение об ошибке (? MSVC еще раз), GCC более точнее о: error: [...] is private within this context.
Аконкагуа
1
@AndrewGoedhart Примите во внимание следующее: class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); }- не то же самое, но разрешение перегрузки работает одинаково: учитывайте все доступные функции, только после выбора подходящей учитывайте доступность ... В данном случае вы также получаете неоднозначность; если вы измените приватную функцию, чтобы она принимала два символа, она будет выбрана, хотя и частной, и вы столкнетесь с следующей ошибкой компиляции.
Аконкагуа,
14

Вы можете просто устранить неоднозначность, вручную выбрав, какой из них вы хотите использовать.

class PlcNetwork : 
  public RouterCallback, 
  public PlcMsgFactoryImplCallback, 
  public MultiCmdQueueCallback {

using NetworkPacket= PlcMsgFactoryImplCallback::NetworkPacket; // <<< add this line
private:
    void sendNetworkPacket(const NetworkPacket &pdu);

}

Компилятор ищет только определения в базовых классах. Если в обоих базовых классах присутствует один и тот же тип и / или псевдоним, он просто жалуется, что не знает, какой из них использовать. Неважно, будет ли результирующий тип одинаковым или нет.

Компилятор ищет имена только на первом шаге, полностью независимый, если это имя является функцией, типом, псевдонимом, методом или чем-то еще. Если имена неоднозначны, компилятор не предпринимает никаких дальнейших действий! Он просто жалуется с сообщением об ошибке и останавливается. Так что просто разрешите неоднозначность с помощью данного оператора using.

Клаус
источник
Есть некоторые сомнения по поводу формулировки. Если он ищет определения , не будет ли он также учитывать тип? Разве он не ищет только имена (и забывает о том, как они определены)? Некоторая ссылка на стандарт была бы отличной ...
Аконкагуа
Этот последний комментарий объясняет, почему правильно. Замените последний абзац этим комментарием, и я буду голосовать;)
Аконкагуа
Я не могу согласиться - я не автор вопросов ... Извините, если бы я действовал вам на нервы. Просто пытаюсь улучшить ответ, поскольку я чувствовал, что он не отвечал на основной вопрос QA раньше ...
Аконкагуа,
@Aconcagua: Ubs, моя вина :-) Спасибо за улучшение!
Клаус
На самом деле это не работает, потому что оба предложения являются частными. Если я добавлю использование в PlcNetwork: | using NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Я получаю ошибку компилятора, потому что предыдущее условие использования было закрытым. Кстати, если я сделаю один базовый класс, использующий предложение, общедоступным, а другой - закрытым, я все равно получу ошибку неоднозначности. Я получаю ошибки неоднозначности на методы, которые не определены в базовом классе.
Эндрю Гоедхарт
8

Из документов :

Объявление псевдонима типа вводит имя, которое может использоваться как синоним для типа, обозначенного как type-id. Он не вводит новый тип и не может изменить значение имени существующего типа.

Хотя эти два usingпредложения представляют один и тот же тип, у компилятора есть два варианта в следующей ситуации:

void sendNetworkPacket(const NetworkPacket &pdu);

Можно выбрать между:

  • MultiCmdQueueCallback::NetworkPacket а также
  • PlcMsgFactoryImplCallback::NetworkPacket

потому что он наследует от обоих MultiCmdQueueCallbackи PlcMsgFactoryImplCallbackбазовых классов. Результатом разрешения имени компилятора является ошибка неоднозначности, которая у вас есть. Чтобы это исправить, вам нужно явно указать компилятору использовать тот или иной код:

void sendNetworkPacket(const MultiCmdQueueCallback::NetworkPacket &pdu);

или

void sendNetworkPacket(const PlcMsgFactoryImplCallback::NetworkPacket &pdu);
Щелкунчик
источник
Честно говоря, я не чувствую себя удовлетворенным ... Они оба являются синонимами одного и того же типа. Я могу легко иметь class C { void f(uint32_t); }; void C::f(unsigned int) { }(при условии совпадения псевдонима). Так почему здесь разница? Они по-прежнему относятся к тому же типу, что подтверждается вашей цитатой (которую я не считаю достаточной для объяснения) ...
Аконкагуа,
@Aconcagua: использование базового типа или псевдонима не имеет значения. Псевдоним никогда не является новым типом. Ваше наблюдение не имеет ничего общего с неоднозначностью, которую вы генерируете, задавая ЖЕ псевдоним в двух базовых классах.
Клаус
1
@Aconcagua Я думаю, что пример, который вы упомянули, не является правильным эквивалентом ситуации из вопроса
NutCracker
Что ж, давайте немного изменим: назовем классы A, B и C и typedef D, тогда вы даже можете сделать следующее: class C : public A, public B { void f(A::D); }; void C::f(B::D) { }- по крайней мере, GCC принимает.
Аконкагуа
Автор вопроса буквально спросил: «Почему компилятор рассматривает каждое предложение using как отдельный тип, даже если они оба указывают на один и тот же базовый тип?» - и я не вижу, как цитата прояснила бы причину , вместо этого она только подтверждает путаницу с QA ... Не хочу сказать, что ответ неправильный , но он не проясняет достаточно в моих глазах ... .
Аконкагуа
2

Есть две ошибки:

  1. Доступ к псевдонимам частного типа
  2. Неоднозначная ссылка на псевдонимы типов

частный частный

Я не вижу проблемы, когда компилятор сначала жалуется на вторую проблему, потому что порядок на самом деле не имеет значения - вы должны исправить обе проблемы, чтобы продолжить.

государственно-общественного

Если вы измените видимость обоих MultiCmdQueueCallback::NetworkPacketи PlcMsgFactoryImplCallback::NetworkPacketлибо на общедоступные, либо на защищенные, то вторая проблема (неоднозначность) очевидна - это два псевдонима разных типов, хотя они имеют один и тот же базовый тип данных. Некоторые могут подумать, что «умный» компилятор может решить эту проблему (конкретный случай), но имейте в виду, что компилятор должен «мыслить в общем» и принимать решения на основе глобальных правил, а не делать исключения для конкретного случая. Представьте себе следующий случай:

class MultiCmdQueueCallback {
    using NetworkPacketID  = size_t;
    // ...
};


class PlcMsgFactoryImplCallback {
    using NetworkPacketID = uint64_t;
    // ...
};

Должен ли компилятор относиться к обоим NetworkPacketIDодинаково? Точно нет. Потому что в 32-битной системе size_tона 32-битная, а uint64_tвсегда 64-битная. Но если мы хотим, чтобы компилятор проверял базовые типы данных, он не мог бы различить их в 64-битной системе.

государственно-частного

Я полагаю, что этот пример не имеет никакого смысла в сценарии использования OP, но так как здесь мы решаем проблемы в целом, давайте рассмотрим, что:

class MultiCmdQueueCallback {
private:
    using NetworkPacket  = Networking::NetworkPacket;
    // ...
};

class PlcMsgFactoryImplCallback {
public:
    using NetworkPacket  = Networking::NetworkPacket;
    // ...
};

Я думаю, что в этом случае компилятор должен рассматривать PlcNetwork::NetworkPacketкак, PlcMsgFactoryImplCallback::NetworkPacketпотому что у него нет других вариантов. Почему он до сих пор отказывается это сделать и обвиняет в неопределенности, для меня загадка.

PooSH
источник
«Почему он до сих пор отказывается это сделать и обвиняет в неопределенности, для меня загадка». В C ++ поиск имени (видимость) предшествует проверке доступа. IIRC, я где-то читал, что обоснование заключается в том, что изменение имени с частного на публичное не должно нарушать существующий код, но я не совсем уверен.
LF