Mengapa dua klausa yang menggunakan untuk menyelesaikan dengan jenis yang sama dilihat sebagai ambigius di gcc

32

Saya memiliki dua kelas dasar dengan menggunakan klausa

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


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

Saya kemudian mendeklarasikan sebuah kelas

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

kompiler kemudian menandai rujukan kesalahan ke 'NetworkPacket' adalah ambigu 'sendNetworkPacket (NetworkPacket & ...'

Sekarang keduanya 'menggunakan klausa' untuk kelas jaringan yang sama: NetworkPacket

dan sebenarnya jika saya mengganti deklarasi metode dengan:

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

itu mengkompilasi dengan baik.

Mengapa kompiler memperlakukan masing-masing menggunakan klausa sebagai tipe yang berbeda walaupun keduanya menunjuk ke tipe dasar yang sama. Apakah ini diamanatkan oleh standar atau apakah kita memiliki bug penyusun?

Andrew Goedhart
sumber
Tampaknya kompiler tidak cukup pintar
idris
Intinya adalah bahwa kompiler pada titik ini hanya tahu bahwa ada tiga NetworkPacket- di MultiCmdQueueCallback, di PlcMsgFactoryImplCallback, di Networking. Yang mana yang harus digunakan harus ditentukan. Dan saya tidak berpikir menempatkan virtualakan membantu di sini.
theWiseBro
@ Idris: sebaliknya, maksudmu standar tidak cukup permisif. kompiler benar untuk mengikuti standar.
Jarod42
@ Jarod42 Dalam jawaban di bawah ini 'sinonim untuk tipe yang dilambangkan dengan type-id' jadi jika mereka memiliki tipe-id yang sama maka dapat menggunakan keduanya. apakah standart atau compiler, sepertinya seseorang sebenarnya tidak cukup pintar.
idris
salah satu masalah multi-warisan
eagle275

Jawaban:

28

Sebelum melihat alias jenis yang dihasilkan, (dan aksesibilitas)

kita melihat nama

dan memang,

NetworkPacket mungkin

  • MultiCmdQueueCallback::NetworkPacket
  • atau PlcMsgFactoryImplCallback::NetworkPacket

Fakta yang mereka berdua tunjukkan Networking::NetworkPackettidak relevan.

Kami melakukan resolusi nama depan, yang menghasilkan ambiguitas.

Jarod42
sumber
Sebenarnya ini hanya sebagian benar Jika saya menambahkan menggunakan ke PlcNetwork: | menggunakan NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Saya mendapatkan kesalahan kompilator karena menggunakan klausa sebelumnya adalah pribadi.
Andrew Goedhart
@AndrewGoedhart Bukan kontradiksi. Pencarian nama dimulai di kelas sendiri terlebih dahulu. Ketika kompiler kemudian menemukan nama unik di sana, ia puas.
Aconcagua
Masalah saya di sini adalah mengapa nama yang berasal dari klausa penamaan pribadi di kelas dasar. Jika saya menghapus salah satu deklarasi pribadi yaitu, jadi salah satu kelas dasar memiliki klausa pribadi menggunakan dan tidak ada yang lain, kesalahan berubah menjadi 'Paket Jaringan tidak menyebutkan jenis'
Andrew Goedhart
1
Pencarian nama @AndrewGoedhart (jelas) tidak mempertimbangkan aksesibilitas. Anda mendapatkan kesalahan yang sama jika Anda membuat satu publik dan yang lainnya pribadi. Itulah kesalahan pertama yang ditemukan, jadi itulah kesalahan pertama yang akan dicetak. Jika Anda menghapus satu alias, maka masalah ambiguitas hilang, tetapi salah satu yang tidak dapat diakses tetap ada, sehingga Anda mendapatkan kesalahan cetak berikutnya. By the way, tidak pesan kesalahan baik (? MSVC sekali lagi), GCC lebih lebih tepat tentang: error: [...] is private within this context.
Aconcagua
1
@AndrewGoedhart Pertimbangkan yang berikut ini: class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); }- tidak sama, tetapi resolusi kelebihan bekerja sama: Pertimbangkan semua fungsi yang tersedia, hanya setelah memilih yang tepat, pertimbangkan aksesibilitas ... Dalam kasus yang diberikan, Anda juga mendapatkan ambiguitas; jika Anda mengubah fungsi privat untuk menerima dua karakter, itu akan dipilih meskipun bersifat pribadi - dan Anda mengalami kesalahan kompilasi berikutnya.
Aconcagua
14

Anda cukup dapat menyelesaikan ambiguitas dengan memilih secara manual mana yang ingin Anda gunakan.

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

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

}

Kompilator hanya mencari definisi di kelas dasar. Jika jenis dan atau alias yang sama ada di kedua kelas dasar itu hanya mengeluh bahwa ia tidak tahu mana yang harus digunakan. Tidak masalah apakah tipe yang dihasilkan sama atau tidak.

Kompilator hanya mencari nama di langkah pertama, sepenuhnya independen jika nama ini adalah fungsi, tipe, alias, metode atau apa pun. Jika nama tidak jelas, tidak ada tindakan lebih lanjut yang dilakukan dari kompiler! Itu hanya mengeluh dengan pesan kesalahan dan berhenti. Jadi cukup selesaikan ambiguitas dengan pernyataan menggunakan yang diberikan.

Klaus
sumber
Punya keraguan tentang kata-katanya. Jika mencari definisi , bukankah akan mempertimbangkan jenisnya juga? Bukankah itu hanya mencari nama saja (dan melupakan bagaimana didefinisikan)? Beberapa referensi standar akan sangat bagus ...
Aconcagua
Komentar terakhir inilah yang menjelaskan alasannya dengan benar. Ganti paragraf terakhir dengan komentar ini dan saya akan angkat suara;)
Aconcagua
Saya tidak bisa menerima - saya bukan penulis pertanyaan ... Maaf jika saya membuat Anda gugup. Hanya berusaha meningkatkan jawabannya, karena saya merasa itu tidak menjawab pertanyaan inti QA sebelum ...
Aconcagua
@Aconcagua: Ubs, salahku :-) Terima kasih atas peningkatannya!
Klaus
Sebenarnya ini tidak berfungsi karena keduanya menggunakan klausa pribadi. Jika saya menambahkan menggunakan ke PlcNetwork: | menggunakan NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Saya mendapatkan kesalahan kompilator karena menggunakan klausa sebelumnya adalah pribadi. Omong-omong jika saya membuat satu kelas dasar menggunakan klausa publik dan pribadi lainnya, saya masih mendapatkan kesalahan ambiguitas. Saya mendapatkan kesalahan ambiguitas pada metode yang tidak didefinisikan di kelas dasar.
Andrew Goedhart
8

Dari dokumen :

Deklarasi alias tipe memperkenalkan nama yang dapat digunakan sebagai sinonim untuk tipe yang dilambangkan dengan type-id. Itu tidak memperkenalkan tipe baru dan tidak bisa mengubah arti dari nama tipe yang ada.

Meskipun, kedua usingklausa tersebut mewakili tipe yang sama, kompiler memiliki dua pilihan dalam situasi berikut:

void sendNetworkPacket(const NetworkPacket &pdu);

Itu dapat memilih antara:

  • MultiCmdQueueCallback::NetworkPacket dan
  • PlcMsgFactoryImplCallback::NetworkPacket

karena mewarisi dari keduanya MultiCmdQueueCallbackPlcMsgFactoryImplCallback kelas dasar dan . Hasil dari resolusi nama kompiler adalah kesalahan ambiguitas yang Anda miliki. Untuk memperbaikinya, Anda perlu secara eksplisit menginstruksikan kompiler untuk menggunakan satu atau yang lain seperti ini:

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

atau

void sendNetworkPacket(const PlcMsgFactoryImplCallback::NetworkPacket &pdu);
Alat pemecah buah keras
sumber
Sejujurnya, saya tidak merasa puas ... Mereka berdua sinonim untuk tipe yang sama. Saya dapat dengan mudah class C { void f(uint32_t); }; void C::f(unsigned int) { }(asalkan alias cocok). Jadi mengapa ada perbedaan di sini? Mereka masih tipe yang sama, dikonfirmasi oleh kutipan Anda (yang saya pikir tidak cukup untuk menjelaskan) ...
Aconcagua
@Aconcagua: Menggunakan tipe dasar atau alias tidak pernah membuat perbedaan. Alias ​​bukan tipe baru. Pengamatan Anda tidak ada hubungannya dengan ambiguitas yang Anda hasilkan dengan memberikan alias SAMA di dua kelas dasar.
Klaus
1
@Aconcagua Saya pikir contoh yang Anda sebutkan bukan padanan yang tepat untuk situasi dari pertanyaan
NutCracker
Nah, mari kita ubah sedikit: Mari kita beri nama kelas A, B dan C dan typedef D, maka Anda bahkan dapat melakukannya: class C : public A, public B { void f(A::D); }; void C::f(B::D) { }- setidaknya GCC menerima.
Aconcagua
Penulis pertanyaan secara harfiah bertanya, 'Mengapa kompiler memperlakukan masing-masing menggunakan klausa sebagai tipe yang berbeda walaupun keduanya menunjuk ke tipe mendasar yang sama?' - dan saya tidak melihat bagaimana kutipan akan menjelaskan mengapa , sebagai gantinya, itu hanya mengkonfirmasi kebingungan QA ... Tidak ingin mengatakan jawabannya salah , tetapi tidak cukup menjelaskan di mata saya .. .
Aconcagua
2

Ada dua kesalahan:

  1. Mengakses alias tipe pribadi
  2. Referensi mendua untuk mengetik alias

pribadi-pribadi

Saya tidak melihat masalah yang dikompilasi oleh kompiler tentang masalah kedua terlebih dahulu karena urutannya tidak terlalu penting - Anda harus memperbaiki kedua masalah untuk melanjutkan.

publik-publik

Jika Anda mengubah visibilitas keduanya MultiCmdQueueCallback::NetworkPacketdan PlcMsgFactoryImplCallback::NetworkPacketmenjadi publik atau dilindungi, maka masalah kedua (ambiguitas) jelas - keduanya adalah dua jenis alias yang berbeda meskipun keduanya memiliki tipe data dasar yang sama. Beberapa orang mungkin berpikir bahwa kompiler "pintar" dapat menyelesaikan ini (kasus khusus) untuk Anda, tetapi perlu diingat bahwa kompiler perlu "berpikir secara umum" dan membuat keputusan berdasarkan aturan global alih-alih membuat pengecualian spesifik kasus. Bayangkan kasus berikut:

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


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

Haruskah kompiler memperlakukan keduanya NetworkPacketIDsama? Tentu tidak. Karena pada sistem 32-bit, sementara size_t32-bituint64_t selalu 64-bit. Tetapi jika kita ingin kompiler memeriksa tipe data yang mendasarinya, maka kompiler itu tidak dapat membedakannya pada sistem 64-bit.

privasi Umum

Saya percaya contoh ini tidak masuk akal dalam kasus penggunaan OP, tetapi karena di sini kita sedang memecahkan masalah secara umum, mari kita pertimbangkan bahwa:

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

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

Saya pikir dalam hal ini compiler harus memperlakukan PlcNetwork::NetworkPacketseperti PlcMsgFactoryImplCallback::NetworkPacketkarena tidak memiliki choises lain. Mengapa ia masih menolak untuk melakukannya dan menyalahkan ambiguitas adalah sebuah misteri bagi saya.

PooSH
sumber
"Mengapa masih menolak untuk melakukannya dan menyalahkan ambiguitas adalah sebuah misteri bagi saya." Di C ++, pencarian nama (visibilitas) mendahului pemeriksaan akses. IIRC, saya membaca di suatu tempat bahwa alasannya adalah bahwa mengubah nama dari pribadi ke publik tidak boleh melanggar kode yang ada, tapi saya tidak sepenuhnya yakin.
LF