Pemformatan

Manipulator iostream dan std::format dari C++20.

Daftar isi

Keluaran yang rapi mempermudah pembacaan tabel, log, dan laporan. C++ menyediakan dua generasi alat pemformatan, yaitu manipulator iostream yang dipakai bersama operator << sejak lama, dan std::format dari C++20 yang menata tampilan lewat format string. Topik ini mempraktikkan keduanya pada data yang sama, sehingga perbedaan gaya dan letak pengaturannya terlihat jelas.

Mengatur tampilan dengan manipulator#

Manipulator adalah nilai yang disisipkan ke stream untuk mengubah tampilan data berikutnya. Manipulator lebar dan presisi berasal dari header <iomanip>, sedangkan yang mengatur arah perataan seperti std::left diatur langsung pada stream. Aturan umumnya, std::setw hanya berlaku untuk satu data berikutnya, sedangkan std::fixed, std::setprecision, dan std::setfill menempel sampai diubah lagi.

cpp manipulator.cpp
#include <iomanip>
#include <iostream>
#include <string>

int main() {
    const std::string waktu[] = {"07:00", "08:00", "09:00"};
    const double suhu[] = {22.5, 23.1, 24.8};

    std::cout << std::left << std::setw(8) << "waktu"
              << std::right << std::setw(8) << "suhu" << "\n";

    for (int i = 0; i < 3; ++i) {
        std::cout << std::left << std::setw(8) << waktu[i]
                  << std::right << std::fixed << std::setprecision(1)
                  << std::setw(8) << suhu[i] << "\n";
    }

    std::cout << std::setfill('*') << std::setw(16) << "" << "\n";
    return 0;
}
text Keluaran
waktu       suhu
07:00       22.5
08:00       23.1
09:00       24.8
****************

Kolom waktu diratakan kiri dengan std::left, sedangkan kolom suhu diratakan kanan dengan std::right dan ditampilkan dengan satu angka di belakang koma melalui kombinasi std::fixed dan std::setprecision(1). Baris terakhir memakai std::setfill('*') sehingga lebar 16 karakter terisi karakter bintang. Kombinasi ini memadai untuk tabel kecil, tetapi pengaturan yang menempel perlu diingat karena berlaku untuk semua cetakan setelahnya.

Dua perilaku manipulator patut dicatat. Data yang lebih panjang daripada lebar std::setw tetap dicetak utuh, sehingga kolom bergeser apabila isinya melebihi lebar yang ditetapkan dan tidak pernah terpotong. Di sisi lain, std::setprecision tanpa std::fixed berarti jumlah angka signifikan, sehingga std::setprecision(3) menampilkan 23.4567 sebagai 23.5. Dengan std::fixed, nilai yang sama tampil sebagai 23.457 karena presisinya berpindah ke belakang koma.

Pemformatan modern dengan std::format#

std::format memindahkan seluruh pengaturan ke dalam format string, yaitu teks pola yang memuat pengganti {} beserta spesifikasinya. Pengganti diisi oleh argumen secara berurutan, dan spesifikasinya ditulis setelah tanda titik dua, misalnya {:<8} untuk rata kiri dalam lebar 8 karakter atau {:.1f} untuk satu angka di belakang koma. Hasilnya berupa std::string yang tinggal dicetak, sehingga keadaan stream tidak tersentuh sama sekali.

cpp main.cpp
// tidak-dijalankan (butuh -std=c++23; perintah build ada di bawah)
#include <format>
#include <iostream>
#include <string>

int main() {
    const std::string waktu[] = {"07:00", "08:00", "09:00"};
    const double suhu[] = {22.5, 23.1, 24.8};

    std::cout << std::format("{:<8}{:>8}\n", "waktu", "suhu");
    for (int i = 0; i < 3; ++i) {
        std::cout << std::format("{:<8}{:>8.1f}\n", waktu[i], suhu[i]);
    }
    std::cout << std::format("{:*^16}\n", "");
    return 0;
}
bash
# kompilasi dengan standar C++23
g++ -std=c++23 main.cpp -o main
# jalankan program
./main
text Keluaran
waktu       suhu
07:00       22.5
08:00       23.1
09:00       24.8
****************

Format string {:*^16} menata teks di tengah lebar 16 karakter dengan karakter pengisi bintang, yang setara dengan kombinasi std::setfill dan std::setw pada versi manipulator. Keduanya menghasilkan tabel yang persis sama, tetapi std::format menyatukan lebar, perataan, dan presisi pada satu tempat yang mudah dibaca. Tanda { dan } yang dimaksud sebagai karakter biasa perlu ditulis ganda, yaitu {{ dan }}.

Tipe tampilan pada std::format#

Spesifikasi di dalam pengganti {} mengikuti urutan tetap, yaitu pengisi dan perataan, tanda, bentuk alternatif #, lebar, presisi, dan tipe tampilan (presentation type) di ujungnya. Tanda + memaksa tanda muncul pula pada bilangan positif, sedangkan # mengaktifkan bentuk alternatif seperti awalan 0x pada heksadesimal. Tipe tampilan menentukan notasi bilangannya, misalnya x untuk heksadesimal, b untuk biner, e untuk notasi ilmiah, dan g untuk pilihan umum yang memilih antara tetap dan ilmiah.

cpp format-tipe.cpp
// tidak-dijalankan (butuh -std=c++23; perintah build ada di bawah)
#include <format>
#include <iostream>

int main() {
    std::cout << std::format("tanda   : {:+d}\n", 42);
    std::cout << std::format("heksa   : {:#x}\n", 255);
    std::cout << std::format("biner   : {:b}\n", 10);
    std::cout << std::format("ilmiah  : {:+.2e}\n", 12345.678);
    std::cout << std::format("umum    : {:g}\n", 0.000123);
    return 0;
}
bash
# kompilasi dengan standar C++23
g++ -std=c++23 format-tipe.cpp -o format-tipe
# jalankan program
./format-tipe
text Keluaran
tanda   : +42
heksa   : 0xff
biner   : 1010
ilmiah  : +1.23e+04
umum    : 0.000123

Notasi {:*^16} pada bagian sebelumnya dapat dibaca ulang dengan urutan di atas, yaitu pengisi bintang, perataan tengah, dan lebar 16 karakter. Presisi yang ditulis tanpa tipe tampilan pada bilangan pecahan berarti jumlah angka signifikan, sedangkan presisi pada tipe f berarti jumlah angka di belakang koma. Keunggulan lainnya bersifat pencegahan, karena format string diperiksa saat kompilasi terhadap jumlah dan tipe argumennya, sehingga ketidakcocokan terdeteksi sebelum program dijalankan.

Membandingkan manipulator dan std::format#

AspekManipulator iostreamstd::format
Letak pengaturankeadaan stream yang menempelformat string pada tiap panggilan
Lingkup berlakustd::setw sekali, sisanya menempelsatu panggilan std::format
Keterbacaan polaberpencah di antara operator <<terpusat dalam satu teks pola
Hasillangsung ke streamstring yang dapat dipakai ulang
Standartersedia sejak C++ awalC++20, membutuhkan compiler modern

Keduanya sah digunakan, dan kode lama yang besar umumnya tetap memakai manipulator. Untuk kode baru, std::format mengurangi risiko lupa mengembalikan keadaan stream karena pengaturannya tidak menempel. Spesifikasi formatnya juga mencakup tipe tampilan untuk basis bilangan, karakter, dan boolean, sehingga kebutuhan tabel seperti contoh di atas tidak lagi memerlukan rantai manipulator yang panjang.

Kesalahan umum#

Kekeliruan berikut sering muncul saat pemformatan pertama kali diterapkan.

  • Menganggap std::setw menempel. Lebar hanya berlaku pada satu data berikutnya, sehingga tabel dengan banyak kolom memerlukan std::setw pada setiap sisipan. Perbaikannya menuliskannya ulang di setiap kolom atau berpindah ke std::format.
  • Lupa mengembalikan keadaan stream. std::fixed dan std::setprecision tetap aktif untuk seluruh cetakan setelahnya, termasuk yang tidak membutuhkannya. Perbaikannya mengatur ulang presisi sebelum cetakan lain atau memindahkan pengaturannya ke std::format.
  • Menyusun kode std::format tanpa standar yang memadai. Kompilasi dengan standar lama gagal karena header <format> belum tersedia. Perbaikannya menyertakan -std=c++20 atau -std=c++23 dan memakai GCC 13 ke atas.
  • Memakai kurung kurawal tunggal untuk karakter harfiah. Tanda { dan } pada format string harus ditulis ganda, yaitu {{ dan }}, apabila keduanya bukan pengganti argumen.
  • Menyusun format string yang tidak cocok dengan argumennya. std::format memeriksa format string saat kompilasi, sehingga program gagal dikompilasi ketika jumlah pengganti tidak sesuai dengan jumlah argumennya. Perbaikannya menyamakan jumlah keduanya atau menyebut indeks secara eksplisit, misalnya {0} dan {1}.

Ringkasan#

  • Manipulator seperti std::setw, std::fixed, std::setprecision, dan std::setfill mengatur tampilan lewat keadaan stream, dan hanya std::setw yang berlaku sekali.
  • std::format dari C++20 menata lebar, perataan, presisi, dan pengisi dalam satu format string, lalu mengembalikan hasilnya sebagai string.
  • Kompilasi program dengan <format> memerlukan -std=c++20 atau lebih baru serta GCC 13 ke atas.
  • Pengaturan yang menempel pada stream berlaku untuk semua cetakan setelahnya, sehingga perlu dikembalikan apabila tidak lagi diinginkan.

Referensi#