Dlaczego dwie klauzule używające tego samego typu są postrzegane w gcc jako niejednoznaczne

32

Mam dwie klasy podstawowe z wykorzystaniem klauzul

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


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

Następnie deklaruję klasę

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

kompilator następnie oznacza odniesienie błędu do „NetworkPacket” jest niejednoznaczne „sendNetworkPacket (NetworkPacket i ...”

Teraz obie „klauzule używające” rozwiązują tę samą klasę bazową Networking: NetworkPacket

i w rzeczywistości, jeśli zastąpię deklarację metody:

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

dobrze się kompiluje.

Dlaczego kompilator traktuje każdą klauzulę używającą jako odrębny typ, mimo że oba wskazują na ten sam typ bazowy. Czy jest to wymagane przez standard, czy mamy błąd kompilatora?

Andrew Goedhart
źródło
Wydaje się, że kompilator nie jest wystarczająco sprytny
idris
Chodzi o to, że kompilator w tym momencie po prostu wie, że istnieją trzy NetworkPacket- w MultiCmdQueueCallback, w PlcMsgFactoryImplCallback, w sieci. Który z nich powinien zostać użyty. I nie sądzę, żeby wprowadzenie virtualmogło tu pomóc.
theWiseBro
@idris: zamiast tego miałeś na myśli, że standard nie jest wystarczająco liberalny. kompilatory słusznie przestrzegają standardu.
Jarod42
@ Jarod42 W odpowiedzi poniżej „synonim typu oznaczonego przez typ-id”, więc jeśli mają ten sam identyfikator typu, można użyć obu. czy to w standardzie, czy w kompilatorze, wydaje się, że ktoś nie jest wystarczająco sprytny.
idris
jeden z problemów wielokrotnego dziedziczenia
eagle275

Odpowiedzi:

28

Przed spojrzeniem na typ wynikowy aliasu (i dostępność)

patrzymy na imiona

i rzeczywiście

NetworkPacket może być

  • MultiCmdQueueCallback::NetworkPacket
  • lub PlcMsgFactoryImplCallback::NetworkPacket

Fakt, na który oba wskazują, Networking::NetworkPacketjest nieistotny.

Robimy rozpoznawanie imienia, co powoduje niejasności.

Jarod42
źródło
W rzeczywistości jest to tylko częściowo prawdziwe, jeśli dodam użycie do PlcNetwork: | using NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Otrzymuję błąd kompilatora, ponieważ poprzednia klauzula using jest prywatna.
Andrew Goedhart
@AndrewGoedhart Nie jest to sprzeczność. Wyszukiwanie nazw zaczyna się najpierw od własnej klasy. Ponieważ kompilator znajduje tam już unikalną nazwę, jest spełniony.
Aconcagua,
Mój problem polega na tym, dlaczego nazwa propaguje z prywatnej klauzuli nazewnictwa w klasie bazowej. Jeśli usunę jedną z prywatnych deklaracji, tj. Jedna z klas podstawowych będzie miała prywatną klauzulę using, a druga żadna, błąd zmieni się na „Pakiet sieciowy nie określa typu”
Andrew Goedhart
1
@AndrewGoedhart Wyszukiwanie nazw (oczywiście) nie uwzględnia dostępności. Ten sam błąd pojawia się, jeśli jeden uczynisz publicznym, a drugi prywatnym. To pierwszy wykryty błąd, więc to pierwszy błąd do wydrukowania. Jeśli usuniesz jeden alias, problem dwuznaczności zniknie, ale pozostanie jeden z niedostępności, więc zostanie wydrukowany następny błąd. Nawiasem mówiąc, nie jest to dobry komunikat błędu (? MSVC jeszcze raz), GCC jest bardziej jest bardziej precyzyjne informacje: error: [...] is private within this context.
Aconcagua,
1
@AndrewGoedhart Zastanów się, co następuje: class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); }- nie to samo, ale rozwiązanie przeciążenia działa podobnie: Rozważ wszystkie dostępne funkcje, dopiero po wybraniu odpowiedniej rozważ rozważ dostępność ... W danym przypadku masz również dwuznaczność; jeśli zmienisz funkcję prywatną na akceptowanie dwóch znaków, zostanie ona wybrana, chociaż będzie prywatna - i wystąpi kolejny błąd kompilacji.
Aconcagua
14

Możesz po prostu rozwiązać dwuznaczność, ręcznie wybierając, którego chcesz użyć.

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

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

}

Kompilator szuka tylko definicji w klasach podstawowych. Jeśli ten sam typ i / lub alias jest obecny w obu klasach podstawowych, po prostu narzeka, że ​​nie wie, którego użyć. Nie ma znaczenia, czy wynikowy typ jest taki sam, czy nie.

Kompilator szuka nazw tylko w pierwszym kroku, w pełni niezależny, jeśli jest to funkcja, typ, alias, metoda lub cokolwiek innego. Jeśli nazwy są niejednoznaczne, kompilator nie wykonuje żadnych dalszych działań! Po prostu narzeka na komunikat o błędzie i zatrzymuje się. Po prostu usuń niejednoznaczność z podaną instrukcją using.

Klaus
źródło
Masz wątpliwości co do brzmienia. Jeśli spojrzy na definicje , czy też nie wziąłby pod uwagę typu? Czy nie szukałby tylko samych nazw (i zapomniał o tym, jak zdefiniowano)? Odniesienie do normy byłoby świetne ...
Aconcagua,
Ten ostatni komentarz wyjaśnia, dlaczego poprawnie. Zastąp ostatni akapit tym komentarzem, a ja głosuję;)
Aconcagua,
Nie mogę zaakceptować - nie jestem autorką pytań ... Przepraszam, jeśli mogłem się denerwować. Próbuję tylko poprawić odpowiedź, ponieważ czułem, że nie odpowiedziała na podstawowe pytanie QA wcześniej ...
Aconcagua,
@Aconcagua: Ubs, moja wina :-) Dzięki za poprawę!
Klaus
W rzeczywistości to nie działa, ponieważ obie klauzule używające są prywatne. Jeśli dodam za pomocą do PlcNetwork: | using NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Otrzymuję błąd kompilatora, ponieważ poprzednia klauzula using jest prywatna. Nawiasem mówiąc, jeśli ustawię jedną klasę bazową za pomocą klauzuli jako publiczną, a drugą jako prywatną, nadal otrzymuję błąd niejednoznaczności. Występują błędy niejednoznaczności dotyczące metod, które nie są zdefiniowane w klasie podstawowej.
Andrew Goedhart,
8

Z dokumentów :

Deklaracja aliasu typu wprowadza nazwę, która może być używana jako synonim typu oznaczonego przez typ-id. Nie wprowadza nowego typu i nie może zmienić znaczenia istniejącej nazwy typu.

Chociaż te dwie usingklauzule reprezentują ten sam typ, kompilator ma dwie możliwości w następującej sytuacji:

void sendNetworkPacket(const NetworkPacket &pdu);

Może wybierać między:

  • MultiCmdQueueCallback::NetworkPacket i
  • PlcMsgFactoryImplCallback::NetworkPacket

ponieważ dziedziczy z obu MultiCmdQueueCallbacki PlcMsgFactoryImplCallbackbazowych klas. Rezultatem rozpoznawania nazw kompilatora jest błąd niejednoznaczności. Aby to naprawić, musisz wyraźnie poinstruować kompilator, aby używał jednego lub drugiego w ten sposób:

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

lub

void sendNetworkPacket(const PlcMsgFactoryImplCallback::NetworkPacket &pdu);
Orzechówka
źródło
Szczerze mówiąc, nie czuję się usatysfakcjonowany ... Oba są synonimami tego samego typu. Mogę łatwo mieć class C { void f(uint32_t); }; void C::f(unsigned int) { }(pod warunkiem dopasowania aliasów). Skąd więc różnica? Nadal są tego samego typu, co potwierdzają twoje cytaty (których nie uważam za wystarczające do wyjaśnienia) ...
Aconcagua,
@Aconcagua: Używanie typu podstawowego lub aliasu nigdy nie robi różnicy. Alias ​​nigdy nie jest nowym typem. Twoja obserwacja nie ma nic wspólnego z dwuznacznością, którą generujesz, podając SAMY alias w dwóch klasach podstawowych.
Klaus
1
@Aconcagua Myślę, że wspomniany przykład nie jest odpowiednim odpowiednikiem dla sytuacji z pytania
NutCracker
Cóż, zmieńmy trochę: Nazwijmy klasy A, B i C oraz typedef D, wtedy możesz nawet zrobić: class C : public A, public B { void f(A::D); }; void C::f(B::D) { }- przynajmniej GCC akceptuje.
Aconcagua,
Autor pytania dosłownie zadał pytanie „Dlaczego kompilator traktuje każdą klauzulę używającą jako odrębny typ, mimo że oba wskazują ten sam typ podstawowy?” - i nie rozumiem, w jaki sposób cytat wyjaśniłby, dlaczego , zamiast tego po prostu potwierdza zamieszanie związane z kontrolą jakości ... Nie chcę powiedzieć, że odpowiedź jest błędna , ale nie wyjaśnia wystarczająco w moich oczach ... .
Aconcagua
2

Istnieją dwa błędy:

  1. Dostęp do aliasów typu prywatnego
  2. Niejednoznaczne odniesienie do aliasów typów

prywatny-prywatny

Nie widzę problemu, że kompilator narzeka najpierw na drugi problem, ponieważ kolejność nie ma tak naprawdę znaczenia - musisz rozwiązać oba problemy, aby kontynuować.

publiczny-publiczny

Jeśli zmienisz widoczność zarówno MultiCmdQueueCallback::NetworkPacketi PlcMsgFactoryImplCallback::NetworkPacketna publicznym lub chronionym, potem drugi emisyjnej (dwuznaczności) jest oczywista - to są dwie różne aliasy typu choć mają ten sam podstawowy typ danych. Niektórzy mogą myśleć, że „sprytny” kompilator może rozwiązać ten problem (konkretny przypadek), ale należy pamiętać, że kompilator musi „myśleć ogólnie” i podejmować decyzje w oparciu o reguły globalne zamiast robić wyjątki dla poszczególnych przypadków. Wyobraź sobie następujący przypadek:

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


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

Czy kompilator powinien traktować NetworkPacketIDto samo? Na pewno nie. Ponieważ w systemie 32-bitowym size_tjest on 32-bitowy, a uint64_tzawsze jest 64-bitowy. Ale jeśli chcemy, aby kompilator sprawdzał podstawowe typy danych, nie mógł rozróżnić tych w systemie 64-bitowym.

publiczny prywatny

Uważam, że ten przykład nie ma sensu w przypadku użycia OP, ale ponieważ tutaj rozwiązujemy problemy w ogóle, rozważmy:

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

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

Myślę, że w tym przypadku kompilator powinien traktować PlcNetwork::NetworkPackettak, PlcMsgFactoryImplCallback::NetworkPacketponieważ nie ma innych wyborów. Dlaczego nadal tego nie robi i obwinianie się o dwuznaczność jest dla mnie tajemnicą.

PooSH
źródło
„Dlaczego nadal tego nie robi, a obwinianie o dwuznaczność jest dla mnie tajemnicą”. W C ++ wyszukiwanie nazw (widoczność) poprzedza sprawdzanie dostępu. IIRC, czytałem gdzieś, że uzasadnienie jest takie, że zmiana nazwy z prywatnej na publiczną nie powinna złamać istniejącego kodu, ale nie jestem całkowicie pewien.
LF