Apakah saya diperbolehkan untuk memindahkan elemen dari a std::initializer_list<T>
?
#include <initializer_list>
#include <utility>
template<typename T>
void foo(std::initializer_list<T> list)
{
for (auto it = list.begin(); it != list.end(); ++it)
{
bar(std::move(*it)); // kosher?
}
}
Karena std::intializer_list<T>
memerlukan perhatian compiler khusus dan tidak memiliki nilai semantik seperti container normal dari pustaka standar C ++, saya lebih suka aman daripada menyesal dan bertanya.
c++
templates
c++11
move-semantics
initializer-list
fredoverflow
sumber
sumber
initializer_list<T>
adalah non -const. Seperti,initializer_list<int>
mengacu padaint
objek. Tapi saya pikir itu adalah cacat - itu dimaksudkan agar kompiler dapat mengalokasikan daftar secara statis dalam memori hanya baca.Jawaban:
Tidak, itu tidak akan berfungsi sebagaimana mestinya; Anda masih akan mendapatkan salinannya. Saya cukup terkejut dengan ini, karena saya pikir yang
initializer_list
ada untuk menyimpan sejumlah temporer sampai merekamove
mati.begin
danend
sebagaiinitializer_list
gantinyaconst T *
, jadi hasil darimove
kode Anda adalahT const &&
- referensi nilai r yang tidak dapat diubah. Ekspresi seperti itu tidak dapat dipindahkan secara berarti. Ini akan mengikat ke parameter fungsi bertipeT const &
karena nilai r memang mengikat ke referensi nilai konstanta, dan Anda masih akan melihat semantik salin.Mungkin alasannya adalah agar kompiler dapat memilih untuk membuat
initializer_list
konstanta yang diinisialisasi secara statis, tetapi tampaknya akan lebih bersih untuk membuat tipenyainitializer_list
atauconst initializer_list
atas kebijaksanaan kompiler, sehingga pengguna tidak tahu apakah mengharapkanconst
atau bisa berubah hasil daribegin
danend
. Tapi itu hanya firasat saya, mungkin ada alasan bagus mengapa saya salah.Pembaruan: Saya telah menulis proposal ISO untuk
initializer_list
dukungan tipe bergerak-saja. Ini hanya draf pertama, dan belum diterapkan di mana pun, tetapi Anda dapat melihatnya untuk analisis masalah lebih lanjut.sumber
std::move
itu aman, jika tidak produktif. (PembatasanT const&&
memindahkan konstruktor.)const std::initializer_list<T>
atau hanyastd::initializer_list<T>
dengan cara yang tidak terlalu sering menimbulkan kejutan. Pertimbangkan bahwa setiap argumen diinitializer_list
dapatconst
atau tidak dan yang dikenal dalam konteks pemanggil, tetapi kompilator harus menghasilkan hanya satu versi kode dalam konteks callee (yaitu difoo
dalamnya tidak tahu apa-apa tentang argumen bahwa penelepon sedang lewat)std::initializer_list &&
kelebihan beban melakukan sesuatu, bahkan jika kelebihan beban non-referensi juga diperlukan. Saya kira itu akan lebih membingungkan daripada situasi saat ini, yang sudah buruk.Tidak seperti yang Anda inginkan. Anda tidak dapat memindahkan
const
objek. Danstd::initializer_list
hanya menyediakanconst
akses ke elemennya. Jadi jenisit
yaituconst T *
.Upaya Anda untuk menelepon
std::move(*it)
hanya akan menghasilkan nilai l. YAITU: salinan.std::initializer_list
referensi memori statis . Itulah tujuan kelas. Anda tidak dapat berpindah dari memori statis, karena gerakan berarti mengubahnya. Anda hanya dapat menyalin darinya.sumber
initializer_list
mereferensikan tumpukan jika diperlukan. (Jika isinya tidak konstan, itu masih thread-safe.)initializer_list
obyek itu sendiri mungkin merupakan Xvalue, tapi isinya (array sebenarnya nilai-nilai yang menunjuk ke) adalahconst
, karena mereka isinya mungkin nilai-nilai statis. Anda tidak dapat berpindah dari konten fileinitializer_list
.const
x.move
mungkin tidak ada artinya, tetapi legal dan bahkan mungkin untuk mendeklarasikan parameter yang menerima hal itu. Jika pemindahan jenis tertentu terjadi tanpa operasi, itu bahkan mungkin bekerja dengan benar.std::move
. Ini memastikan bahwa Anda dapat mengetahui dari pemeriksaan saat operasi pemindahan terjadi, karena ini memengaruhi sumber dan tujuan (Anda tidak ingin hal itu terjadi secara implisit untuk objek bernama). Karena itu, jika Anda menggunakanstd::move
di tempat di mana operasi pemindahan tidak terjadi (dan tidak ada gerakan aktual yang akan terjadi jika Anda memiliki nilaiconst
x), maka kodenya menyesatkan. Saya pikir itu adalah kesalahan untukstd::move
dipanggil pada suatuconst
objek.const &&
di kelas yang mengumpulkan sampah dengan pointer terkelola, di mana semua yang relevan dapat berubah dan memindahkan memindahkan manajemen penunjuk tetapi tidak memengaruhi nilai yang terkandung. Selalu ada kasus tepi yang rumit: v).Ini tidak akan berfungsi seperti yang dinyatakan, karena
list.begin()
memiliki tipeconst T *
, dan tidak mungkin Anda dapat berpindah dari objek konstan. Perancang bahasa mungkin membuatnya sedemikian rupa untuk memungkinkan daftar penginisialisasi berisi, misalnya konstanta string, yang darinya tidak pantas untuk dipindahkan.Namun, jika Anda berada dalam situasi di mana Anda tahu bahwa daftar penginisialisasi berisi ekspresi rvalue (atau Anda ingin memaksa pengguna untuk menuliskannya) maka ada trik yang akan membuatnya bekerja (saya terinspirasi oleh jawaban Sumant untuk ini, tetapi solusinya jauh lebih sederhana dari yang itu). Anda memerlukan elemen yang disimpan dalam daftar penginisialisasi bukan
T
nilai, tetapi nilai yang merangkumT&&
. Kemudian, meskipun nilai itu sendiriconst
memenuhi syarat, mereka masih dapat mengambil rvalue yang dapat dimodifikasi.Sekarang, alih-alih mendeklarasikan
initializer_list<T>
argumen, Anda mendeklarasikaninitializer_list<rref_capture<T> >
argumen. Berikut adalah contoh konkret, yang melibatkan vektorstd::unique_ptr<int>
pointer pintar, yang hanya mendefinisikan semantik bergerak (jadi objek ini sendiri tidak akan pernah bisa disimpan dalam daftar penginisialisasi); namun daftar penginisialisasi di bawah ini dapat dikompilasi tanpa masalah.Satu pertanyaan memang membutuhkan jawaban: jika elemen dari daftar penginisialisasi harus merupakan prvalues yang benar (dalam contoh ini adalah xvalues), apakah bahasa memastikan bahwa masa pakai temporer yang sesuai meluas ke titik di mana mereka digunakan? Sejujurnya, menurut saya bagian 8.5 dari standar yang relevan tidak membahas masalah ini sama sekali. Namun, membaca 1.9: 10, tampaknya ekspresi lengkap yang relevan dalam semua kasus mencakup penggunaan daftar penginisialisasi, jadi saya rasa tidak ada bahaya menggantung referensi nilai r.
sumber
"Hello world"
? Jika Anda berpindah dari mereka, Anda cukup menyalin sebuah pointer (atau mengikat referensi).{..}
terikat ke referensi dalam parameter fungsirref_capture
. Ini tidak memperpanjang masa hidup mereka, mereka masih hancur pada akhir ekspresi penuh saat mereka diciptakan.std::initializer_list<rref_capture<T>>
dalam beberapa sifat transformasi yang Anda pilih - katakanlah,std::decay_t
- untuk memblokir deduksi yang tidak diinginkan.Saya pikir mungkin bermanfaat untuk menawarkan titik awal yang masuk akal untuk sebuah solusi.
Komentar sebaris.
sumber
initializer_list
dapat dipindahkan dari, bukan apakah ada yang punya solusi. Selain itu, nilai jual utama dariinitializer_list
adalah bahwa itu hanya template pada jenis elemen, bukan jumlah elemen, dan oleh karena itu tidak mengharuskan penerima untuk juga diberi template - dan ini benar-benar kehilangan itu.initializer_list
tetapi tidak tunduk pada semua kendala yang membuatnya berguna. :)initializer_list
(melalui sihir kompilator) menghindari keharusan fungsi templat pada jumlah elemen, sesuatu yang secara inheren dibutuhkan oleh alternatif berdasarkan array dan / atau fungsi variadik, sehingga membatasi rentang kasus di mana yang terakhir dapat digunakan. Menurut pemahaman saya, ini justru salah satu alasan utama untuk memilikiinitializer_list
, jadi sepertinya perlu disebutkan.Sepertinya tidak diperbolehkan dalam standar saat ini karena sudah dijawab . Berikut adalah solusi lain untuk mencapai sesuatu yang serupa, dengan mendefinisikan fungsi sebagai variadic alih-alih mengambil daftar penginisialisasi.
Template variadic dapat menangani referensi nilai-r dengan tepat, tidak seperti initializer_list.
Dalam kode contoh ini, saya menggunakan satu set fungsi pembantu kecil untuk mengubah argumen variadic menjadi vektor, agar mirip dengan kode aslinya. Tapi tentu saja Anda bisa menulis fungsi rekursif dengan template variadic secara langsung.
sumber
initializer_list
dapat dipindahkan dari, bukan apakah ada yang punya solusi. Selain itu, nilai jual utama dariinitializer_list
adalah bahwa itu hanya template pada jenis elemen, bukan jumlah elemen, dan oleh karena itu tidak mengharuskan penerima untuk juga diberi template - dan ini benar-benar kehilangan itu.Saya memiliki implementasi yang lebih sederhana yang menggunakan kelas pembungkus yang bertindak sebagai tag untuk menandai maksud pemindahan elemen. Ini adalah biaya waktu kompilasi.
Kelas pembungkus dirancang untuk digunakan dengan cara
std::move
yang digunakan, cukup gantistd::move
denganmove_wrapper
, tetapi ini membutuhkan C ++ 17. Untuk spesifikasi yang lebih lama, Anda dapat menggunakan metode pembuat tambahan.Anda harus menulis metode / konstruktor pembangun yang menerima kelas pembungkus di dalamnya
initializer_list
dan memindahkan elemen yang sesuai.Jika Anda memerlukan beberapa elemen untuk disalin alih-alih dipindahkan, buat salinan sebelum meneruskannya
initializer_list
.Kode tersebut harus didokumentasikan sendiri.
sumber
Daripada menggunakan a
std::initializer_list<T>
, Anda bisa mendeklarasikan argumen Anda sebagai referensi nilai r array:Lihat contoh menggunakan
std::unique_ptr<int>
: https://gcc.godbolt.org/z/2uNxv6sumber
Pertimbangkan
in<T>
idiom yang dijelaskan di cpptruths . Idenya adalah untuk menentukan lvalue / rvalue pada saat run-time dan kemudian memanggil move atau copy-construction.in<T>
akan mendeteksi rvalue / lvalue meskipun antarmuka standar yang disediakan oleh initializer_list adalah referensi const.sumber