Sebagian besar variabel yang selama ini kita tulis memang berubah nilai, tetapi banyak pula yang seharusnya tidak pernah berubah: konstanta pi, ukuran layar, atau anggaran waktu satu frame game. Untuk kategori kedua ini, C++ menyediakan dua kata kunci yang sering tertukar: const yang membuat variabel read-only selama program berjalan, dan constexpr yang menuntut nilainya selesai dihitung saat kompilasi. Perbedaannya bukan sekadar istilah — constexpr membuka pintu pemeriksaan otomatis oleh compiler melalui static_assert dan menjadi fondasi pemrograman compile-time yang makin penting di C++ modern. Chapter ini membedah keduanya, membuktikan bahwa compiler benar-benar menjaga konstanta, dan menutup dengan pengantar literal type.
const: Read-Only Setelah Inisialisasi #
Variabel const wajib diberi nilai pada saat dideklarasikan, dan setelah itu compiler menolak setiap upaya mengubahnya. Nilainya sendiri boleh berasal dari mana saja — hasil input pengguna, pembacaan konfigurasi, atau literal biasa — selama sekali diisi lalu tidak disentuh lagi. Cobalah melanggarnya:
int main() {
const int ukuranLayar = 1080;
ukuranLayar = 2160;
return 0;
}g++ menolaknya dengan pesan yang lugas:
const-assign.cpp: In function ‘int main()’:
const-assign.cpp:3:17: error: assignment of read-only variable ‘ukuranLayar’
3 | ukuranLayar = 2160;
| ~~~~~~~~~~~~^~~~~~Perlindungan ini bekerja dua arah: compiler mencegah kesalahan kita, dan pembaca kode mendapat janji bahwa nilai itu stabil dari awal sampai akhir. Manfaat keduanya terasa nyata pada konstanta yang dipakai di banyak tempat — frameBudgetMs di bawah bisa dipakai berulang kali tanpa risiko berubah di tengah jalan. Perhatikan bahwa const bukan membuat variabel “tetap” secara teknis memori, melainkan membuat akses lewat namanya read-only; pembahasan lebih dalam soal konstanta pada parameter dan pointer menyusul di Section Pointer dan Reference.
constexpr: Dihitung Saat Kompilasi #
constexpr menaikkan tuntutan satu tingkat: variabel yang ditandai demikian harus bisa dihitung pada saat kompilasi, sebelum program bahkan dijalankan. Program berikut memperlihatkan bentuk lengkapnya, termasuk pengantar singkat fungsi constexpr — fungsi sendiri akan kita pelajari tuntas di Section Fungsi; untuk sekarang cukup lihat bahwa ia bisa dipanggil oleh compiler saat menghitung konstanta:
#include <iostream>
constexpr int sisi = 7; // diketahui saat kompilasi
constexpr int luas = sisi * sisi; // dihitung compiler, bukan CPU
constexpr int kuadrat(int n) { // fungsi constexpr: bentuknya saja dulu
return n * n;
}
constexpr int volumeKubus = kuadrat(3) * 3; // 27, selesai sebelum program jalan
static_assert(luas == 49, "luas harus 49"); // pemeriksaan otomatis compiler
int main() {
const int ukuranLayar = 1080; // read-only selama program berjalan
const int frameBudgetMs = 16; // anggaran waktu frame game 60 FPS
std::cout << "Luas persegi : " << luas << "\n";
std::cout << "Volume kubus : " << volumeKubus << "\n";
std::cout << "Ukuran layar : " << ukuranLayar << "p\n";
std::cout << "Frame budget : " << frameBudgetMs << " ms\n";
return 0;
}Output yang Dihasilkan:
Luas persegi : 49
Volume kubus : 27
Ukuran layar : 1080p
Frame budget : 16 msPoin-poinnya:
- Baris 4-5:
luasdihitung darisisi * sisi. Karena keduanyaconstexpr, compiler sendiri yang menghitung49dan menuliskannya ke dalam program — pada saat runtime,luassudah adalah angka jadi, mirip literal langsung. - Baris 7-9: fungsi
kuadratditandaiconstexpr, artinya compiler boleh menjalankannya saat kompilasi bila argumennya konstanta. Pemanggilankuadrat(3)pada baris 11 selesai saat kompilasi. - Baris 12 (
static_assert(...)): compiler memeriksa kondisi ini saat kompilasi. Bila benar, ia diam; bila salah, kompilasi gagal dengan pesan teks yang kita sediakan. Ini seperti unit test tingkat compiler, dan kita lihat wujud kegagalannya sebentar lagi. - Baris 15-16: dua
constbiasa di dalammain().ukuranLayardanframeBudgetMshanya read-only, bukan harus dihitung compiler — meski di contoh ini nilainya memang konstanta literal, pada program nyata nilainya bisa datang dari runtime.
Mengapa repot memindahkan perhitungan ke compile time? Dua alasan: pekerjaan yang selesai saat kompilasi tidak memakan waktu runtime sama sekali, dan hasilnya bisa menjadi bahan pemeriksaan static_assert maupun syarat struktur bahasa lain (misalnya ukuran array, topik Section Array dan String).
const versus constexpr: Kapan Memakai yang Mana #
| Aspek | const | constexpr |
|---|---|---|
| Jaminan inti | read-only setelah inisialisasi | nilai selesai dihitung saat kompilasi |
| Nilai awal boleh dari runtime | ya | tidak, harus bisa dihitung compiler |
| Dipakai untuk variabel | ya | ya |
| Dipakai untuk fungsi | tidak bermakna | ya (fungsi yang bisa dipanggil compile time) |
| Contoh pemakaian | konstanta dari file konfigurasi | konstanta matematika, ukuran tetap |
Aturan praktis yang lazim dipakai komunitas: kalau nilainya harus diketahui saat kompilasi (ukuran array, template, static_assert), pakai constexpr. Kalau nilainya bisa datang dari runtime tetapi tak boleh berubah lagi, pakai const. Dan bila keduanya benar — konstanta matematika seperti sisi di atas — pilih constexpr, karena ia sekaligus memberi jaminan const.
static_assert dan Literal Type #
static_assert(kondisi, "pesan") telah kita lihat bekerja senyap ketika benar. Kegagalannya justru informatif — berikut varian yang sengaja salah:
constexpr int luas = 49;
static_assert(luas == 50, "luas harus 50");Kompilasi berhenti dengan laporan yang menyertakan pesan kita:
sa-gagal.cpp:2:20: error: static assertion failed: luas harus 50
2 | static_assert(luas == 50, "luas harus 50");
| ~~~~~^~~~~
sa-gagal.cpp:2:20: note: the comparison reduces to ‘(49 == 50)’Satu syarat menutup Chapter ini: untuk dipakai dalam constexpr maupun static_assert, sebuah tipe harus tergolong literal type — tipe yang nilainya bisa dibangun saat kompilasi. Anggota paling umumnya adalah semua tipe aritmetika (int, double, char, bool), enum, pointer, reference, serta class yang constructornya juga constexpr — daftar yang akan bertambah seiring kita berkenalan dengan class di Section Pemrograman Berorientasi Objek.
Konstanta kini aman di tangan kita. Namun ada pertanyaan yang belum dijawab tuntas sejak Chapter char dan bool: apa yang sebenarnya terjadi ketika nilai dari satu tipe dipaksa masuk ke tipe lain? Itulah wilayah konversi tipe data, topik Chapter berikutnya.
Rujukan resmi untuk topik Chapter ini: cv qualifiers (const) , constant expression , dan static_assert di cppreference.