Kadang sebuah fungsi perlu mengembalikan lebih dari satu nilai: hasil bagi sekaligus sisanya, nama sekaligus umur, koordinat x sekaligus y. Membuat struct khusus untuk setiap kasus kecil itu terasa berlebihan, dan standard library menawarkan jalan tengahnya: std::pair<T1, T2> untuk pasangan dua nilai dan std::tuple<T...> untuk tiga nilai atau lebih. Keduanya bukan sekadar pembungkus — mereka diketahui oleh standard library itu sendiri; map menyimpan elemennya sebagai pair, dan banyak algorithm mengembalikan tuple. Chapter ini mengoperasikan keduanya, memakai structured bindings untuk membongkarnya, dan menimbang kapan mereka lebih baik daripada struct.

pair: Dua Nilai Berpasangan #

std::pair menyimpan anggota publik bernama first dan second. Ia sudah sering menyelinap lewat kode kita: kembalian insert pada container asosiatif adalah pair, dan setiap elemen std::map adalah pair<const Kunci, Nilai>.

cpp
#include <iostream>  // std::cout
#include <string>    // std::string
#include <tuple>     // std::tuple, std::get, std::make_tuple, std::tuple_size_v
#include <utility>   // std::pair, std::make_pair

// fungsi mengembalikan DUA nilai sekaligus lewat pair
std::pair<int, int> bagiDenganSisa(int a, int b) {
    return {a / b, a % b};
}

// tuple memperluas ide yang sama untuk tiga nilai atau lebih
std::tuple<std::string, int, double> profilMahasiswa() {
    return {"Budi", 21, 3.85};
}

int main() {
    // --- pair: dua nilai berpasangan ---
    std::pair<std::string, int> produk{"Kopi", 15000};
    std::cout << "pair          : " << produk.first
              << " - Rp" << produk.second << "\n";

    // menerima kembalian pair dengan structured bindings
    auto [hasil, sisa] = bagiDenganSisa(17, 5);
    std::cout << "17 dibagi 5   : hasil=" << hasil
              << ", sisa=" << sisa << "\n";

    // map menyimpan elemen berupa pair<const Kunci, Nilai>
    std::pair<const std::string, int> entri{"teh", 30};
    std::cout << "entri map     : kunci=" << entri.first
              << ", nilai=" << entri.second << "\n";

    // --- tuple: lebih dari dua tipe ---
    auto profil = profilMahasiswa();
    std::cout << "get<0>        : " << std::get<0>(profil) << "\n";
    std::cout << "get<1>        : " << std::get<1>(profil) << "\n";
    std::cout << "get<2>        : " << std::get<2>(profil) << "\n";

    // jumlah elemen tuple diketahui saat kompilasi
    std::cout << "tuple_size_v  : "
              << std::tuple_size_v<decltype(profil)> << "\n";

    // structured bindings membongkar tuple tanpa std::get
    auto [nama, umur, ipk] = profil;
    std::cout << "bongkar       : " << nama << ", " << umur
              << ", IPK " << ipk << "\n";

    // make_pair dan make_tuple menyimpulkan tipe dari argumen
    auto p = std::make_pair(std::string("apel"), 5);
    auto t = std::make_tuple(1, 2.5, 'x');
    std::cout << "make_pair     : " << p.first << " x" << p.second << "\n";
    std::cout << "make_tuple<2> : " << std::get<2>(t) << "\n";

    // membandingkan pair dan tuple: urutan anggotanya yang dibandingkan
    auto a = std::make_pair(1, 10);
    auto b = std::make_pair(1, 20);
    std::cout << "pair a < b    : " << (a < b ? "ya" : "tidak") << "\n";

    return 0;
}

Output yang Dihasilkan:

text
pair          : Kopi - Rp15000
17 dibagi 5   : hasil=3, sisa=2
entri map     : kunci=teh, nilai=30
get<0>        : Budi
get<1>        : 21
get<2>        : 3.85
tuple_size_v  : 3
bongkar       : Budi, 21, IPK 3.85
make_pair     : apel x5
make_tuple<2> : x
pair a < b    : ya

Mari kita bedah:

  1. Baris 7-9 (bagiDenganSisa): fungsi mengembalikan std::pair<int, int> dari dua perhitungan sekaligus — tanpa parameter keluaran (out parameter) dan tanpa struct tambahan.
  2. Baris 23 (auto [hasil, sisa] = ...): structured bindings langsung membongkar kembalian pair menjadi dua nama yang bermakna. Tanpa itu kita menulis std::get<0>(...) dan std::get<1>(...) yang jauh lebih sulit dibaca.
  3. Baris 28 (pair<const std::string, int>): inilah tipe persis yang diberikan oleh iterasi std::map. Perhatikan const pada kunci: map tidak mengizinkan kita mengubah kunci lewat iterasi, dan tipenya mewariskan aturan itu.
  4. Baris 34-36 (std::get<N>): elemen tuple diakses menurut indeks konstanta waktu kompilasi, bukan menurut nama. std::get<0>(profil) menghasilkan std::string, std::get<1> menghasilkan int — tipe kembaliannya ikut mengikuti anggota yang diminta.
  5. Baris 39-40 (tuple_size_v): std::tuple_size_v<decltype(profil)> adalah compile-time query yang menghasilkan 3. Ini berguna dalam kode generik yang perlu tahu panjang tuple tanpa menjalankannya.
  6. Baris 48-51 (make_pair / make_tuple): kedua fungsi murni kemudahan — tipe argumen disimpulkan otomatis, sehingga kita tidak menulis tipe panjang secara eksplisit. Sejak C++17, sintaks auto p = pair{...} dan pengurangan argumen template menjadikan make_* lebih jarang dibutuhkan, tetapi kita tetap akan menemukannya di kode lama.
  7. Baris 56 (a < b): pair dan tuple punya perbandingan bawaan yang bekerja leksikografis: bandingkan first dulu; bila sama, bandingkan second. Karena first keduanya sama-sama 1, keputusan jatuh ke second — 10 < 20 menghasilkan ya.

pair, tuple, atau struct? #

Ketiganya mengelompokkan data, jadi pertimbangannya soal kejelasan:

PilihanKelebihanKekurangan
std::pair / std::tupleringkas, didukung penuh standard libraryanggota bernama first/get<0> — makna mudah kabur
structanggota bernama (nama, umur) — paling terbacaperlu definisi sendiri

Aturan yang sehat: pair/tuple untuk nilai yang maknanya jelas dari konteks dan umur pendek (kembalian fungsi internal, elemen map); struct untuk data yang menyeberangi batas modul atau punya lebih dari tiga anggota. Kode yang membaca std::get<2>(hasil) dalam jumlah besar adalah sinyal untuk beralih ke struct.

Pair dan tuple mengelompokkan nilai yang semuanya ada. Berbeda dengannya, chapter berikutnya membahas nilai yang mungkin tidak ada sama sekali: std::optional, jawaban standar untuk fungsi yang bisa gagal.

Rujukan resmi: cppreference — std::pair , cppreference — std::tuple , cppreference — std::get .