std::variant<A, B> menampung salah satu dari daftar tipe yang kita tulis sendiri — daftar yang tertutup. Ada situasi yang justru kebalikannya: fungsi menyimpan “nilai apa pun” tanpa tahu sejak awal tipenya, misalnya metadata plugin, properti konfigurasi, atau data hasil parsing yang bentuknya baru diketahui saat runtime. Untuk kebutuhan terbuka itulah <any> menyediakan std::any: wadah yang bisa menampung tipe apa pun yang bisa disalin, lengkap dengan pemeriksaan tipe yang aman. Kebebasan ini ada harganya — pemeriksaan tipe dan pengambilan nilai dilakukan pada runtime — sehingga any adalah alat terakhir dalam daftar, dipakai ketika variant tidak memungkinkan. Chapter ini mengoperasikan siklus hidup any lengkap dan menimbang kapan ia layak dipilih.

Wadah Serbaguna dan Aturan Mainnya #

Siklus hidup std::any sederhana: mulai kosong, diisi dengan tipe apa pun (mengganti isi berarti menghapus isi lama), diperiksa lewat has_value() dan type(), dibaca lewat std::any_cast, dan bisa dikosongkan lagi dengan reset(). Dua bentuk any_cast penting dibedakan: bentuk by value any_cast<T>(a) melempar std::bad_any_cast bila tipe tidak cocok, sedangkan bentuk pointer any_cast<T>(&a) mengembalikan nullptr bila tidak cocok — bentuk kedua inilah yang kita pakai agar contoh tetap berjalan tanpa exception (materi Section Error Handling).

cpp
#include <any>       // std::any, std::any_cast
#include <iostream>  // std::cout
#include <string>    // std::string

int main() {
    std::any kotak;   // kosong saat lahir

    std::cout << "ada isi?        : " << kotak.has_value() << "\n";

    // tampung int
    kotak = 42;
    std::cout << "isi int        : " << std::any_cast<int>(kotak) << "\n";
    std::cout << "tipe == int?   : " << (kotak.type() == typeid(int)) << "\n";

    // ganti ke string
    kotak = std::string("teks bebas");
    std::cout << "isi string     : " << std::any_cast<std::string>(kotak) << "\n";
    std::cout << "tipe == string?: "
              << (kotak.type() == typeid(std::string)) << "\n";

    // ganti ke double
    kotak = 3.5;
    std::cout << "tipe == double?: " << (kotak.type() == typeid(double)) << "\n";

    // any_cast lewat pointer: tidak melempar exception, kembalian nullptr bila salah tipe
    if (int* p = std::any_cast<int>(&kotak)) {
        std::cout << "int            : " << *p << "\n";
    } else {
        std::cout << "int            : nullptr (bukan int)\n";
    }
    if (double* d = std::any_cast<double>(&kotak)) {
        std::cout << "double         : " << *d << "\n";
    }

    // reset: kembalikan kotak ke keadaan kosong
    kotak.reset();
    std::cout << "setelah reset  : ada isi? "
              << (kotak.has_value() ? "ya" : "tidak") << "\n";

    return 0;
}

Output yang Dihasilkan:

text
ada isi?        : 0
isi int        : 42
tipe == int?   : 1
isi string     : teks bebas
tipe == string?: 1
tipe == double?: 1
int            : nullptr (bukan int)
double         : 3.5
setelah reset  : ada isi? tidak

Mari kita bedah:

  1. Baris 6-8 (std::any kotak;): any yang baru dibuat kosong; has_value() melapor 0 (false). Bandingkan dengan variant yang selalu berisi alternatif pertamanya.
  2. Baris 11-13 (kotak = 42): penugasan menyalin nilai int ke dalam any. any_cast<int>(kotak) membaca kembali nilainya — bentuk by value ini akan melempar std::bad_any_cast bila kita meminta tipe yang salah.
  3. Baris 13, 17, 19 (type() == typeid(...)): type() mengembalikan informasi tipe runtime yang bisa kita bandingkan dengan typeid. Perbandingan menghasilkan bool deterministik — berbeda dari mencetak type().name(), yang nama hasilnya berbeda antar compiler (di g++ berupa nama singkat seperti i untuk int).
  4. Baris 14-16 (kotak = std::string(...)): mengganti isi dari int ke string. Objek int lama dihapus, string baru disalin masuk — any mengurus kedua langkah itu.
  5. Baris 25-30 (any_cast bentuk pointer): any_cast<int>(&kotak) mengembalikan int* bila isi memang int, atau nullptr bila bukan. Karena isinya saat itu double, percobaan pertama jatuh ke cabang else — pemeriksaan aman tanpa exception.
  6. Baris 36-38 (reset()): mengosongkan kembali kotak; has_value() kembali 0 dan sumber daya milik isi lama dibebaskan.

Biaya yang Kita Bayar #

std::any bukan tipe gratis. Ia menyimpan nilai di memori internal (kadang small buffer, kadang alokasi heap bila nilainya besar), mengingat tipenya lewat mekanisme runtime, dan setiap pembacaan wajib melewati pemeriksaan any_cast. Ditambah lagi, compiler tidak bisa memeriksa kesalahan tipe pada waktu kompilasi — kesalahan hanya terungkap saat program berjalan. Karena itu urutan pertimbangan kita sebaiknya selalu: struct bila bentuknya pasti, optional bila mungkin kosong, variant bila pilihannya terbatas, dan any hanya bila daftar tipenya benar-benar terbuka.

PertanyaanAlat yang cocok
“Tipe pasti, tapi mungkin tidak ada”std::optional<T>
“Salah satu dari tipe A, B, C (tertulis)”std::variant<A, B, C>
“Tipe apa pun, belum diketahui saat menulis kode”std::any

Dengan tiga tipe utilitas nilai ini tuntas, kita beralih ke utilitas yang bekerja pada kumpulan data: std::span, jendela tanpa kepemilikan ke data berurutan.

Rujukan resmi: cppreference — std::any , cppreference — any_cast .