Ketika kita menulis auto x = ..., kita sebenarnya sedang meminta compiler melakukan pekerjaan yang dulu kita lakukan manual: membaca tipe inisialisasi, menerapkan aturan deduksi, lalu memutuskan tipe akhir variabel. Pekerjaan ini punya aturan yang tegas dan konsisten — bukan tebakan — dan memahami aturannya adalah pembeda antara auto yang membuat kode lebih aman atau lebih licik. Chapter Uniform Initialization ditutup dengan pertanyaan “tipe apa yang compiler lihat dari inisialisasi itu?”; chapter ini menjawabnya secara sistematis: auto untuk variabel, kembalian fungsi (C++14), dan parameter (C++20), lalu decltype yang membaca tipe tanpa mengevaluasi ekspresi, decltype(auto) yang menjaga referensi, dan structured bindings yang menyulap satu deklarasi auto menjadi beberapa nama sekaligus. Semua aturan kita buktikan lewat static_assert — pemeriksaan tipe yang dijalankan compiler saat kompilasi, sehingga klaim di chapter ini bukan opini melainkan fakta yang diverifikasi.
Program Pengujian Aturan Deduksi #
// type-deduction.cpp — aturan deduksi auto/decltype yang dipraktikkan
// Kompilasi: g++ -std=c++23 -Wall -Wextra type-deduction.cpp -o type-deduction
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
int g = 0;
int& ambilRef() { return g; } // mengembalikan referensi ke global
// return type deduction (C++14): tipe kembalian disimpulkan
constexpr auto kuadrat(int x) { return x * x; }
// decltype(auto): MEMPERTAHANKAN tipe penuh, termasuk referensinya
decltype(auto) lewatRef() { return ambilRef(); }
// auto pada parameter (C++20) = singkatan template
auto dobel(auto x) { return x * 2; }
int main() {
int sumber = 10;
const int& ref = sumber;
// Aturan inti: auto by value MELEPAS const dan reference -> salinan
auto a = ref; // a: int (salinan)
auto& b = sumber; // b: int&
const auto& c = ref; // c: const int&
static_assert(std::is_same_v<decltype(a), int>);
static_assert(std::is_same_v<decltype(b), int&>);
static_assert(std::is_same_v<decltype(c), const int&>);
a = 99; // mengubah SALINAN
std::cout << "auto = ref -> sumber tetap: " << sumber
<< ", a jadi: " << a << "\n";
b = 7; // mengubah sumber lewat referensi
std::cout << "auto& -> sumber ikut : " << sumber << "\n";
// Konsekuensinya di range-for
std::vector<int> v{1, 2, 3};
for (auto x : v) { // salinan tiap elemen
x *= 10; // mengubah salinan: v tetap
}
std::cout << "loop by value: " << v[0] << v[1] << v[2] << "\n";
for (auto& x : v) { // referensi ke elemen asli
x *= 10; // mengubah v sungguhan
}
std::cout << "loop auto& : " << v[0] << v[1] << v[2] << "\n";
// decltype: tipe dari ekspresi, tanpa mengevaluasi ekspresinya
decltype(v.size()) n = v.size(); // size_type, bukan int
static_assert(std::is_same_v<decltype(n), std::size_t>);
std::cout << "decltype : n=" << n << "\n";
int& r = ambilRef();
decltype(r) r2 = g; // decltype mempertahankan int&
r2 = 1;
std::cout << "decltype(int&) -> g=" << g << "\n";
// decltype(auto) pada return: referensi tidak dilepas
lewatRef() = 42;
std::cout << "decltype(auto) -> g=" << g << "\n";
static_assert(kuadrat(5) == 25); // kuadrat constexpr: dihitung saat kompilasi
std::cout << "auto return : " << kuadrat(6) << ", dobel: "
<< dobel(21) << " " << dobel(2.5) << "\n";
// structured bindings: keluarga auto untuk membongkar pasangan
std::pair<int, std::string> pasangan{5, "MERAH"};
auto& [nomor, label] = pasangan; // binding sebagai referensi
nomor = 6;
label = "BIRU";
std::cout << "binding auto&: " << pasangan.first
<< " " << pasangan.second << "\n";
return 0;
}Output yang Dihasilkan:
auto = ref -> sumber tetap: 10, a jadi: 99
auto& -> sumber ikut : 7
loop by value: 123
loop auto& : 102030
decltype : n=3
decltype(int&) -> g=1
decltype(auto) -> g=42
auto return : 36, dobel: 42 5
binding auto&: 6 BIRUMari kita bedah:
- Baris 27-32 (aturan inti):
auto a = ref;menghasilkanint, bukanconst int&. Saat menduksi by value, compiler melepas reference danconstpuncak dari tipe sumber, lalu membuat salinan. Tigastatic_assertdi baris 30-32 membuktikan tipe hasilnya:int,int&, danconst int&— masing-masing persis seperti yang kita tulis. - Baris 34-38 (akibat perilaku): mengisi
a = 99mengubah salinan, sehinggasumbertetap 10; mengisib = 7mengubahsumbersungguhan lewat referensi, sehingga tercetak 7. Ini alasan bug berbasisautosering “diam”: keduanya kompilasi normal, bedanya hanya siapa yang berubah. - Baris 42-49 (konsekuensi di loop):
for (auto x : v)menyalin tiap elemen sehinggavtetap1 2 3;for (auto& x : v)mengikat referensi ke elemen asli sehinggavberubah menjadi10 20 30. Pola ini akan kita perdalam pada chapter range-based for. - Baris 52-54 (decltype):
decltype(v.size())menghasilkanstd::size_t— tipe kembaliansize()yang panjang dan mudah salah diketik — tanpa kita perlu tahu menulisnya.static_assertmengonfirmasi bahwanbenar-benarstd::size_t, bukanintyang kebetulan cocok. - Baris 56-59 (decltype menjaga referensi): karena
rbertipeint&,decltype(r)menghasilkanint&— referensinya dipertahankan. Mengisir2 = 1mengubahgyang dirujuk, terbukti dari keluarang=1. - Baris 62-63 (decltype(auto)):
lewatRef() = 42;legal dan mengubahgmenjadi 42 — artinya kembalian fungsi ini adalah referensi hidup, bukan salinan. Bandingkan bila kembaliannya ditulisauto: salinanintdikembalikan dan penugasan seperti itu tidak bermakna. - Baris 65-67 (auto pada return dan parameter):
kuadratmenyimpulkan kembaliannyaint(C++14), bahkan bisaconstexpr;dobelmemakai parameterauto(C++20) sehingga satu fungsi melayanidobel(21)dandobel(2.5)— di belakang layar compiler membangun template dengan dua instansiasi.
Aturan Ringkas Deduksi auto #
Aturan di atas bisa diringkas ke dalam satu kalimat: by value mengupas, by reference mengawetkan. Tabel berikut memetakan bentuk deklarasi ke efeknya, dengan ref bertipe const int&:
| Bentuk deklarasi | Tipe hasil | Efek |
|---|---|---|
auto x = ref; | int | salinan; const dan & dilepas |
auto& x = ref; | const int& | alias; const sumber dipertahankan |
const auto& x = ref; | const int& | alias baca-saja, aman untuk data besar |
auto&& x = ref; | mengikuti sisi kanan | forwarding reference — lvalue menjadi &, rvalue menjadi && |
Baris terakhir memperkenalkan istilah yang akan penting di chapter Perfect Forwarding: auto&& bukan “referensi rvalue”, melainkan forwarding reference yang menyesuaikan diri dengan sisi kanannya — lvalue terikat sebagai int&, sementara sementara (temporary) terikat sebagai int&&. Untuk variabel biasa sehari-hari, kita cukup mengingat dua pilihan utama: salin dengan auto bila memang ingin kepemilikan sendiri, dan ambil const auto& bila hanya membaca data yang berat seperti std::string atau std::vector.
Ada satu variasi kurung kurawal yang jarang disadari:
auto a{5}; // int — satu nilai tunggal di dalam {}
auto b = {5}; // std::initializer_list<int> — tanda = mengubah makna
auto c{5, 10}; // error sejak C++17: kurung kurawal multi-elemen ditolak
Tanda = di baris kedua membuat compiler membaca {5} sebagai daftar, sehingga tipe yang diduksi adalah std::initializer_list<int> — bukan int. Karena std::initializer_list merujuk data di belakang layar, menyimpannya ke variabel yang hidup lebih lama dari daftarnya adalah jebakan; deklarasi auto a{5}; bebas dari masalah itu.
decltype: Tipe dari Ekspresi #
decltype dan auto sering disandingkan padahal keduanya menjawab pertanyaan berbeda. auto berarti “buat variabel ini dengan tipe yang cocok untuk inisialisasi ini”, sedangkan decltype berarti “sebutkan tipe dari ekspresi ini” — tanpa mengeksekusinya, tanpa membuat variabel. Karena tidak mengevaluasi, decltype boleh menyebut ekspresi yang tidak sah saat runtime, misalnya decltype(f()) x; mendeklarasikan x dengan tipe kembalian f() tanpa benar-benar memanggil f().
Dua kegunaan praktisnya terlihat pada program di atas. Pertama, tipe yang panjang seperti kembalian v.size() tidak perlu diketik ulang (baris 52) — kita tetap mendapat tipe yang presisi, bukan int yang berpotensi terpotong di platform berbeda. Kedua, decltype menjaga referensi: decltype(r) menghasilkan int& sedangkan auto x = r; hanya menghasilkan int. Saat menulis kode generik dengan template, perbedaan ini menentukan apakah kita meneruskan referensi atau memotongnya diam-diam.
decltype(auto): auto yang Menjaga Referensi #
Fungsi lewatRef() pada baris 17 mengembalikan decltype(auto), bukan auto. Jika kita menulis auto lewatRef(), compiler menerapkan aturan by value: tipe kembalian menjadi int, referensi dilepas, dan pemanggil menerima salinan — penugasan lewatRef() = 42; bahkan tidak akan dikompilasi. Dengan decltype(auto), compiler menyimpulkan tipe persis seperti decltype pada ekspresi kembaliannya, sehingga int& tetap int& dan penugasan lewat kembalian mengubah objek aslinya.
Kapan kita memakainya? Terutama di kode template atau wrapper yang meneruskan kembalian fungsi lain — kita belum tahu apakah fungsi yang dibungkus mengembalikan nilai atau referensi, dan decltype(auto) meneruskan apa adanya tanpa memaksa salinan. Untuk fungsi biasa dengan tipe yang sudah jelas, menulis tipe kembalian secara eksplisit tetap lebih mudah dibaca. Anggap decltype(auto) sebagai alat presisi di kotak peralatan: jarang terpakai, tetapi saat dibutuhkan tidak ada penggantinya.
Structured Bindings: Anggota Keluarga auto #
Baris 71 menunjukkan bentuk deklarasi yang masih satu keluarga: auto& [nomor, label] = pasangan;. Di sini compiler menduksi tipe setiap elemen std::pair lalu mengikatnya ke dua nama — nomor dan label bukan variabel baru, melainkan nama alternatif untuk pasangan.first dan pasangan.second. Modifier &, const auto&, atau auto polos berlaku dengan aturan yang sama seperti tabel di atas: by value menyalin, by reference mengalias.
Mengubah nomor = 6 dan label = "BIRU" langsung mengubah pasangan — keluaran 6 BIRU membuktikannya. Fitur ini lahir di C++17 dan menjadi gaya bawaan ketika mengiterasi std::map atau menerima beberapa kembalian sekaligus; karena cukup penting, kita bedah tuntas pada chapter Structured Bindings yang membahasnya untuk struct, std::pair, std::tuple, dan std::array.
Dengan aturan deduksi ini, auto berhenti terasa seperti tebak-tebakan dan mulai terasa seperti kontrak yang bisa kita baca. Bab berikutnya mengajak deduksi ini bekerja di tempat yang paling sering kita tulis sehari-hari: loop range-based for, lengkap dengan perangkap umur temporary dan cara membuat class sendiri ikut didukung.
Rujukan resmi: cppreference — auto , cppreference — decltype , cppreference — structured binding declaration .