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.

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

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

Mari kita bedah:

  1. 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.
  2. Baris 25-28: penugasan nilai = 42 mengganti alternatif aktif menjadi int; index() melapor 0 (posisi int dalam daftar) dan holds_alternative<int> melapor 1 (true). std::get<int> mengambil isinya — bila kita menulis std::get<std::string> pada keadaan ini, program melempar std::bad_variant_access (kita bahas exception di Section Error Handling).
  3. Baris 30-36: setiap penugasan dengan tipe berbeda memindahkan alternatif aktif: 3.14 menjadikan index 1, string menjadikan 2. Nilai lama dihancurkan dengan benar — inilah yang tidak dijamin union.
  4. Baris 39-45 (get_if): std::get_if<T>(&nilai) mengembalikan const T* — pointer bila tipe cocok, nullptr bila tidak. Karena tidak melempar exception, pola if (auto* p = get_if...) adalah akses paling tenang untuk pemeriksaan tunggal.
  5. Baris 8-17 (std::visit + lambda): visit memanggil lambda kita sekali, dengan parameter isi bertipe alternatif yang sedang aktif. Lambda generik (pembahasan pada Chapter Generic Lambda) menerima tipe apa pun; if constexpr memilih cabang pada waktu kompilasi menurut tipe tersebut.
  6. Baris 48-56 (tiga kunjungan): nilai yang sama diperlakukan berbeda menurut tipenya — 42 dicetak sebagai bilangan bulat, 2.5 sebagai desimal, "tes" sebagai teks. Tidak ada switch manual, tidak ada penyimpanan tag tersendiri: variant mengelolanya.

variant vs union #

Keduanya menampung “salah satu dari beberapa tipe”, tetapi tingkat keamanannya berbeda jauh:

Aspekunionstd::variant
Tahu tipe aktiftidak (kita kelola sendiri)ya (internal)
Salah membaca tipeundefined behaviorexception terkendali / nullptr dari get_if
Anggota bertipe kompleks (string, vector)tidak praktisdidukung penuh
Ukuranukuran anggota terbesarukuran terbesar + sedikit overhead pencatat
Cocok untukkode sistem yang sangat ketathampir 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 .