auto, const, dan constexpr

Type inference, pola pikir const-correctness, dan perhitungan saat kompilasi.

Daftar isi

Topik ini membahas tiga kata kunci yang mengatur tipe dan ketetapan nilai, yaitu auto, const, dan constexpr. Kata kunci auto memindahkan tugas menuliskan tipe kepada compiler, sedangkan const dan constexpr menetapkan bahwa sebuah nilai tidak boleh berubah dan menentukan kapan nilainya diketahui. Ketiganya diperagakan dengan program kecil, lengkap dengan pesan error yang muncul ketika aturannya dilanggar.

auto dan type inference#

Kata kunci auto meminta compiler menentukan tipe variabel dari nilai inisialisasinya. Mekanisme ini disebut type inference, yaitu penurunan tipe secara otomatis oleh compiler. Variabel yang dideklarasikan dengan auto tetap bertipe kuat, hanya saja tipe tersebut diturunkan alih-alih ditulis.

cpp
#include <iostream>

int main() {
    auto jumlah = 42;    // int
    auto harga = 12.5;   // double
    auto inisial = 'D';  // char
    auto aktif = true;   // bool

    std::cout << jumlah << " " << harga << " " << inisial << " " << aktif << "\n";
    return 0;
}
text Keluaran
42 12.5 D 1

Aturan penurunannya mengikuti litera yang diberikan, sehingga 42 menghasilkan int, 12.5 menghasilkan double, 'D' menghasilkan char, dan true menghasilkan bool. Deklarasi auto tanpa inisialisasi ditolak compiler karena tidak ada dasar untuk menurunkan tipe. Pemakaian terbaiknya adalah pada tipe yang panjang atau jelas dari konteks, sedangkan untuk variabel penting yang tipe turunannya membingungkan, menulis tipe secara eksplisit tetap lebih terbaca.

Jebakan auto pada litera string dan ekspresi#

auto mengikuti nilai inisialisasinya apa adanya, termasuk ketika hasil penurunan tipenya tidak diharapkan. Program berikut memuat dua kasus yang paling sering mengejutkan, yaitu litera string dan pembagian bilangan bulat.

cpp
#include <iostream>

int main() {
    auto sapaan = "Halo";
    auto hasil = 7 / 2;

    std::cout << "sapaan         = " << sapaan << "\n";
    std::cout << "sizeof(sapaan) = " << sizeof(sapaan) << "\n";
    std::cout << "hasil          = " << hasil << "\n";
    return 0;
}
text Keluaran
sapaan         = Halo
sizeof(sapaan) = 8
hasil          = 3

Litera "Halo" bukan std::string, melainkan deret karakter yang diturunkan menjadi const char*. Ukuran 8 pada platform 64 bit menegaskan bahwa sapaan adalah pointer. Konsekuensinya, ekspresi sapaan.length() ditolak compiler dengan pesan request for member 'length' in 'sapaan', which is of non-class type 'const char*'. Kasus kedua, 7 / 2 adalah pembagian dua bilangan bulat sehingga hasilnya 3, dan auto hanya mencatat hasil itu. Untuk string, deklarasi eksplisit std::string sapaan = "Halo"; memberi tipe yang benar, sedangkan pembagian yang mengharapkan pecahan membutuhkan operan bertipe pecahan.

const: variabel yang tidak boleh berubah#

Kata kunci const menandai variabel yang nilainya tidak boleh diubah setelah inisialisasi. Variabel semacam ini wajib diinisialisasi saat dideklarasikan, dan setiap percobaan penugasan ulang ditolak compiler.

cpp
#include <iostream>

int main() {
    const double tarifPerKm = 4500.0;
    double jarak = 12.5;

    double biaya = tarifPerKm * jarak;

    std::cout << "Biaya perjalanan: " << biaya << "\n";

    // tarifPerKm = 5000.0;  // ditolak compiler: tarifPerKm tidak boleh diubah
    return 0;
}
text Keluaran
Biaya perjalanan: 56250

Baris penugasan yang dinonaktifkan lewat komentar menunjukkan pola pelanggarannya. Apabila komentar itu dilepas, compiler menolak programnya dengan pesan error: assignment of read-only variable 'tarifPerKm'. Pesan semacam ini muncul saat kompilasi, sehingga pelanggaran tertangkap sebelum program berjalan.

cpp Contoh: penugasan ke variabel const ditolak compiler
// tidak-dijalankan (baris penugasan ditolak compiler, pesannya dijelaskan di prosa)
const int stok = 10;
stok = 20;  // error: assignment of read-only variable 'stok'

Kebiasaan menandai variabel yang memang tidak berubah disebut const-correctness, yaitu pola pikir yang menyerahkan pemeriksaan ketetapan kepada compiler. Semakin banyak nilai yang ditandai const, semakin sedikit tempat bagi kesalahan penugasan yang tidak disengaja.

constexpr: dihitung saat kompilasi#

Kata kunci constexpr menandai variabel atau fungsi yang nilainya dihitung saat kompilasi (compile-time), bukan saat program berjalan. Variabel constexpr otomatis bersifat const, tetapi keduanya tidak sama. Variabel const hanya menjamin tidak berubah, sedangkan nilai constexpr wajib diketahui pada saat kompilasi.

cpp
#include <iostream>

constexpr int ukuranMaksimum = 100;

constexpr int kuadrat(int x) {
    return x * x;
}

int main() {
    constexpr int sisi = 12;
    constexpr int luas = kuadrat(sisi);  // dihitung saat kompilasi

    int data[ukuranMaksimum] = {};
    data[0] = luas;

    std::cout << "kuadrat(" << sisi << ") = " << data[0] << "\n";
    std::cout << "jumlah elemen data = " << sizeof(data) / sizeof(data[0]) << "\n";
    return 0;
}
text Keluaran
kuadrat(12) = 144
jumlah elemen data = 100

Fungsi kuadrat dideklarasikan constexpr, sehingga pemanggilannya dengan argumen yang diketahui saat kompilasi menghasilkan nilai 144 yang dihitung oleh compiler. Kebutuhan nyata dari sifat ini terlihat pada array data, karena ukuran array wajib diketahui saat kompilasi dan hanya nilai constexpr yang memenuhinya. Nilai yang diperoleh dari masukan pengguna atau dari berkas tidak dapat menjadi constexpr.

Fungsi constexpr dengan argumen saat berjalan#

Kata constexpr pada fungsi adalah janji kemampuan, bukan tuntutan, sehingga fungsi yang sama tetap dapat dipanggil dengan nilai yang baru diketahui saat program berjalan. Pemanggilan dengan argumen constexpr dihitung saat kompilasi, sedangkan pemanggilan dengan argumen biasa dihitung saat program berjalan. Program berikut memanggil satu fungsi yang sama dengan kedua cara tersebut.

cpp
#include <iostream>

constexpr int kuadrat(int x) {
    return x * x;
}

int main() {
    int masukan = 9;
    int hasilBerjalan = kuadrat(masukan);
    constexpr int hasilKompilasi = kuadrat(9);

    std::cout << "kuadrat(9) saat berjalan  = " << hasilBerjalan << "\n";
    std::cout << "kuadrat(9) saat kompilasi = " << hasilKompilasi << "\n";
    return 0;
}
text Keluaran
kuadrat(9) saat berjalan  = 81
kuadrat(9) saat kompilasi = 81

Kesalahan sebaliknya terjadi ketika hasil hitung saat kompilasi dituntut dari nilai yang baru diketahui saat program berjalan. Deklarasi constexpr int z = kuadrat(masukan); ditolak compiler dengan pesan the value of 'masukan' is not usable in a constant expression, karena masukan bukan konstanta. Mekanisme dua arah inilah yang membuat satu fungsi constexpr melayani dua kebutuhan sekaligus, yaitu konstanta yang diperiksa saat kompilasi dan perhitungan biasa saat program berjalan.

Memilih antara const dan constexpr#

Kedua kata kunci berdiri di titik yang berbeda. const menjawab pertanyaan apakah nilai boleh berubah, sedangkan constexpr menjawab pertanyaan kapan nilai diketahui. Tabel berikut merangkum perbedaannya.

Aspekconstconstexpr
Perubahan nilaiDitolakDitolak
Waktu nilai diketahuiSaat kompilasi atau saat berjalanWajib saat kompilasi
Bisa diinisialisasi dari masukan penggunaBolehTidak boleh
Layak untuk ukuran arrayHanya bila nilainya juga diketahui saat kompilasiSelalu

Praktik yang disarankan mulai dari pertanyaan sederhana. Apabila nilainya berasal dari perhitungan yang seluruh masukannya sudah diketahui saat kompilasi, pilih constexpr. Apabila nilainya diperoleh saat program berjalan tetapi tidak boleh berubah setelahnya, pilih const.

Kesalahan umum#

Tiga kekeliruan berikut paling sering muncul ketika ketiga kata kunci ini mulai digabungkan.

  • Menulis auto x; tanpa inisialisasi. Compiler melaporkan error: declaration of 'auto x' has no initializer, karena tipe tidak mungkin diturunkan tanpa nilai. Perbaikannya memberi inisialisasi pada deklarasi yang sama.
  • Mengira const cukup untuk menetapkan ukuran array dari nilai yang baru diketahui saat berjalan. Pada konfigurasi default GCC, deklarasi semacam itu lolos hanya karena ekstensi array berpanjang variabel, sehingga kode yang sama ditolak compiler lain. Ukuran array yang portabel membutuhkan nilai yang diketahui saat kompilasi, misalnya constexpr.
  • Menggunakan constexpr untuk nilai yang diperoleh dari masukan pengguna atau berkas. Deklarasinya ditolak karena nilainya tidak mungkin diketahui saat kompilasi, dan perbaikannya menggunakan const biasa.

Ringkasan#

  • auto menurunkan tipe variabel dari inisialisasinya, dan variabelnya tetap wajib diinisialisasi karena tipe tidak dapat ditebak tanpa nilai.
  • const melarang perubahan nilai setelah inisialisasi, dan setiap pelanggarannya ditolak compiler dengan pesan assignment of read-only variable.
  • constexpr menuntut nilai dihitung saat kompilasi, sehingga hanya nilai tersebut yang boleh dipakai sebagai ukuran array.
  • const mengatur kebolehan berubah, sedangkan constexpr mengatur waktu nilai diketahui, dan keduanya dapat dipandu dengan tabel di atas.

Referensi#