Pada Chapter Union kita melihat union: satu area memori yang bisa dibaca sebagai tipe berbeda — cepat, tetapi penuh jebakan karena compiler tidak melacak tipe mana yang sedang aktif. std::variant<A, B, ...> (header <variant>, sejak C++17) adalah jawaban modern untuk kebutuhan yang sama: ia menampung satu nilai yang bertipe salah satu dari daftar tipe yang kita tetapkan, sambil mengingat tipe mana yang sedang aktif. Inilah type-safe union: salah tipe terdeteksi saat runtime secara terkendali, dan daftar tipe yang sah tertulis jelas pada deklarasinya. Chapter ini mengoperasikan variant dari tiga sisi — pemeriksaan tipe, akses aman, dan pemrosesan menyeluruh dengan std::visit.
Tipe yang Berubah, Pengetahuan yang Ikut #
Berbeda dari union yang “percaya saja” apa yang kita tulis, variant memakai discriminator internal untuk mencatat alternatif aktifnya. Kita bisa membaca catatan itu lewat index() atau holds_alternative<T>(), mengambil isinya dengan std::get<T> (yang menolak dengan exception bila salah tipe), atau memakai std::get_if<T> yang berbentuk pointer dan tak pernah melempar.
#include <iostream> // std::cout
#include <string> // std::string
#include <type_traits> // std::is_same_v, std::decay_t
#include <variant> // std::variant, std::visit, std::holds_alternative, std::get_if
// visit dengan lambda: jalur pemrosesan untuk setiap tipe
void tampilkan(const std::variant<int, double, std::string>& v) {
std::visit([](const auto& isi) {
using T = std::decay_t<decltype(isi)>;
if constexpr (std::is_same_v<T, int>) {
std::cout << " bilangan bulat : " << isi << "\n";
} else if constexpr (std::is_same_v<T, double>) {
std::cout << " bilangan desimal: " << isi << "\n";
} else {
std::cout << " teks : " << isi << "\n";
}
}, v);
}
int main() {
// satu nilai yang bisa berupa salah satu dari tiga tipe
std::variant<int, double, std::string> nilai;
// --- mengganti tipe yang ditampung ---
nilai = 42;
std::cout << "nilai = 42 -> index " << nilai.index()
<< ", int? " << std::holds_alternative<int>(nilai) << "\n";
std::cout << "get<int> : " << std::get<int>(nilai) << "\n";
nilai = 3.14;
std::cout << "nilai = 3.14 -> index " << nilai.index()
<< ", int? " << std::holds_alternative<int>(nilai) << "\n";
nilai = std::string("standard");
std::cout << "nilai = \"standard\" -> index " << nilai.index()
<< ", string? " << std::holds_alternative<std::string>(nilai) << "\n";
// --- akses aman tanpa exception: get_if mengembalikan pointer ---
if (const std::string* s = std::get_if<std::string>(&nilai)) {
std::cout << "get_if<string> : \"" << *s << "\" (panjang "
<< s->size() << ")\n";
}
if (std::get_if<int>(&nilai) == nullptr) {
std::cout << "get_if<int> : nullptr (bukan int)\n";
}
// --- visit: satu pemanggilan untuk semua kemungkinan tipe ---
std::cout << "visit 42:\n";
nilai = 42;
tampilkan(nilai);
std::cout << "visit 2.5:\n";
nilai = 2.5;
tampilkan(nilai);
std::cout << "visit \"tes\":\n";
nilai = std::string("tes");
tampilkan(nilai);
return 0;
}Output yang Dihasilkan:
nilai = 42 -> index 0, int? 1
get<int> : 42
nilai = 3.14 -> index 1, int? 0
nilai = "standard" -> index 2, string? 1
get_if<string> : "standard" (panjang 8)
get_if<int> : nullptr (bukan int)
visit 42:
bilangan bulat : 42
visit 2.5:
bilangan desimal: 2.5
visit "tes":
teks : tesMari kita bedah:
- Baris 22 (
std::variant<int, double, std::string>): daftar tipe di sudut kurung adalah kontrak variant — tidak ada tipe lain yang bisa masuk. Objek yang baru dibuat menampung alternatif pertama (int) yang diinisialisasi 0. - Baris 25-28: penugasan
nilai = 42mengganti alternatif aktif menjadi int;index()melapor0(posisi int dalam daftar) danholds_alternative<int>melapor1(true).std::get<int>mengambil isinya — bila kita menulisstd::get<std::string>pada keadaan ini, program melemparstd::bad_variant_access(kita bahas exception di Section Error Handling). - Baris 30-36: setiap penugasan dengan tipe berbeda memindahkan alternatif aktif:
3.14menjadikan index1, string menjadikan2. Nilai lama dihancurkan dengan benar — inilah yang tidak dijamin union. - Baris 39-45 (
get_if):std::get_if<T>(&nilai)mengembalikanconst T*— pointer bila tipe cocok,nullptrbila tidak. Karena tidak melempar exception, polaif (auto* p = get_if...)adalah akses paling tenang untuk pemeriksaan tunggal. - Baris 8-17 (
std::visit+ lambda):visitmemanggil lambda kita sekali, dengan parameterisibertipe alternatif yang sedang aktif. Lambda generik (pembahasan pada Chapter Generic Lambda) menerima tipe apa pun;if constexprmemilih cabang pada waktu kompilasi menurut tipe tersebut. - Baris 48-56 (tiga kunjungan): nilai yang sama diperlakukan berbeda menurut tipenya —
42dicetak sebagai bilangan bulat,2.5sebagai desimal,"tes"sebagai teks. Tidak adaswitchmanual, tidak ada penyimpanan tag tersendiri: variant mengelolanya.
variant vs union #
Keduanya menampung “salah satu dari beberapa tipe”, tetapi tingkat keamanannya berbeda jauh:
| Aspek | union | std::variant |
|---|---|---|
| Tahu tipe aktif | tidak (kita kelola sendiri) | ya (internal) |
| Salah membaca tipe | undefined behavior | exception terkendali / nullptr dari get_if |
| Anggota bertipe kompleks (string, vector) | tidak praktis | didukung penuh |
| Ukuran | ukuran anggota terbesar | ukuran terbesar + sedikit overhead pencatat |
| Cocok untuk | kode sistem yang sangat ketat | hampir semua kebutuhan program biasa |
Aturan praktis kita di section ini: pilih variant kecuali ada alasan ukuran dan determinisme yang sangat kuat untuk memakai union mentah.
Variant menampung nilai dari daftar tipe yang tertutup — kita menuliskannya semua saat deklarasi. Bila kebutuhan kita justru “tipe apa pun, belum tentu kita tahu saat menulis kodenya”, chapter berikutnya menyajikan std::any beserta harganya.
Rujukan resmi: cppreference — std::variant , cppreference — std::visit , cppreference — std::get_if .