Algoritma STL hanya mengenal satu bahasa: iterator. Ketika rentang tujuan berupa vector yang masih kosong, posisi di tengah list, atau sebuah stream, kita tidak selalu punya iterator yang pas — dan di sinilah iterator adapter berperan. Adapter adalah pembungkus tipis yang menerjemahkan satu operasi tulis iterator menjadi operasi lain: std::back_inserter memanggil push_back, std::front_inserter memanggil push_front, std::inserter memanggil insert pada posisi tertentu, sedangkan std::istream_iterator dan std::ostream_iterator menjadikan stream sumber dan tujuan algoritma. Pada Chapter ini kita mengenal keempat keluarga adapter tersebut, membongkar mekanismenya, dan menguji semuanya dalam dua program utuh.

back_inserter: Tujuan yang Tumbuh Sendiri #

Semua algoritma menulis hasil dengan pola yang sama: *tujuan = nilai, lalu maju satu langkah. Pola ini menuntut tempat yang sudah ada — algoritma tidak pernah memeriksa ukuran container tujuan, apalagi memperbesarnya. Menulis ke v.begin() pada vector kosong berarti menulis di luar batas dan menghasilkan undefined behavior. Keluarga algoritma std::copy akan kita bedah tuntas pada Chapter Algoritma copy dan move; fokus Chapter ini adalah sisi tujuannya.

std::back_inserter(c) menjawab masalah tempat itu. Helper dari header <iterator> ini menghasilkan objek std::back_insert_iterator yang menyimpan alamat container, dan setiap penulisan *it = nilai diterjemahkan menjadi c.push_back(nilai). Karena setiap elemen yang ditulis algoritma langsung ditambahkan ke akhir, tujuan tumbuh mengikuti jumlah elemen sumber tanpa satu panggilan resize pun. Semua container yang menyediakan push_back — std::vector, std::list, std::deque, std::string — bisa menjadi sasaran adapter ini.

Pipeline Stream dengan istream_iterator dan ostream_iterator #

Header <iterator> juga menyediakan dua adapter yang menjadikan stream anggota penuh ekosistem algoritma. std::istream_iterator<T> membaca satu objek T dari stream saat dibangun, lalu setiap ++ menarik nilai berikutnya; istream_iterator yang dibangun tanpa argumen berlaku sebagai iterator akhir yang menyatakan stream sudah habis. Sebaliknya, std::ostream_iterator<T>(stream, pembatas) mencetak setiap nilai yang ditulis ke stream, lalu menyambung teks pembatas. Kombinasi keduanya membentuk pipeline input-ke-output tanpa satu loop manual pun.

Program berikut memakai std::stringstream statis sebagai sumber data sehingga output tetap deterministik — istream_iterator tidak harus menunggu ketikan pengguna, karena kita bisa mengarahkannya ke teks yang sudah berisi angka. Aliran datanya: stream dibaca istream_iterator, dikumpulkan std::copy ke back_inserter, lalu dicetak kembali lewat ostream_iterator:

cpp
#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>

int main() {
    // Sumber input statis berupa stringstream sehingga output deterministik
    std::istringstream masukan("42 7 13 8 5");

    // istream_iterator<int> membaca angka satu per satu dari stream
    std::istream_iterator<int> awal(masukan);
    std::istream_iterator<int> akhir;  // iterator akhir: tanda stream habis

    // back_inserter membuat vector tumbuh untuk setiap elemen yang ditulis
    std::vector<int> angka;
    std::copy(awal, akhir, std::back_inserter(angka));

    std::cout << "Dibaca dari stream : ";
    std::copy(angka.begin(), angka.end(),
              std::ostream_iterator<int>(std::cout, " "));
    std::cout << "(" << angka.size() << " angka)\n";

    // fill_n + back_inserter: mengisi vector kosong tanpa resize manual
    std::vector<int> daftarNol;
    std::fill_n(std::back_inserter(daftarNol), 3, 0);
    std::cout << "fill_n 3 x nol     : ";
    std::copy(daftarNol.begin(), daftarNol.end(),
              std::ostream_iterator<int>(std::cout, " "));
    std::cout << "(ukuran akhir " << daftarNol.size() << ")\n";

    // istream_iterator<string>: alur data teks dibaca per kata
    std::istringstream barisTeks("kita suka pipeline sederhana");
    std::istream_iterator<std::string> kata(barisTeks);
    std::istream_iterator<std::string> kataAkhir;
    std::cout << "Kata dari stream   : ";
    std::copy(kata, kataAkhir, std::ostream_iterator<std::string>(std::cout, " "));
    std::cout << "terbaca\n";
    return 0;
}

Output yang Dihasilkan:

text
Dibaca dari stream : 42 7 13 8 5 (5 angka)
fill_n 3 x nol     : 0 0 0 (ukuran akhir 3)
Kata dari stream   : kita suka pipeline sederhana terbaca

Bedah baris demi baris:

  1. Baris 10 (std::istringstream masukan(...)): stringstream berisi teks "42 7 13 8 5" berperan persis seperti std::cin — istream_iterator tidak peduli sumbernya stream apa pun.
  2. Baris 13-14 (awal dan akhir): awal menunjuk angka pertama yang belum dibaca; akhir dibangun tanpa argumen sehingga menjadi penanda akhir stream. Pasangan ini membentuk rentang [awal, akhir) yang sah untuk std::copy.
  3. Baris 18 (std::copy(awal, akhir, std::back_inserter(angka))): setiap angka yang dibaca dari stream ditulis ke adapter, dan setiap penulisan menjadi angka.push_back(...) — vector berisi lima angka tanpa pernah kita sebut ukurannya.
  4. Baris 21-23 (mencetak dengan ostream_iterator): std::ostream_iterator<int>(std::cout, " ") mencetak tiap elemen disambung spasi. Baris 23 menambahkan teks penutup (5 angka) sehingga baris output tidak berakhir dengan pembatas — perilaku yang kita bahas di callout di bawah.
  5. Baris 27 (std::fill_n(std::back_inserter(daftarNol), 3, 0)): fill_n menulis nilai 0 sebanyak tiga kali ke adapter, jadi vector kosong langsung berisi 0 0 0. Adapter ini juga bekerja untuk generate_n dan algoritma penulis lain.
  6. Baris 34-36 (istream_iterator<string>): tipe yang dibaca menentukan cara memotong stream; std::string mengambil data per kata dengan pemisah spasi, sama seperti operator >>.
  7. Baris 38 (copy kata ke ostream_iterator<string>): rentang iterator yang dibangun dari dua istream_iterator berperilaku sama seperti rentang container — bukti bahwa algoritma tidak peduli asal iteratornya.

front_inserter dan inserter Pada Posisi Tertentu #

std::back_inserter selalu menulis di ujung belakang. Untuk ujung depan tersedia std::front_inserter(c) yang memanggil push_front, dan operasi itu hanya dimiliki container yang strukturnya mendukung penyisipan di depan dengan murah: std::deque dan std::list. Konsekuensinya perlu diantisipasi sejak awal: karena setiap elemen baru ditulis di depan, hasil penyalinan lewat front_inserter urutannya terbalik dari sumber.

Bentuk adapter yang paling umum adalah std::inserter(c, pos). Adapter ini menyimpan iterator posisi; setiap penulisan memanggil c.insert(pos, nilai) lalu menggeser posisi tersimpan ke elemen yang baru, sehingga penulisan beruntun tetap berurutan tanpa saling menindih. Pasangan klasiknya adalah menyisipkan nilai ke list terurut: posisi sisip diperoleh dari std::lower_bound — pencarian yang mengembalikan iterator ke elemen pertama yang tidak lebih kecil dari nilai, dan akan kita bedah pada Chapter Binary Search.

cpp
#include <algorithm>
#include <deque>
#include <iostream>
#include <iterator>
#include <list>
#include <vector>

int main() {
    std::vector<int> sumber = {5, 30, 10, 25};

    // 1. front_inserter: setiap tulisan memanggil push_front pada deque
    std::deque<int> dek;
    std::copy(sumber.begin(), sumber.end(), std::front_inserter(dek));
    std::cout << "front_inserter : ";
    std::copy(dek.begin(), dek.end(),
              std::ostream_iterator<int>(std::cout, " "));
    std::cout << "(urutan terbalik)\n";

    // 2. inserter: sisip dua nilai ke list terurut pada posisi sisip
    std::list<int> terurut = {10, 20, 40, 50};
    auto posisi = std::lower_bound(terurut.begin(), terurut.end(), 26);
    std::vector<int> baru = {25, 26};
    std::copy(baru.begin(), baru.end(), std::inserter(terurut, posisi));
    std::cout << "inserter       : ";
    std::copy(terurut.begin(), terurut.end(),
              std::ostream_iterator<int>(std::cout, " "));
    std::cout << "(tetap terurut)\n";
    return 0;
}

Output yang Dihasilkan:

text
front_inserter : 25 10 30 5 (urutan terbalik)
inserter       : 10 20 25 26 40 50 (tetap terurut)

Bedah baris demi baris:

  1. Baris 12-13 (copy ke front_inserter): sumber 5, 30, 10, 25 diproses berurutan, tetapi setiap elemen masuk lewat push_front sehingga isi deque menjadi 25, 10, 30, 5 — pembalikan urutan yang memang dijanjikan mekanisme ini.
  2. Baris 14-17 (mencetak isi deque): std::ostream_iterator dipakai lagi di sini, memperlihatkan bahwa container hasil adapter bisa langsung dialirkan ke output tanpa loop manual.
  3. Baris 21 (std::lower_bound(..., 26)): pada list terurut 10 20 40 50, fungsi ini mengembalikan iterator yang menunjuk 40 — posisi pertama yang tidak lebih kecil dari 26.
  4. Baris 22-23 (copy ke std::inserter): nilai 25 disisipkan sebelum 40, lalu posisi tersimpan maju ke elemen yang baru sehingga 26 disisipkan tepat setelah 25. Hasil akhirnya 10 20 25 26 40 50 dan list tetap terurut.
  5. Baris 15 dan 25 (pembatas " "): kedua pencetakan kembali mengandalkan teks penutup (urutan terbalik) dan (tetap terurut) agar pembatas spasi tidak menggantung di akhir baris output.

Kaitan dengan Algoritma Output Iterator #

Pada Chapter Kategori Iterator kita mengenal kategori output iterator: hanya bisa ditulis, maju satu langkah, dan dirancang sekali jalan. Keempat adapter tulis di Chapter ini adalah penghuni nyata kategori tersebut — back_inserter, front_inserter, inserter, dan ostream_iterator tinggal di jenjang paling sederhana, dan justru di situlah nilainya: algoritma cukup menulis dan maju, tanpa perlu tahu container atau stream apa di baliknya. Inilah yang membuat algoritma tetap generik sampai ke tujuan yang paling tidak terduga.

Daftar algoritma yang menuntut output iterator panjang dan sering kita pakai: std::copy, std::copy_if, std::transform pada tujuannya, std::unique_copy, keluarga std::set_union dan std::set_difference, hingga std::fill_n dan std::generate_n yang kita uji di atas. Semua bisa diarahkan ke adapter mana pun tanpa perubahan kode — ganti back_inserter dengan ostream_iterator, dan hasil algoritma mengalir ke layar. Satu konsekuensi kategori ini perlu dipegang: adapter tulis tidak menjamin hasilnya bisa dibaca kembali, jadi jangan membaca *out dari adapter tulis.

Dengan adapter ini, rantai pengolahan bisa dirangkai dari sumber stream hingga container yang tumbuh sendiri. Chapter berikutnya, Algoritma Berbasis Rentang, memakai bekal yang sudah kita siapkan untuk membahas konvensi [first, last) yang dipakai seluruh algoritma STL.

Rujukan resmi: std::back_inserter , std::istream_iterator , dan std::ostream_iterator .