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:

cpp
int main() {
    const int ukuranLayar = 1080;
    ukuranLayar = 2160;
    return 0;
}

g++ menolaknya dengan pesan yang lugas:

text
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:

cpp
#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:

text
Luas persegi : 49
Volume kubus : 27
Ukuran layar : 1080p
Frame budget : 16 ms

Poin-poinnya:

  1. Baris 4-5: luas dihitung dari sisi * sisi. Karena keduanya constexpr, compiler sendiri yang menghitung 49 dan menuliskannya ke dalam program — pada saat runtime, luas sudah adalah angka jadi, mirip literal langsung.
  2. Baris 7-9: fungsi kuadrat ditandai constexpr, artinya compiler boleh menjalankannya saat kompilasi bila argumennya konstanta. Pemanggilan kuadrat(3) pada baris 11 selesai saat kompilasi.
  3. 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.
  4. Baris 15-16: dua const biasa di dalam main(). ukuranLayar dan frameBudgetMs hanya 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 #

Aspekconstconstexpr
Jaminan intiread-only setelah inisialisasinilai selesai dihitung saat kompilasi
Nilai awal boleh dari runtimeyatidak, harus bisa dihitung compiler
Dipakai untuk variabelyaya
Dipakai untuk fungsitidak bermaknaya (fungsi yang bisa dipanggil compile time)
Contoh pemakaiankonstanta dari file konfigurasikonstanta 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:

cpp
constexpr int luas = 49;
static_assert(luas == 50, "luas harus 50");

Kompilasi berhenti dengan laporan yang menyertakan pesan kita:

text
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.