Mengapa pesan kesalahan template C ++ begitu mengerikan?

28

Templat C ++ terkenal karena menghasilkan pesan kesalahan yang panjang dan tidak dapat dibaca. Saya punya ide umum mengapa pesan kesalahan template di C ++ sangat buruk. Pada dasarnya, masalahnya adalah bahwa kesalahan tidak dipicu sampai kompilator menemukan sintaks yang tidak didukung oleh tipe tertentu dalam templat. Sebagai contoh:

template <class T>
void dosomething(T& x) { x += 5; }

Jika Ttidak mendukung +=operator, kompiler akan menghasilkan pesan kesalahan. Dan jika ini terjadi jauh di dalam perpustakaan di suatu tempat, pesan kesalahan mungkin ribuan baris.

Tapi template C ++ pada dasarnya hanya sebuah mekanisme untuk mengetik bebek waktu kompilasi. Kesalahan template C ++ secara konseptual sangat mirip dengan kesalahan tipe runtime yang mungkin terjadi dalam bahasa dinamis seperti Python. Misalnya, pertimbangkan kode Python berikut:

def dosomething(x):
   x.foo()

Di sini, jika xtidak memiliki foo()metode, interpreter Python melempar pengecualian, dan menampilkan jejak stack bersama dengan pesan kesalahan yang cukup jelas yang menunjukkan masalah. Bahkan jika kesalahan tidak dipicu sampai penerjemah berada jauh di dalam beberapa fungsi perpustakaan, pesan runtime-error masih belum mendekati seburuk muntah yang tidak terbaca dimuntahkan oleh kompiler C ++ khas. Jadi mengapa kompiler C ++ tidak bisa lebih jelas tentang apa yang salah? Mengapa beberapa pesan kesalahan templat C ++ benar-benar menyebabkan jendela konsol saya bergulir selama lebih dari 5 detik?

Channel72
sumber
6
Beberapa kompiler memiliki pesan kesalahan yang mengerikan, tetapi yang lain benar-benar bagus ( clang++mengedipkan mata).
Benjamin Bannier
2
Jadi Anda lebih suka program Anda gagal saat runtime, dikirim, di tangan pelanggan, daripada gagal saat kompilasi?
P Shved
13
@Pavel, tidak. Pertanyaan ini bukan tentang keuntungan / kerugian dari pengecekan runtime vs waktu kompilasi.
Channel72
1
Sebagai contoh kesalahan template C ++ besar, FWIW: codegolf.stackexchange.com/a/10470/7174
kebs

Jawaban:

28

Pesan kesalahan templat mungkin terkenal, tetapi tidak selalu panjang dan tidak dapat dibaca. Dalam hal ini, seluruh pesan kesalahan (dari gcc) adalah:

test.cpp: In function void dosomething(T&) [with T = X]’:
test.cpp:11:   instantiated from here
test.cpp:6: error: no match for operator+=’ in x += 5

Seperti dalam contoh Python Anda, Anda mendapatkan "tumpukan jejak" poin instantiasi templat, dan pesan kesalahan yang jelas menunjukkan masalah.

Terkadang, pesan kesalahan terkait templat bisa lebih lama, karena berbagai alasan:

  • "Jejak tumpukan" mungkin jauh lebih dalam
  • Nama-nama tipe mungkin jauh lebih lama, karena template dipakai dengan instantiasi template lain sebagai argumen mereka, dan ditampilkan dengan semua kualifikasi namespace mereka
  • Ketika resolusi kelebihan gagal, pesan kesalahan mungkin berisi daftar kandidat kelebihan (yang masing-masing mungkin berisi beberapa nama jenis yang sangat panjang)
  • Kesalahan yang sama dapat dilaporkan berkali-kali, jika templat yang tidak valid dipakai di banyak tempat

Perbedaan utama dari Python adalah sistem tipe statis, yang mengarah pada keharusan memasukkan nama tipe (terkadang panjang) dalam pesan kesalahan. Tanpa mereka, kadang-kadang akan sangat sulit untuk mendiagnosis mengapa resolusi kelebihan gagal. Dengan mereka, tantangan Anda bukan lagi untuk menebak di mana masalahnya, tetapi untuk menguraikan hieroglif yang memberi tahu Anda di mana itu.

Juga, memeriksa pada saat runtime berarti bahwa program akan berhenti pada kesalahan pertama yang ditemui, hanya menampilkan satu pesan. Kompiler mungkin menampilkan semua kesalahan yang ditemui, sampai menyerah; setidaknya di C ++, itu tidak boleh berhenti pada kesalahan pertama dalam file, karena itu mungkin merupakan konsekuensi dari kesalahan nanti.

Mike Seymour
sumber
4
Bisakah Anda memberikan contoh kesalahan sebagai konsekuensi dari kesalahan nanti?
Ruslan
12

Beberapa alasan yang jelas termasuk:

  1. Sejarah. Ketika gcc, MSVC, dll. Masih baru, mereka tidak mampu menggunakan banyak ruang ekstra untuk menyimpan data untuk menghasilkan pesan kesalahan yang lebih baik. Ingatan sudah cukup langka sehingga mereka tidak bisa.
  2. Selama bertahun-tahun, konsumen mengabaikan kualitas pesan kesalahan, jadi sebagian besar vendor juga.
  3. Dengan beberapa kode, kompiler dapat menyinkronkan ulang dan mendiagnosis kesalahan nyata nanti dalam kode. Kesalahan dalam template mengalir sangat buruk sehingga apa pun yang melewati yang pertama hampir selalu tidak berguna.
  4. Fleksibilitas umum templat membuatnya sulit untuk menebak apa yang mungkin Anda maksudkan ketika kode Anda mengalami kesalahan.
  5. Di dalam templat, makna nama bergantung pada konteks templat, dan konteks instantiasi, dan pencarian bergantung argumen dapat menambah lebih banyak kemungkinan.
  6. Fungsi overloading dapat memberikan banyak calon untuk apa panggilan fungsi tertentu mungkin merujuk, dan beberapa kompiler (misalnya, gcc) patuh daftar mereka semua ketika ada ambiguitas.
  7. Banyak coder yang tidak pernah mempertimbangkan untuk menggunakan parameter normal tanpa memastikan bahwa nilai yang dikirimkan memenuhi persyaratan, bahkan tidak mencoba untuk memeriksa parameter template sama sekali (dan saya harus mengaku, saya cenderung ke arah ini sendiri).

Itu jauh dari lengkap, tetapi Anda mendapatkan ide umum. Bahkan jika itu tidak mudah, sebagian besar dapat disembuhkan. Selama bertahun-tahun, saya telah memberi tahu orang-orang untuk mendapatkan salinan Comeau C ++ untuk penggunaan reguler; Saya mungkin telah menyimpan cukup dari satu pesan kesalahan satu kali untuk membayar kompiler. Sekarang Dentang mencapai titik yang sama (dan bahkan lebih murah).

Saya akan menutup dengan pengamatan umum yang terdengar seperti lelucon, tetapi sebenarnya tidak. Sebagian besar waktu, pekerjaan nyata kompiler secara jujur adalah mengubah kode sumber menjadi pesan kesalahan. Sudah saatnya vendor berkonsentrasi melakukan pekerjaan itu sedikit lebih baik - meskipun saya akan secara terbuka mengakui bahwa ketika saya telah menulis kompiler, saya memiliki kecenderungan yang kuat untuk memperlakukannya sebagai sekunder (paling-paling) dan dalam beberapa kasus hampir mengabaikannya. sama sekali.

Jerry Coffin
sumber
9

Jawaban sederhananya adalah, karena Python dirancang untuk bekerja seperti itu, sedangkan banyak hal yang terkait dengan template muncul secara tidak sengaja. Itu tidak pernah dimaksudkan untuk menjadi sistem Turing-lengkap untuk dirinya sendiri, misalnya. Dan jika Anda tidak dapat dengan sengaja merencanakan dan alasan tentang apa yang terjadi ketika sistem Anda bekerja , mengapa ada orang yang mengharapkan perencanaan yang cermat dan bijaksana tentang apa yang terjadi ketika terjadi kesalahan?

Juga, seperti yang Anda tunjukkan, interpreter Python dapat mempermudah Anda dengan menampilkan jejak stack karena itu mengartikan kode Python. Jika kompiler C ++ hits kesalahan template dan memberi Anda jejak stack, itu akan sama tidak membantu seperti "template muntah," bukan?

Mason Wheeler
sumber