Saat koleksi data kita tumbuh lebih besar dari sekadar demo, logika pengolahannya berpindah ke dalam fungsi: satu fungsi menghitung rata-rata, satu lagi mencari nilai maksimum. Di titik ini array C-style menunjukkan kelemahan paling fundamentalnya. Ketika array dikirim sebagai parameter, compiler mengubahnya menjadi pointer ke elemen pertama — seluruh informasi ukurannya tidak ikut terkirim. Konversi ini bernama array decay dan menjadi alasan di balik pola parameter yang selama ini kita lihat di kode nyata: const int* arr, int n. Pada Chapter ini kita membuktikan decay secara langsung, membangun pola parameter yang aman, dan mengenal std::span sebagai solusi modernnya.

Array Decay: Ukuran yang Hilang #

Pointer adalah variabel yang menyimpan alamat memori; pembahasannya tuntas pada Section Pointer dan Reference. Untuk sekarang cukup tahu dua hal: pointer hanya menunjuk lokasi elemen pertama, dan ukuran pointer selalu sama (8 byte di platform 64-bit) apa pun panjang array aslinya. Program berikut membuktikan perbedaannya secara nyata:

cpp
#include <iostream>

// int arr[] pada parameter sebenarnya adalah int* — ukuran TIDAK dibawa.
// Karena itu fungsi wajib menerima jumlah elemen sebagai parameter kedua.
double rataRata(const int* arr, int n) {
    if (n == 0) return 0.0;

    int total = 0;
    for (int i = 0; i < n; ++i) {
        total += arr[i];
    }
    return static_cast<double>(total) / n;
}

// Bentuk "const int arr[]" setara persis dengan "const int* arr"
int maksimum(const int arr[], int n) {
    int terbesar = arr[0];
    for (int i = 1; i < n; ++i) {
        if (arr[i] > terbesar) terbesar = arr[i];
    }
    return terbesar;
}

// Bukti decay: sizeof(arr) di dalam fungsi adalah ukuran pointer, bukan array
void tampilkanUkuran(const int arr[]) {
    std::cout << "sizeof(arr) di dalam fungsi: " << sizeof(arr)
              << " byte (bukan 20)\n";
}

int main() {
    int nilai[] = {70, 85, 60, 90, 75};
    int jumlah = sizeof(nilai) / sizeof(nilai[0]);  // 5

    std::cout << "Jumlah elemen      : " << jumlah << "\n";
    std::cout << "sizeof(nilai) di main: " << sizeof(nilai) << " byte\n";
    std::cout << "Rata-rata          : " << rataRata(nilai, jumlah) << "\n";
    std::cout << "Maksimum           : " << maksimum(nilai, jumlah) << "\n";
    tampilkanUkuran(nilai);

    return 0;
}

Output yang Dihasilkan:

text
Jumlah elemen      : 5
sizeof(nilai) di main: 20 byte
Rata-rata          : 76
Maksimum           : 90
sizeof(arr) di dalam fungsi: 8 byte (bukan 20)

Perhatikan kontras dua baris terakhir angka sizeof: di main() array aslinya tetap utuh sebesar 20 byte, tetapi begitu tiba di dalam fungsi, parameter yang diterima hanya pointer sebesar 8 byte. Menariknya, GCC sendiri mengingatkan kita lewat peringatan kompilasi yang disengaja muncul pada program ini:

text Peringatan compiler pada decay.cpp
warning: 'sizeof' on array function parameter 'arr' will return size of 'const int*' [-Wsizeof-array-argument]
   26 |     std::cout << "sizeof(arr) di dalam fungsi: " << sizeof(arr)
      |                                                           ~^~~~

Pesan -Wsizeof-array-argument adalah pengingat resmi dari compiler: parameter yang ditulis const int arr[] pada fungsi bukan array, melainkan const int*. Sintaks arr[] hanyalah gula notasi yang lebih mudah dibaca; kedua bentuk pada baris 6 dan 19 menghasilkan fungsi yang identik. Peringatan ini memang sengaja kita biarkan di program di atas karena isinya persis materi Chapter ini.

Mari kita bedah bagian penting lainnya:

  1. Baris 6 (double rataRata(const int* arr, int n)): pola standar pengiriman array — alamat elemen pertama disertai jumlah elemen. Kata const melarang fungsi mengubah data milik pemanggil; bentuknya diperdalam pada Section Pointer dan Reference.
  2. Baris 7 (if (n == 0) return 0.0;): fungsi membela diri dari pembagian nol. Karena ukuran tidak pernah otomatis, pengirim yang salah mengirim n tidak bisa dicegah compiler.
  3. Baris 12-13 (total += arr[i];): di dalam fungsi, akses indeks tetap ditulis seperti biasa. Compiler menerjemahkannya menjadi aritmetika alamat dari pointer arr.
  4. Baris 25-26 (sizeof(nilai) / sizeof(nilai[0])): perhitungan jumlah elemen hanya sah di tempat array dideklarasikan. Begitu array telah decay, trik ini tidak lagi menghasilkan ukuran asli.

Pola const int* + ukuran, dan Mekanisme di Baliknya #

Mengapa C++ mendesain decay seperti ini? Alasannya performa: menyalin seluruh array setiap kali dipanggil akan sangat mahal, sehingga compiler hanya mengirim alamatnya — satu langkah copy yang murah dan konstan. Efek sampingnya, fungsi menerima akses langsung ke array asli pemanggil, bukan salinannya. Karena itu pengubahan elemen di dalam fungsi akan terlihat oleh pemanggil, dan kata const menjadi penting sebagai kontrak bahwa fungsi hanya membaca.

Kehilangan ukuran lalu dikompensasi dengan konvensi. Selain mengirim n secara eksplisit, ada dua pola umum yang masih sering ditemui di kode lama: mengirim elemen sentinel penanda akhir (seperti '\0' pada C-style string yang kita bahas di Chapter berikutnya), atau mendokumentasikan batas minimum di komentar. Keduanya rapuh dibanding pengiriman n yang eksplisit, sehingga pola (const int* arr, int n) tetap rekomendasi utama saat kita harus menerima array C-style.

std::span: Array yang Membawa Ukurannya #

C++20 menambahkan tipe std::span yang menyelesaikan masalah decay dengan elegan: satu parameter membawa alamat data sekaligus jumlah elemen. span bersifat non-owning — ia tidak memiliki datanya, hanya “melihat” milik pemanggil, sehingga tetap semurah mengirim dua angka. Program berikut menulis ulang fungsi rata-rata memakai span:

cpp
#include <iostream>
#include <span>

// std::span membawa alamat data SEKALIGUS jumlah elemen.
// Tipe template dibahas pada Section Generic Programming dan Templates;
// untuk sekarang cukup baca: "span yang berisi int konstan".
double rataRata(std::span<const int> data) {
    if (data.size() == 0) return 0.0;

    int total = 0;
    for (int x : data) {
        total += x;
    }
    return static_cast<double>(total) / data.size();
}

int main() {
    int nilai[] = {70, 85, 60, 90, 75};

    // array C-style otomatis disiapkan menjadi span
    std::cout << "Rata-rata semua : " << rataRata(nilai) << "\n";

    // .first(3) mengambil potongan 3 elemen pertama tanpa menyalin data
    std::cout << "Rata-rata 3 awal: " << rataRata(std::span{nilai}.first(3))
              << "\n";

    return 0;
}

Output yang Dihasilkan:

text
Rata-rata semua : 76
Rata-rata 3 awal: 71.6667

Bandiket fungsi rataRata versi span: tidak ada parameter n, tidak ada risiko jumlah elemen salah kirim, dan data.size() selalu akurat karena ukurannya ikut bepergian dengan data. Pemanggil rataRata(nilai) bahkan tidak perlu konversi eksplisit — array C-style menyesuaikan diri menjadi span secara otomatis. Tipe std::span dan teman-temannya dibedah lebih dalam pada Section Standard Library; yang perlu terbawa dari sini adalah polanya.

Chapter berikutnya memperkenalkan std::array, wadah ukuran tetap yang mengatasi decay sepenuhnya: ukurannya melekat pada tipenya sendiri, sehingga bisa disalin, dikembalikan dari fungsi, dan tidak pernah lupa seberapa panjang ia.

Rujukan resmi: cppreference — std::span , cppreference — Array declaration .