Mengapa std :: initializer_list bukan bahasa bawaan?

96

Mengapa std::initializer_listbahasa inti tidak ada di dalamnya?

Menurut saya, ini adalah fitur yang cukup penting dari C ++ 11 namun tidak memiliki kata kunci yang dicadangkan sendiri (atau yang serupa).

Sebaliknya, initializer_listitu hanya kelas template dari pustaka standar yang memiliki pemetaan implisit khusus dari sintaks braced-init-list baru {...} yang ditangani oleh compiler.

Pada pemikiran pertama, solusi ini cukup hacky .

Apakah ini cara penambahan baru ke bahasa C ++ sekarang akan diimplementasikan: dengan peran implisit dari beberapa kelas template dan bukan oleh bahasa inti ?


Harap pertimbangkan contoh-contoh ini:

   widget<int> w = {1,2,3}; //this is how we want to use a class

mengapa kelas baru dipilih:

   widget( std::initializer_list<T> init )

alih-alih menggunakan sesuatu yang mirip dengan salah satu ide ini:

   widget( T[] init, int length )  // (1)
   widget( T... init )             // (2)
   widget( std::vector<T> init )   // (3)
  1. array klasik, Anda mungkin bisa menambahkan constdi sana-sini
  2. tiga titik sudah ada dalam bahasa (var-args, sekarang template variadic), mengapa tidak menggunakan kembali sintaks (dan membuatnya terasa built-in )
  3. hanya wadah yang ada, bisa menambahkan constdan&

Semuanya sudah menjadi bagian dari bahasa. Saya hanya menulis 3 ide pertama saya, saya yakin ada banyak pendekatan lain.

emesx
sumber
26
Komite standar benci menambahkan kata kunci baru!
Alex Chamberlain
11
Ini saya mengerti, tetapi ada banyak kemungkinan bagaimana cara memperluas bahasa ( kata kunci hanyalah sebuah contoh )
emesx
10
std::array<T>tidak lebih dari 'bagian dari bahasa' std::initializer_list<T>. Dan ini hampir bukan satu-satunya komponen pustaka yang diandalkan oleh bahasa tersebut. Lihat new/ delete,, type_infoberbagai jenis pengecualian size_t, dll.
bames53
6
@ Elmes: Saya akan menyarankan const T(*)[N], karena itu berperilaku sangat mirip dengan cara std::initializer_listkerjanya.
Mooing Duck
1
Ini menjawab mengapa std::arrayatau array berukuran statis adalah alternatif yang kurang diinginkan.
boycy

Jawaban:

48

Sudah ada contoh fitur bahasa "inti" yang mengembalikan tipe yang ditentukan dalam stdnamespace. typeidkembali std::type_infodan (peregangan satu poin mungkin) sizeofkembali std::size_t.

Dalam kasus sebelumnya, Anda sudah perlu menyertakan tajuk standar untuk menggunakan apa yang disebut fitur "bahasa inti".

Sekarang, untuk daftar penginisialisasi kebetulan tidak ada kata kunci yang diperlukan untuk menghasilkan objek, sintaksnya adalah kurung kurawal sensitif konteks. Selain itu, itu sama dengan type_info. Secara pribadi saya tidak berpikir tidak adanya kata kunci membuatnya "lebih hacky". Mungkin sedikit lebih mengejutkan, tetapi ingat bahwa tujuannya adalah untuk memungkinkan sintaks braced-initializer yang sama yang telah diizinkan untuk agregat.

Jadi ya, Anda mungkin dapat mengharapkan lebih banyak dari prinsip desain ini di masa mendatang:

  • jika lebih banyak kesempatan muncul di mana dimungkinkan untuk memperkenalkan fitur baru tanpa kata kunci baru maka panitia akan mengambilnya.
  • jika fitur baru membutuhkan tipe yang kompleks, maka tipe tersebut akan ditempatkan stdbukan sebagai bawaan.

Karenanya:

  • jika fitur baru membutuhkan jenis yang kompleks dan dapat diperkenalkan tanpa kata kunci baru maka Anda akan mendapatkan apa yang Anda miliki di sini, yaitu sintaks "bahasa inti" tanpa kata kunci baru dan yang menggunakan jenis pustaka dari std.

Apa yang terjadi, menurut saya, adalah bahwa tidak ada pembagian mutlak dalam C ++ antara "bahasa inti" dan pustaka standar. Mereka adalah bab yang berbeda dalam standar tetapi masing-masing mereferensikan yang lain, dan selalu demikian.

Ada pendekatan lain dalam C ++ 11, yaitu lambda memperkenalkan objek yang memiliki tipe anonim yang dihasilkan oleh kompilator. Karena mereka tidak memiliki nama, mereka tidak ada di namespace sama sekali, tentu tidak ada di dalamnya std. Itu bukan pendekatan yang cocok untuk daftar penginisialisasi, karena Anda menggunakan nama tipe saat Anda menulis konstruktor yang menerimanya.

Steve Jessop
sumber
1
Menurut saya pembagian ini tidak mungkin (mailny?) Karena peran tipe implisit seperti itu . type_infodan size_tmerupakan argumen yang bagus .. yah size_thanya sebuah typedef .. jadi mari kita lewati ini. Perbedaan antara type_infodan initializer_listadalah bahwa yang pertama adalah hasil dari operator eksplisit , dan yang kedua dari tindakan kompilator implisit . Menurut saya, itu juga initializer_list bisa diganti dengan beberapa kontainer yang sudah ada .. atau lebih baik lagi: setiap pengguna menyatakan sebagai tipe argumen!
emesx
4
... atau mungkin alasan sederhana bahwa jika Anda menulis konstruktor untuk vectoritu, arraymaka Anda dapat membuat vektor dari larik apa pun dengan tipe yang tepat, bukan hanya yang dihasilkan oleh sintaks daftar penginisialisasi. Saya tidak yakin itu akan menjadi hal yang buruk untuk membangun kontainer dari salah satu array, tapi itu bukan maksud panitia dalam memperkenalkan sintaks baru.
Steve Jessop
2
@Christian: Tidak, std::arraybahkan tidak memiliki konstruktor. The std::arraykasus hanya agregat-inisialisasi. Juga, saya menyambut Anda untuk bergabung dengan saya di ruang obrolan Lounge <C ++>, karena diskusi ini agak lama.
Xeo
3
@ChristianRau: Xeo berarti elemen disalin ketika daftar penginisialisasi dibuat. Menyalin daftar penginisialisasi tidak menyalin elemen yang ada.
Mooing Duck
2
@Christian List-inisialisasi tidak berarti initializer_list. Ini bisa banyak hal, termasuk inisialisasi langsung yang baik, atau inisialisasi agregat. Tak satu pun dari mereka yang melibatkan initializer_list (dan beberapa hanya tidak bisa bekerja seperti itu).
R. Martinho Fernandes
42

Komite Standar C ++ tampaknya memilih untuk tidak menambahkan kata kunci baru, mungkin karena itu meningkatkan risiko pemecahan kode yang ada (kode lama dapat menggunakan kata kunci tersebut sebagai nama variabel, kelas, atau apa pun).

Selain itu, menurut saya, mendefinisikan std::initializer_listsebagai penampung templated adalah pilihan yang cukup elegan: jika itu adalah kata kunci, bagaimana Anda mengakses jenis yang mendasarinya? Bagaimana Anda akan mengulanginya? Anda juga memerlukan banyak operator baru, dan itu hanya akan memaksa Anda mengingat lebih banyak nama dan lebih banyak kata kunci untuk melakukan hal yang sama seperti yang dapat Anda lakukan dengan penampung standar.

Memperlakukan std::initializer_listsebagai wadah lain memberi Anda kesempatan untuk menulis kode umum yang bekerja dengan hal-hal itu.

MEMPERBARUI:

Lalu mengapa memperkenalkan tipe baru, daripada menggunakan kombinasi yang sudah ada? (dari komentar)

Untuk memulainya, semua container lainnya memiliki metode untuk menambahkan, menghapus, dan meletakkan elemen, yang tidak diinginkan untuk koleksi yang dibuat oleh kompilator. Satu-satunya pengecualian adalah std::array<>, yang membungkus larik gaya-C ukuran tetap dan karenanya akan tetap menjadi satu-satunya kandidat yang masuk akal.

Namun, seperti yang ditunjukkan Nicol Bolas dengan benar dalam komentar, perbedaan mendasar lainnya antara std::initializer_listdan semua wadah standar lainnya (termasuk std::array<>) adalah bahwa wadah terakhir memiliki semantik nilai , sementara std::initializer_listmemiliki semantik referensi . Menyalin std::initializer_list, misalnya, tidak akan menyebabkan salinan elemen yang dikandungnya.

Selain itu (sekali lagi, atas kebaikan Nicol Bolas), memiliki wadah khusus untuk daftar inisialisasi brace memungkinkan overloading pada cara pengguna melakukan inisialisasi.

Andy Prowl
sumber
4
Lalu mengapa memperkenalkan tipe baru, daripada menggunakan kombinasi yang sudah ada?
emesx
3
@elmes: Sebenarnya lebih suka std::array. Tetapi std::arraymengalokasikan memori sambil std::initializaer_listmembungkus array waktu kompilasi. Anggap saja sebagai perbedaan antara char s[] = "array";dan char *s = "initializer_list";.
rodrigo
2
Dan menjadi tipe normal membuat kelebihan beban, spesialisasi template, dekorasi nama dan sejenisnya, bukan masalah.
rodrigo
2
@rodrigo: std::arraytidak mengalokasikan memori apa pun, itu sederhana T arr[N];, hal yang sama yang mendukung std::initializer_list.
Xeo
6
@ Xeo: T arr[N] mengalokasikan memori, mungkin tidak di heap dinamis tetapi di tempat lain ... Begitu juga std::array. Namun yang tidak kosong initializer_listtidak dapat dibangun oleh pengguna sehingga jelas tidak dapat mengalokasikan memori.
rodrigo
6

Ini bukanlah hal baru. Misalnya, for (i : some_container)mengandalkan keberadaan metode tertentu atau fungsi mandiri di some_containerkelas. C # bahkan lebih mengandalkan pustaka .NET-nya. Sebenarnya, menurut saya, ini adalah solusi yang cukup elegan, karena Anda dapat membuat kelas Anda kompatibel dengan beberapa struktur bahasa tanpa mempersulit spesifikasi bahasa.

Menakuti
sumber
2
metode di kelas atau berdiri sendiri begindan endmetode. Ini IMO sedikit berbeda.
emesx
3
Apakah itu? Sekali lagi Anda memiliki konstruksi bahasa murni yang mengandalkan konstruksi spesifik dari kode Anda. Ini juga mungkin telah dilakukan dengan memperkenalkan kata kunci baru, misalnya,iterable class MyClass { };
Spook
tetapi Anda dapat menempatkan metode di mana pun Anda inginkan, menerapkannya sesuka Anda .. Ada beberapa kesamaan, saya setuju! Pertanyaan ini tentang initializer_listmeskipun
emesx
4

Ini memang bukan hal baru dan berapa banyak yang telah menunjukkan, praktik ini ada di C ++ dan ada, katakanlah, di C #.

Andrei Alexandrescu telah menyebutkan hal yang baik tentang ini: Anda mungkin menganggapnya sebagai bagian dari namespace "inti" imajiner, maka itu akan lebih masuk akal.

Jadi, itu sebenarnya sesuatu seperti: core::initializer_list, core::size_t, core::begin(), core::end()dan sebagainya. Ini hanya kebetulan yang tidak menguntungkan bahwa stdnamespace memiliki beberapa konstruksi bahasa inti di dalamnya.

Artem Tokmakov
sumber
2

Tidak hanya dapat bekerja sepenuhnya di perpustakaan standar. Dimasukkan ke dalam pustaka standar tidak berarti bahwa kompiler tidak dapat memainkan trik-trik cerdas.

Meskipun mungkin tidak dapat dalam semua kasus, itu mungkin mengatakan dengan sangat baik: jenis ini terkenal, atau tipe sederhana, mari kita abaikan initializer_listdan hanya memiliki gambaran memori tentang apa nilai yang diinisialisasi seharusnya.

Dengan kata lain int i {5};dapat setara dengan int i(5);atau int i=5;atau bahkan intwrapper iw {5};Di mana intwrapperkelas pembungkus sederhana di atas int dengan konstruktor sepele mengambilinitializer_list

Paul de Vrieze
sumber
Apakah kita memiliki contoh kompiler yang dapat direproduksi yang benar-benar memainkan "trik pintar" seperti ini? Agak masuk akal di bawah seolah-olah , tapi saya ingin melihat pembuktiannya.
underscore_d
Ide untuk mengoptimalkan kompiler adalah kompilator dapat mengubah kode menjadi kode yang setara. C ++ khususnya mengandalkan pengoptimalan untuk abstraksi "gratis". Ide untuk mengganti kode dari pustaka standar adalah umum (lihat daftar bawaan gcc gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html ).
Paul de Vrieze
Faktanya, ide Anda yang int i {5}melibatkan apa pun std::initializer_listsalah. inttidak ada konstruktor yang mengambilstd::initializer_list , jadi 5hanya digunakan secara langsung untuk membuatnya. Jadi contoh utamanya tidak relevan; tidak ada optimasi yang harus dilakukan. Di luar itu, karena std::initializer_listmelibatkan kompiler yang membuat dan membuat proxy array 'imajiner', saya kira ini dapat mendukung pengoptimalan, tetapi itu adalah bagian 'ajaib' dalam kompiler, jadi ini terpisah dari apakah pengoptimal secara umum dapat melakukan sesuatu yang pintar dengan yang cantik. objek tumpul yang berisi 2 iterator yang menghasilkan
underscore_d
1

Ini bukan bagian dari bahasa inti karena dapat diimplementasikan sepenuhnya di perpustakaan, hanya baris operator newdan operator delete. Keuntungan apa yang akan membuat kompiler lebih rumit untuk membuatnya?

Pete Becker
sumber