Pertanyaan nilai untuk rvalue konversi implisit


Saya melihat istilah "lvalue-to-r-value conversion" yang digunakan di banyak tempat di seluruh standar C ++. Konversi semacam ini sering dilakukan secara implisit, sejauh yang saya tahu.

Salah satu fitur yang tidak terduga (bagi saya) dari ungkapan dari standar adalah bahwa mereka memutuskan untuk memperlakukan nilai-nilai-ke-rnilai sebagai konversi. Bagaimana jika mereka mengatakan bahwa glvalue selalu diterima, bukannya prvalue. Akankah kalimat itu benar-benar memiliki arti yang berbeda? Sebagai contoh, kita membaca bahwa nilai dan xvalues ​​adalah contoh dari glalue. Kami tidak membaca bahwa nilai dan xvalues ​​dapat dikonversikan menjadi glalue. Apakah ada perbedaan makna?

Sebelum pertemuan pertama saya dengan terminologi ini, saya digunakan untuk memodelkan nilai dan rujukan secara mental lebih atau kurang sebagai berikut: "Nilai adalah selalu dapat bertindak sebagai rujukan, tetapi sebagai tambahan dapat muncul di sisi kiri =, dan di sebelah kanan dari &".

Ini, bagi saya, adalah perilaku intuitif yang jika saya memiliki nama variabel, maka saya dapat menempatkan nama itu di mana-mana di mana saya akan meletakkan secara harfiah. Model ini tampaknya konsisten dengan terminologi konversi implisit lvalue-to-rnalue yang digunakan dalam standar, selama konversi implisit ini dijamin akan terjadi.

Tapi, karena mereka menggunakan terminologi ini, saya mulai bertanya-tanya apakah konversi lnalue-to-rvalue implisit mungkin gagal terjadi dalam beberapa kasus. Artinya, mungkin model mental saya salah di sini. Berikut ini adalah bagian yang relevan dari standar: (terima kasih kepada komentator).

Kapan pun sebuah glvalue muncul dalam konteks di mana prvalue diharapkan, glvalue dikonversikan menjadi prvalue; lihat 4.1, 4.2, dan 4.3. [Catatan: Upaya untuk mengikat referensi rnalue ke lvalue bukanlah konteks seperti itu; lihat 8.5.3. —catatan akhir]

Saya mengerti apa yang mereka gambarkan dalam catatan adalah sebagai berikut:

int x = 1;
int && y = x; //in this declaration context, x won't bind to y.
// but the literal 1 would have bound, so this is one context where the implicit 
// lvalue to rvalue conversion did not happen.  
// The expression on right is an lvalue. if it had been a prvalue, it would have bound.
// Therefore, the lvalue to prvalue conversion did not happen (which is good). 

Jadi, pertanyaan saya adalah (adalah):

1) Dapatkah seseorang mengklarifikasi konteks di mana konversi ini dapat terjadi secara implisit? Khususnya, selain konteks pengikatan pada rujukan referensi, adakah yang lain di mana nilai lvalue-to-r-value gagal terjadi secara implisit?

2) Juga, tanda kurung [Note:...] dalam klausa itu tampak bahwa kita bisa mengetahuinya dari kalimat sebelumnya. Bagian mana dari standar itu?

3) Apakah itu berarti bahwa pengikatan rujukan-nilai bukanlah konteks di mana kita mengharapkan ekspresi prvalue (di sebelah kanan)?

4) Seperti konversi lainnya, apakah konversi nilai-ke-nilai-ke-prekuatan melibatkan kerja pada waktu proses yang memungkinkan saya untuk mengamatinya?

Tujuan saya di sini bukanlah untuk bertanya apakah itu diinginkan untuk memungkinkan konversi seperti itu. Saya mencoba belajar menjelaskan kepada diri sendiri perilaku kode ini menggunakan standar sebagai titik awal.

Jawaban yang bagus akan melalui kutipan yang saya tempatkan di atas dan menjelaskan (berdasarkan penguraian teks) apakah catatan di dalamnya juga tersirat dari teksnya. Ini kemudian mungkin akan menambahkan tanda kutip lainnya yang memberi tahu saya konteks lain di mana konversi ini mungkin gagal terjadi secara implisit, atau menjelaskan tidak ada lagi konteks seperti itu. Mungkin diskusi umum tentang mengapa glvalue menjadi prvalue dianggap sebagai konversi.


32
2017-12-31 01:52


asal


Jawaban:


Saya pikir konversi nilai-ke-r-nilai lebih dari sekedar gunakan lvalue di mana nilai rv diperlukan. Ini dapat membuat salinan kelas, dan selalu menghasilkan a nilai, bukan objek.

Saya menggunakan n3485 untuk "C ++ 11" dan n1256 untuk "C99".


Objek dan nilai

Deskripsi paling ringkas ada di C99 / 3.14:

obyek

wilayah penyimpanan data di lingkungan eksekusi, isi yang dapat mewakili   nilai-nilai

Ada juga sedikit di C ++ 11 / [intro.object] / 1

Beberapa objek polimorfik; implementasi menghasilkan informasi yang terkait dengan   setiap objek yang memungkinkan untuk menentukan jenis objek selama eksekusi program. Untuk objek lain, penafsiran nilai yang ditemukan di dalamnya ditentukan oleh jenis ekspresi yang digunakan untuk mengaksesnya.

Jadi suatu objek mengandung nilai (dapat berisi).


Kategori nilai

Terlepas dari namanya, kategori nilai mengklasifikasikan ekspresi, bukan nilai. bahkan ekspresi-nilai tidak bisa dianggap nilai.

Taksonomi penuh / kategorisasi dapat ditemukan di [basic.lval]; inilah diskusi StackOverflow.

Inilah bagian-bagian tentang objek:

  • Sebuah lvalue ([...]) menetapkan fungsi atau objek. [...]
  • Sebuah xvalue (sebuah nilai "eXpiring") juga mengacu pada objek [...]
  • SEBUAH glvalue (Nilai "umum") adalah nilai atau nilai x.
  • Sebuah rvalue ([...]) adalah xvalue, objek sementara atau subobjek daripadanya, atau nilai yang tidak terkait dengan objek.
  • Nilai prabayar (nilai "murni") adalah rvalue yang bukan merupakan nilai x. [...]

Perhatikan frasa "nilai yang tidak terkait dengan objek". Perhatikan juga bahwa xvalue-expressions merujuk pada objek, benar nilai-nilai harus selalu terjadi sebagai prvalue-expressions.


Nilai lvalue-to-r-value

Seperti yang ditunjukkan catatan kaki 53, sekarang seharusnya disebut "konversi nilai-ke-nilai-mikro". Pertama, inilah kutipannya:

1 Nilai gl dari non-fungsi, tipe non-array T dapat dikonversi menjadi prvalue. Jika T adalah jenis yang tidak lengkap, program yang mengharuskan konversi ini tidak terbentuk. Jika objek yang direferensikan oleh glvalue bukanlah objek bertipe T dan bukan obyek dari jenis yang berasal T, atau jika objek tidak diinisialisasi, sebuah program   yang mengharuskan konversi ini memiliki perilaku yang tidak terdefinisi. Jika T adalah tipe non-kelas, tipe prvalue adalah versi tanpa batas dari versi cv-unqualified T. Jika tidak, jenis prvalue adalah T.

Paragraf pertama ini menentukan persyaratan dan jenis konversi yang dihasilkan. Belum peduli dengan efek konversi (selain dari Perilaku Tidak Terdefinisi).

2 Ketika nilai lvalue-to-r-value terjadi dalam operand yang tidak dievaluasi atau subekspresi dari nilai yang terkandung dalam objek yang direferensikan tidak diakses. Jika tidak, jika glvalue memiliki tipe kelas, salinan konversi menginisialisasi tipe sementara Tdari nilai dan hasil dari konversi adalah prvalue untuk sementara. Jika tidak, jika glvalue memiliki tipe (mungkin cv-qualified) std::nullptr_t, yang   hasil prvalue adalah null pointer konstan. Jika tidak, nilai yang terkandung dalam objek yang ditunjukkan oleh glvalue adalah hasil prvalue.

Saya berpendapat bahwa Anda akan melihat konversi nilai-ke-rujukan yang paling sering diterapkan ke jenis non-kelas. Sebagai contoh,

struct my_class { int m; };

my_class x{42};
my_class y{0};

x = y;

Ekspresi x = y tidak tidak terapkan konversi lvalue-to-rvalue menjadi y (Itu akan membuat sementara my_class, ngomong-ngomong). Alasannya adalah itu x = y ditafsirkan sebagai x.operator=(y), yang mengambil y per default dengan referensitidak berdasarkan nilai (untuk pengikatan referensi, lihat di bawah ini, ia tidak dapat mengikat rvalue, karena itu akan menjadi objek sementara yang berbeda dari y). Namun, definisi default dari my_class::operator= menerapkan konversi lvalue-to-rvalue menjadi x.m.

Oleh karena itu, bagian yang paling penting bagi saya tampaknya

Jika tidak, nilai yang terkandung dalam objek yang ditunjukkan oleh glvalue adalah hasil prvalue.

Jadi biasanya, konversi lvalue-to-rvalue akan adil baca nilai dari suatu objek. Ini bukan hanya konversi tidak ada di antara kategori nilai (ekspresi); itu bahkan dapat membuat sementara dengan memanggil konstruktor salinan. Dan konversi nilai-ke-rvalue selalu mengembalikan prvalue nilai, bukan (sementara) obyek.

Perhatikan bahwa konversi nilai-ke-r-nilai bukan satu-satunya konversi yang mengubah nilai menjadi prvalue: Ada juga konversi penunjuk-ke-penunjuk dan konversi fungsi-ke-penunjuk.


nilai dan ekspresi

Kebanyakan ekspresi tidak menghasilkan objek[[rujukan?]]. Namun, sebuah ekspresi-id bisa menjadi identifier, yang menunjukkan suatu kesatuan. Objek adalah entitas, jadi ada ekspresi yang menghasilkan objek:

int x;
x = 5;

Sisi kiri dari ekspresi tugas  x = 5 juga perlu menjadi ekspresi. x di sini adalah sebuah ekspresi-id, karena x adalah pengenal. Hasil dari ini ekspresi-id aku s objek yang dilambangkan oleh x.

Ekspresi menerapkan konversi implisit: [expr] / 9

Kapanpun ekspresi glvalue muncul sebagai operand dari operator yang mengharapkan prvalue untuk operan itu, nilai lvalue-to-rvalue, array-to-pointer, atau konversi standar fungsi-ke-penunjuk diterapkan untuk mengonversi ekspresi ke nilai prvalue.

Dan / 10 tentang konversi aritmatika biasa serta / 3 tentang konversi yang ditentukan pengguna.

Saya sekarang ingin mengutip seorang operator yang "mengharapkan nilai untuk operan itu", tetapi tidak dapat menemukan satu pun kecuali casting. Misalnya, [expr.dynamic.cast] / 2 "Jika T adalah tipe pointer, v [operand] akan menjadi prvalue dari pointer untuk melengkapi tipe kelas ".

Itu konversi aritmatika biasa diperlukan oleh banyak operator aritmatika melakukan memohon konversi nilai-ke-r-nilai secara tidak langsung melalui konversi standar yang digunakan. Semua konversi standar tetapi tiga yang mengkonversi dari nilai-nilai ke rvalues ​​mengharapkan prvalues.

Namun, penugasan sederhana tidak meminta konversi aritmatika biasa. Ini didefinisikan dalam [expr.ass] / 2 sebagai:

Dalam penugasan sederhana (=), nilai ekspresi menggantikan yang dari objek yang dirujuk oleh operan kiri.

Jadi meskipun tidak secara eksplisit membutuhkan ekspresi prvalue di sisi kanan, ia memang membutuhkan a nilai. Tidak jelas bagi saya jika ini dengan ketat membutuhkan konversi lvalue-to-rvalue. Ada argumen bahwa mengakses nilai variabel terinisialisasi harus selalu memicu perilaku tidak terdefinisi (juga lihat CWG 616), tidak peduli apakah itu dengan menetapkan nilainya ke suatu objek atau dengan menambahkan nilainya ke nilai lain. Tetapi perilaku tidak terdefinisi ini hanya diperlukan untuk konversi nilai-ke-nilai (AFAIK), yang kemudian menjadi satu-satunya cara untuk mengakses nilai yang disimpan dalam suatu objek.

Jika pandangan yang lebih konseptual ini valid, bahwa kita memerlukan konversi nilai-ke-nilai untuk mengakses nilai di dalam objek, maka akan jauh lebih mudah untuk memahami di mana itu (dan perlu) diterapkan.


Inisialisasi

Seperti halnya tugas sederhana, ada a diskusi apakah nilai lvalue-to-rvalue diperlukan untuk menginisialisasi objek lain:

int x = 42; // initializer is a non-string literal -> prvalue
int y = x;  // initializer is an object / lvalue

Untuk tipe-tipe fundamental, [dcl.init] / 17 poin bullet terakhir mengatakan:

Jika tidak, nilai awal dari objek yang diinisialisasi adalah nilai (kemungkinan dikonversi) dari ekspresi penginisialisasi. Konversi standar akan digunakan, jika diperlukan, untuk mengonversi ekspresi penginisialisasi menjadi versi tipe tujuan yang tidak memenuhi syarat; tidak ada konversi yang ditentukan oleh pengguna yang dipertimbangkan. Jika konversi tidak dapat dilakukan, inisialisasi tidak terbentuk.

Namun, itu juga disebutkan nilai dari ekspresi penginisialisasi. Serupa dengan ekspresi tugas sederhana, kita dapat menganggap ini sebagai permintaan tidak langsung dari konversi nilai-ke-r-nilai.


Pengikatan referensi

Jika kita melihat konversi nilai-ke-nilai sebagai cara untuk mengakses nilai suatu objek (ditambah penciptaan sementara untuk operand jenis kelas), kami memahami bahwa itu tidak diterapkan secara umum untuk mengikat referensi: Referensi adalah lvalue, itu selalu mengacu pada suatu objek. Jadi jika kita mengikat nilai ke referensi, kita perlu membuat objek sementara yang memegang nilai-nilai itu. Dan ini memang kasus jika ekspresi penginisialisasi-referensi adalah prvalue (yang merupakan nilai atau objek sementara):

int const& lr = 42; // create a temporary object, bind it to `r`
int&& rv = 42;      // same

Mengikat prvalue ke referensi lnilai dilarang, tetapi pratinjau jenis kelas dengan fungsi konversi yang menghasilkan referensi nilai mungkin terikat dengan referensi nilai dari jenis konversi.

Deskripsi lengkap tentang pengikatan referensi dalam [dcl.init.ref] agak panjang dan agak di luar topik. Saya pikir esensi dari hal itu berkaitan dengan pertanyaan ini adalah referensi merujuk pada objek, oleh karena itu tidak ada konversi nilai-ke-nilai (objek-ke-nilai) glvalue-to-prvalue.


19
2018-01-08 15:11



Pada glalue: Nilai gl (nilai "umum") adalah ekspresi yang bernilai lvalue atau xvalue. Nilai gla dapat dikonversi secara implisit menjadi prvalue dengan lnalue-to-rvalue, array-to-pointer, atau konversi implisit fungsi-ke-pointer.

Transformasi nilai diterapkan ketika argumen nilai (misalnya referensi ke objek) digunakan dalam konteks di mana rvalue (misalnya angka) diharapkan.

Lvalue menjadi rvalue konversi
Nilai dari setiap non-fungsi, tipe non-array T dapat secara implisit dikonversi ke prvalue dari tipe yang sama. Jika T adalah tipe non-kelas, konversi ini juga menghilangkan cv-kualifikasi. Kecuali ditemui dalam konteks yang tidak dievaluasi (dalam operan sizeof, typeid, noexcept, atau decltype), konversi ini secara efektif menyalin-membangun objek sementara tipe T menggunakan glvalue asli sebagai argumen konstruktor, dan bahwa objek sementara dikembalikan sebagai prvalue . Jika glvalue memiliki tipe std :: nullptr_t, maka prvalue yang dihasilkan adalah null pointer nullptr konstan.


2
2018-01-08 07:44