Mengapa referensi bukan "const" di C ++?

87

Kita tahu bahwa "variabel const" menunjukkan bahwa setelah ditetapkan, Anda tidak dapat mengubah variabel, seperti ini:

int const i = 1;
i = 2;

Program di atas akan gagal untuk dikompilasi; gcc meminta dengan kesalahan:

assignment of read-only variable 'i'

Tidak masalah, saya bisa memahaminya, tetapi contoh berikut di luar pemahaman saya:

#include<iostream>
using namespace std;
int main()
{
    boolalpha(cout);
    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;
    int const &ri = i;
    cout << is_const<decltype(ri)>::value << endl;
    return 0;
}

Ini menghasilkan

true
false

Aneh. Kita tahu bahwa begitu referensi terikat ke nama / variabel, kita tidak bisa mengubah pengikatan ini, kita mengubah objek terikatnya. Jadi saya kira tipe riharus sama dengan i: kapan ian int const, mengapa ritidak const?

Troskyvs
sumber
3
Juga, boolalpha(cout)sangat tidak biasa. Anda bisa melakukannya std::cout << boolalpha.
isanae
6
Begitu banyak untuk rimenjadi "alias" yang tidak bisa dibedakan i.
Kaz
1
Ini karena referensi selalu terikat pada objek yang sama. ijuga merupakan rujukan tetapi karena alasan historis Anda tidak menyatakannya secara eksplisit. Dengan demikian iadalah referensi yang mengacu pada suatu penyimpanan dan rimerupakan referensi yang mengacu pada penyimpanan yang sama. Tetapi tidak ada perbedaan di alam antara i dan ri. Karena Anda tidak dapat mengubah pengikatan referensi, Anda tidak perlu mengkualifikasinya sebagai const. Dan izinkan saya mengklaim bahwa komentar @Kaz jauh lebih baik daripada jawaban yang divalidasi (jangan pernah menjelaskan referensi menggunakan pointer, ref adalah nama, ptr adalah variabel).
Jean-Baptiste Yunès
1
Pertanyaan yang fantastis. Sebelum melihat contoh ini, saya berharap is_constuntuk kembali truedalam kasus ini juga. Menurut pendapat saya, ini adalah contoh yang baik mengapa constsecara fundamental terbelakang; atribut "bisa berubah" (a la Rust's mut) sepertinya akan lebih konsisten.
Kyle Strand
1
Mungkin judulnya harus diubah menjadi "mengapa is_const<int const &>::valuesalah?" atau serupa; Saya berjuang untuk melihat arti dari pertanyaan selain bertanya tentang perilaku sifat tipe
MM

Jawaban:

53

Ini mungkin tampak kontra-intuitif tetapi saya pikir cara untuk memahami ini adalah dengan menyadari bahwa, dalam hal tertentu, referensi diperlakukan secara sintaksis seperti petunjuk .

Ini tampaknya logis untuk sebuah penunjuk :

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

Keluaran:

true
false

Ini logis karena kita tahu ini bukan objek penunjuk yang const (dapat dibuat untuk menunjuk ke tempat lain) itu adalah objek yang sedang diarahkan.

Jadi kita dengan benar melihat keteguhan dari pointer itu sendiri yang dikembalikan sebagai false.

Jika kita ingin membuat pointernya sendiri, constkita harus mengatakan:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* const ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

Keluaran:

true
true

Jadi saya pikir kita melihat analogi sintaksis dengan referensi .

Namun referensi secara semantik berbeda dengan pointer terutama dalam satu hal penting, kami tidak diizinkan untuk me - rebind referensi ke objek lain setelah terikat.

Jadi meskipun referensi memiliki sintaks yang sama dengan pointer , aturannya berbeda sehingga bahasa mencegah kita mendeklarasikan referensi itu sendiri constseperti ini:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const& const ri = i; // COMPILE TIME ERROR!
    cout << is_const<decltype(ri)>::value << endl;
}

Saya berasumsi kita tidak diperbolehkan melakukan ini karena tampaknya tidak diperlukan ketika aturan bahasa mencegah referensi dari rebound dengan cara yang sama seperti pointer (jika tidak dideklarasikan const).

Jadi untuk menjawab pertanyaan:

T) Mengapa "referensi" bukan "const" di C ++?

Dalam contoh Anda, sintaksis membuat sesuatu dirujuk dengan constcara yang sama seperti jika Anda mendeklarasikan sebuah pointer .

Benar atau salah kita tidak diperbolehkan membuat referensi itu sendiri consttetapi jika kita ada maka akan terlihat seperti ini:

int const& const ri = i; // not allowed

T) kita tahu begitu referensi diikat ke nama / variabel, kita tidak bisa mengubah pengikatan ini, kita mengubah objek yang diikat. Jadi saya kira jenisnya riharus sama dengan i: kapan ia int const, mengapa ritidak const?

Mengapa decltype()tidak ditransfer ke objek referensi terikat?

Saya kira ini untuk persamaan semantik dengan pointer dan mungkin juga fungsi dari decltype()(tipe yang dideklarasikan) adalah untuk melihat kembali apa yang dideklarasikan sebelum pengikatan terjadi.

Galik
sumber
13
Kedengarannya seperti Anda mengatakan "referensi tidak bisa menjadi konstan karena selalu konstan"?
ruakh
2
Mungkin benar bahwa "secara semantik semua tindakan pada mereka setelah mereka terikat akan ditransfer ke objek yang mereka rujuk", tetapi OP mengharapkan hal itu untuk diterapkan decltypejuga, dan ternyata tidak.
ruakh
10
Ada banyak anggapan yang berkelok-kelok dan analogi yang berlaku meragukan untuk petunjuk di sini, yang menurut saya membingungkan masalah lebih dari yang diperlukan, ketika memeriksa standar seperti sonyuanyao langsung ke titik sebenarnya: Referensi bukanlah tipe yang memenuhi syarat cv, oleh karena itu tidak mungkin const, karena itu std::is_constharus kembali false. Mereka malah bisa menggunakan kata-kata yang berarti harus kembali true, tapi ternyata tidak. Itu dia! Semua hal tentang pointer, "Saya berasumsi", "Saya kira", dll tidak memberikan penjelasan yang nyata.
underscore_d
1
@underscore_d Ini mungkin pertanyaan yang lebih baik untuk obrolan daripada bagian komentar di sini, tetapi apakah Anda memiliki contoh "kebingungan yang tidak perlu" dari cara berpikir tentang referensi ini? Jika mereka dapat diterapkan seperti itu, apa masalahnya dengan berpikir seperti itu? (Saya rasa pertanyaan decltypeini tidak dihitung karena bukan operasi runtime, jadi ide "saat runtime, referensi berperilaku seperti pointer", apakah benar atau tidak, tidak benar-benar berlaku.
Kyle Strand
1
Referensi juga tidak diperlakukan secara sintaksis seperti pointer. Mereka memiliki sintaks yang berbeda untuk dideklarasikan dan digunakan. Jadi saya bahkan tidak yakin apa yang Anda maksud.
Jordan Melo
55

kenapa "ri" bukan "const"?

std::is_const memeriksa apakah tipe tersebut memenuhi syarat const atau tidak.

Jika T adalah tipe yang memenuhi syarat const (yaitu, const, atau const volatile), berikan nilai konstanta anggota sama dengan true. Untuk jenis lainnya, nilainya salah.

Tapi referensi tidak bisa memenuhi syarat konstitusi. Referensi [dcl.ref] / 1

Referensi yang memenuhi syarat Cv tidak berbentuk kecuali jika kualifikasi-cv diperkenalkan melalui penggunaan nama-typedef ([dcl.typedef], [temp.param]) atau pernyataan-penentu ([dcl.type.simple]) , dalam hal ini cv-qualifier akan diabaikan.

Jadi is_const<decltype(ri)>::valueakan kembali falsekarena ri(referensi) bukan tipe yang memenuhi syarat const. Seperti yang Anda katakan, kami tidak dapat me-rebind referensi setelah inisialisasi, yang berarti referensi selalu "const", di sisi lain, referensi yang memenuhi syarat const atau referensi tanpa kualifikasi const mungkin tidak masuk akal sebenarnya.

songyuanyao
sumber
5
Langsung ke Standar dan karena itu langsung ke intinya, daripada semua anggapan yang membingungkan dari jawaban yang diterima.
underscore_d
5
@underscore_d Ini adalah jawaban yang bagus sampai Anda mengetahui bahwa op juga menanyakan alasannya. "Karena dikatakan begitu" tidak terlalu berguna untuk aspek pertanyaan itu.
simpleuser
5
@ user9999999 Jawaban lain juga tidak benar-benar menjawab aspek itu. Jika referensi tidak bisa dipantulkan, mengapa tidak is_constkembali true? Jawaban itu mencoba menarik analogi tentang bagaimana pointer secara opsional dapat dipasang ulang, sedangkan referensi tidak - dan dengan demikian, mengarah pada kontradiksi diri untuk alasan yang sama. Saya tidak yakin ada penjelasan yang sebenarnya, selain keputusan yang agak sewenang-wenang oleh mereka yang menulis Standar, dan terkadang itulah yang terbaik yang bisa kita harapkan. Karena itu jawaban ini.
underscore_d
2
Aspek penting lainnya, saya pikir, adalah bahwa decltypeini bukan fungsi dan karena itu bekerja langsung pada referensi itu sendiri bukan pada disebut-ke objek. (Ini mungkin lebih relevan dengan jawaban "referensi pada dasarnya adalah penunjuk", tetapi saya masih berpikir itu adalah bagian dari apa yang membuat contoh ini membingungkan dan oleh karena itu perlu disebutkan di sini.)
Kyle Strand
3
Untuk lebih menjelaskan komentar dari @KStrand: decltype(name)bertindak berbeda dari seorang jenderal decltype(expr). Jadi misalnya, decltype(i)adalah tipe yang dideklarasikan iis const int, sementara decltype((i))akan int const &.
Daniel Schepler
18

Anda perlu menggunakan std::remove_referenceuntuk mendapatkan nilai yang Anda cari.

std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;

Untuk informasi lebih lanjut, lihat posting ini .

chema989
sumber
8

Mengapa makro tidak const? Fungsi? Literals? Nama-nama jenisnya?

const hal-hal hanyalah sebagian dari hal-hal yang tidak dapat diubah.

Karena tipe referensi hanya itu - tipe - mungkin masuk akal untuk meminta const-qualifier pada mereka semua untuk simetri dengan tipe lain (terutama dengan tipe pointer), tapi ini akan menjadi sangat membosankan dengan sangat cepat.

Jika C ++ memiliki objek yang tidak dapat diubah secara default, memerlukan mutablekata kunci pada apa pun yang tidak Anda inginkan const, maka ini akan mudah: jangan izinkan pemrogram menambahkan mutableke jenis referensi.

Karena adanya, mereka tidak dapat diubah tanpa kualifikasi.

Dan, karena mereka tidak const-kualifikasi, mungkin akan lebih membingungkan untuk is_consttipe referensi untuk menghasilkan true.

Saya menemukan ini sebagai kompromi yang masuk akal, terutama karena keabadian itu ditegakkan oleh fakta bahwa tidak ada sintaks untuk mengubah referensi.

Balapan Ringan dalam Orbit
sumber
6

Ini adalah quirk / fitur di C ++. Meskipun kami tidak menganggap referensi sebagai tipe, mereka sebenarnya "duduk" dalam sistem tipe. Meskipun ini tampak janggal (mengingat bahwa ketika referensi digunakan, semantik referensi terjadi secara otomatis dan referensi "disingkirkan"), ada beberapa alasan yang dapat dipertahankan mengapa referensi dimodelkan dalam sistem tipe alih-alih sebagai atribut terpisah di luar Tipe.

Pertama, mari kita pertimbangkan bahwa tidak setiap atribut dari nama yang dideklarasikan harus dalam sistem tipe. Dari bahasa C, kami memiliki "kelas penyimpanan", dan "hubungan". Sebuah nama dapat diperkenalkan sebagai extern const int ri, di mana externmenunjukkan kelas penyimpanan statis dan adanya linkage. Jenisnya adil const int.

C ++ jelas merangkul gagasan bahwa ekspresi memiliki atribut yang berada di luar sistem tipe. Bahasa tersebut sekarang memiliki konsep "kelas nilai" yang merupakan upaya untuk mengatur semakin banyak atribut non-tipe yang dapat ditunjukkan oleh ekspresi.

Namun referensi adalah tipe. Mengapa?

Dulu dijelaskan dalam tutorial C ++ bahwa deklarasi seperti const int &ridiperkenalkan risebagai memiliki tipe const int, tetapi semantik referensi. Semantik referensi itu bukanlah sebuah tipe; itu hanyalah semacam atribut yang menunjukkan hubungan yang tidak biasa antara nama dan lokasi penyimpanan. Selain itu, fakta bahwa referensi bukanlah tipe digunakan untuk merasionalisasi mengapa Anda tidak dapat membuat tipe berdasarkan referensi, meskipun sintaks konstruksi tipe mengizinkannya. Misalnya, array atau pointer ke referensi tidak dimungkinkan: const int &ari[5]dan const int &*pri.

Namun sebenarnya referensi adalah tipe dan decltype(ri)mengambil beberapa node tipe referensi yang tidak memenuhi syarat. Anda harus turun melewati node ini di pohon tipe untuk mendapatkan tipe yang mendasarinya remove_reference.

Saat Anda menggunakan ri, referensi tersebut secara transparan diselesaikan, sehingga ri"terlihat dan terasa seperti i" dan bisa disebut "alias" untuk itu. Dalam sistem tipe, risebenarnya memiliki tipe yang merupakan " referensi ke const int ".

Mengapa tipe referensi?

Pertimbangkan bahwa jika referensi bukanlah tipe, maka fungsi-fungsi ini akan dianggap memiliki tipe yang sama:

void foo(int);
void foo(int &);

Itu tidak mungkin karena alasan yang cukup terbukti dengan sendirinya. Jika mereka memiliki tipe yang sama, itu berarti deklarasi mana pun akan cocok untuk kedua definisi, dan setiap (int)fungsi harus dicurigai mengambil referensi.

Demikian pula, jika referensi bukan tipe, maka kedua deklarasi kelas ini akan menjadi setara:

class foo {
  int m;
};

class foo {
  int &m;
};

Ini akan benar untuk satu unit terjemahan menggunakan satu deklarasi, dan unit terjemahan lain dalam program yang sama menggunakan deklarasi lainnya.

Faktanya adalah bahwa referensi menyiratkan perbedaan dalam implementasi dan tidak mungkin memisahkannya dari tipe, karena tipe dalam C ++ berkaitan dengan implementasi entitas: "layout" -nya dalam bit sehingga untuk berbicara. Jika dua fungsi memiliki tipe yang sama, keduanya dapat dipanggil dengan konvensi pemanggilan biner yang sama: ABI-nya sama. Jika dua struct atau class memiliki tipe yang sama, tata letaknya sama seperti semantik akses ke semua anggota. Kehadiran referensi mengubah aspek tipe ini, jadi keputusan desain langsung untuk memasukkannya ke dalam sistem tipe adalah keputusan yang mudah. (Namun, perhatikan argumen balasan di sini: anggota struct / class bisa jadi static, yang juga mengubah representasi; namun itu bukan tipe!)

Jadi, referensi dalam sistem tipe sebagai "warga kelas dua" (tidak seperti fungsi dan array dalam ISO C). Ada hal-hal tertentu yang tidak dapat kita "lakukan" dengan referensi, seperti mendeklarasikan pointer ke referensi, atau susunannya. Tapi itu tidak berarti mereka bukan tipe. Mereka bukan tipe dengan cara yang masuk akal.

Tidak semua pembatasan kelas dua ini penting. Mengingat bahwa ada struktur referensi, mungkin ada array referensi! Misalnya

// fantasy syntax
int x = 0, y = 0;
int &ar[2] = { x, y };

// ar[0] is now an alias for x: could be useful!

Ini tidak diterapkan di C ++, itu saja. Pointer ke referensi sama sekali tidak masuk akal, karena pointer yang diangkat dari referensi hanya menuju ke objek yang direferensikan. Kemungkinan alasan mengapa tidak ada array referensi adalah bahwa orang-orang C ++ menganggap array sebagai semacam fitur tingkat rendah yang diwarisi dari C yang rusak dalam banyak hal yang tidak dapat diperbaiki, dan mereka tidak ingin menyentuh array sebagai dasar untuk sesuatu yang baru. Keberadaan array referensi, bagaimanapun, akan membuat contoh yang jelas tentang bagaimana referensi harus menjadi tipe.

Non constjenis -qualifiable: ditemukan dalam ISO C90, juga!

Beberapa jawaban mengisyaratkan fakta bahwa referensi tidak mengambil constkualifikasi. Itu agak red herring, karena deklarasi const int &ri = itersebut bahkan tidak mencoba membuat constreferensi yang memenuhi syarat: ini adalah referensi ke tipe yang memenuhi syarat const (yang dengan sendirinya tidak const). Sama seperti const in *rimendeklarasikan pointer ke sesuatu const, tetapi pointer itu sendiri tidak const.

Meskipun demikian, memang benar bahwa referensi tidak dapat membawa constkualifikasi itu sendiri.

Namun, ini tidak begitu aneh. Bahkan dalam bahasa ISO C 90, tidak semua tipe bisa const. Yaitu, array tidak bisa.

Pertama, sintaks tidak ada untuk mendeklarasikan array const: int a const [42]salah.

Namun, apa yang coba dilakukan oleh pernyataan di atas dapat diekspresikan melalui perantara typedef:

typedef int array_t[42];
const array_t a;

Tapi ini tidak berfungsi seperti yang terlihat. Dalam deklarasi ini, bukan ayang constmemenuhi syarat, tetapi elemennya! Artinya, a[0]adalah const int, tetapi ahanya "array int". Akibatnya, ini tidak memerlukan diagnosis:

int *p = a; /* surprise! */

Ini melakukan:

a[0] = 1;

Sekali lagi, ini menggarisbawahi gagasan bahwa referensi dalam arti tertentu "kelas kedua" dalam sistem tipe, seperti array.

Perhatikan bagaimana analogi tersebut berlaku lebih dalam, karena array juga memiliki "perilaku konversi yang tidak terlihat", seperti referensi. Tanpa programmer harus menggunakan operator eksplisit apa pun, pengenal asecara otomatis berubah menjadi int *pointer, seolah-olah ekspresi &a[0]telah digunakan. Ini analog dengan bagaimana referensi ri, ketika kita menggunakannya sebagai ekspresi utama, secara ajaib menunjukkan objek iyang diikat. Ini hanyalah "pembusukan" seperti "larik ke peluruhan penunjuk".

Dan sama seperti kita tidak boleh bingung dengan "array to pointer" yang membusuk menjadi pemikiran yang salah bahwa "array hanyalah pointer dalam C dan C ++", kita juga tidak boleh berpikir bahwa referensi hanyalah alias yang tidak memiliki jenisnya sendiri.

Saat decltype(ri)menyembunyikan konversi biasa dari referensi ke objek rujukannya, ini tidak jauh berbeda dengan sizeof amenyembunyikan konversi array-ke-pointer, dan mengoperasikan tipe array itu sendiri untuk menghitung ukurannya.

Kaz
sumber
Anda mendapat banyak informasi menarik dan berguna di sini (+1), tetapi itu lebih atau kurang terbatas pada fakta bahwa "referensi ada dalam sistem tipe" - ini tidak sepenuhnya menjawab pertanyaan OP. Di antara jawaban ini dan jawaban @ songyuanyao, menurut saya ada lebih dari cukup informasi untuk memahami situasinya, tetapi ini terasa seperti latar belakang yang diperlukan untuk sebuah jawaban daripada jawaban yang lengkap.
Kyle Strand
1
Juga, saya pikir kalimat ini patut disoroti: "Saat Anda menggunakan ri, referensi diselesaikan secara transparan ..." Satu poin kunci (yang saya sebutkan di komentar tetapi sejauh ini belum muncul di jawaban mana pun ) adalah yang decltype tidak melakukan resolusi transparan ini (ini bukan fungsi, jadi ritidak "digunakan" dalam arti yang Anda gambarkan). Ini cocok di sangat baik dengan seluruh fokus Anda pada sistem jenis - mereka koneksi utama adalah bahwa decltypeadalah operasi jenis-sistem .
Kyle Strand
Hmm, hal-hal tentang array terasa seperti garis singgung, tetapi kalimat terakhir sangat membantu.
Kyle Strand
..... meskipun pada titik ini saya sudah memilih Anda dan saya bukan OP, jadi Anda tidak benar-benar mendapatkan atau kehilangan apa pun dengan mendengarkan kritik saya ....
Kyle Strand
Jelas jawaban yang mudah (dan benar) hanya mengarahkan kita pada standar tetapi saya suka pemikiran latar belakang di sini meskipun beberapa mungkin berpendapat bahwa sebagian darinya adalah anggapan. +1.
Steve Kidd
-6

const X & x ”berarti x alias objek X, tetapi Anda tidak dapat mengubah objek X tersebut melalui x.

Dan lihat std :: is_const .

Sotter.liu
sumber
ini bahkan tidak mencoba menjawab pertanyaan itu.
bolov