Pengantar metaprogramming di C ++

Sebelumnya 1 2 3 Halaman 3 Halaman 3 dari 3
  • Variabel status: Parameter template
  • Konstruksi loop: Melalui rekursi
  • Pemilihan jalur eksekusi: Dengan menggunakan ekspresi atau spesialisasi bersyarat
  • Aritmatika integer

Jika tidak ada batasan jumlah instance rekursif dan jumlah variabel status yang diizinkan, ini cukup untuk menghitung apa pun yang dapat dihitung. Namun, mungkin tidak nyaman melakukannya dengan menggunakan templat. Selain itu, karena instansiasi template memerlukan resource compiler yang substansial, instantiation rekursif yang ekstensif dengan cepat memperlambat compiler atau bahkan menghabiskan resource yang tersedia. Standar C ++ merekomendasikan tetapi tidak mengamanatkan bahwa 1.024 tingkat instansiasi rekursif diizinkan sebagai minimum, yang cukup untuk sebagian besar (tetapi tentu saja tidak semua) tugas metaprogramming template.

Jadi, dalam praktiknya, metaprogram template harus digunakan secara hemat. Ada beberapa situasi, bagaimanapun, ketika mereka tak tergantikan sebagai alat untuk mengimplementasikan templat yang nyaman. Secara khusus, terkadang mereka dapat disembunyikan di bagian dalam template yang lebih konvensional untuk menekan lebih banyak kinerja dari implementasi algoritma kritis.

Instansiasi rekursif versus argumen template rekursif

Pertimbangkan template rekursif berikut:

template struct Doublify {}; template struct Masalah {using LongType = Doublify
   
    ; }; template struct Masalah {using LongType = double; }; Masalah :: LongType aduh;
   

Penggunaan Trouble::LongTypetidak hanya memicu Instansiasi rekursif Trouble, Trouble, ..., Trouble, tetapi juga instantiates Doublifylebih jenis yang semakin kompleks. Tabel tersebut menggambarkan seberapa cepat pertumbuhannya.

Pertumbuhan Trouble::LongType

 
Ketik Alias Jenis yang Mendasari
Trouble::LongType double
Trouble::LongType Doublify
Trouble::LongType Doublify

   Doublify>

Trouble::LongType Doublify

     Doublify>,

  

     Doublify>>

Seperti yang diperlihatkan tabel, kompleksitas deskripsi tipe ekspresi Trouble::LongTypetumbuh secara eksponensial N. Secara umum, situasi seperti ini lebih menekankan pada compiler C ++ daripada instansiasi rekursif yang tidak melibatkan argumen template rekursif. Salah satu masalah di sini adalah kompilator menyimpan representasi nama yang terkoyak untuk tipe tersebut. Nama yang terkoyak ini mengkodekan spesialisasi template yang tepat dalam beberapa cara, dan implementasi C ++ awal menggunakan pengkodean yang kira-kira sebanding dengan panjang template-id. Kompiler ini kemudian menggunakan lebih dari 10.000 karakter untuk Trouble::LongType.

Implementasi C ++ yang lebih baru memperhitungkan fakta bahwa template-ids bersarang cukup umum dalam program C ++ modern dan menggunakan teknik kompresi yang cerdas untuk mengurangi pertumbuhan enkode nama secara signifikan (misalnya, beberapa ratus karakter untuk Trouble::LongType). Kompiler yang lebih baru ini juga menghindari pembuatan nama yang rusak jika tidak ada yang benar-benar diperlukan karena tidak ada kode tingkat rendah yang benar-benar dibuat untuk contoh template. Namun, semua hal lain dianggap sama, mungkin lebih baik untuk mengatur instansiasi rekursif sedemikian rupa sehingga argumen template tidak perlu disarangkan secara rekursif.

Nilai pencacahan versus konstanta statis

Pada masa awal C ++, nilai enumerasi adalah satu-satunya mekanisme untuk membuat "konstanta sejati" (disebut ekspresi konstan ) sebagai anggota yang disebutkan dalam deklarasi kelas. Dengan mereka, Anda dapat, misalnya, mendefinisikan Pow3metaprogram untuk menghitung pangkat 3 sebagai berikut:

meta / pow3enum.hpp // template utama untuk menghitung 3 ke template ke-N struct Pow3 {enum {value = 3 * Pow3 :: value}; }; // spesialisasi penuh untuk mengakhiri template rekursi struct Pow3 {enum {value = 1}; };

Standarisasi C ++ 98 memperkenalkan konsep inisialisasi konstanta statis di kelas, sehingga program metaprogram Pow3 dapat terlihat sebagai berikut:

meta / pow3const.hpp // template utama untuk menghitung 3 ke template ke-N struct Pow3 {static int nilai const = 3 * Pow3 :: value; }; // spesialisasi penuh untuk mengakhiri template rekursi struct Pow3 {static int const value = 1; };

Namun, ada kekurangan dengan versi ini: Anggota konstanta statis adalah lvalues. Jadi, jika Anda memiliki deklarasi seperti

void foo (int const &);

dan Anda meneruskannya sebagai hasil dari program metaprogram:

foo (Pow3 :: nilai);

kompilator harus lulus alamat dari Pow3::value, dan bahwa pasukan compiler untuk instantiate dan mengalokasikan definisi untuk anggota statis. Hasilnya, komputasi tidak lagi terbatas pada efek "waktu kompilasi" murni.

Nilai pencacahan bukanlah lvalues ​​(artinya, mereka tidak memiliki alamat). Jadi, saat Anda meneruskannya dengan referensi, tidak ada memori statis yang digunakan. Ini hampir persis seperti jika Anda meneruskan nilai yang dihitung sebagai literal.

C ++ 11, bagaimanapun, memperkenalkan constexpranggota data statis, dan itu tidak terbatas pada tipe integral. Mereka tidak menyelesaikan masalah alamat yang diangkat di atas, tetapi terlepas dari kekurangan itu, mereka sekarang menjadi cara umum untuk menghasilkan hasil metaprogram. Mereka memiliki keuntungan karena memiliki tipe yang benar (sebagai lawan dari tipe enum buatan), dan tipe itu dapat disimpulkan ketika anggota statis dideklarasikan dengan penentu tipe otomatis. C ++ 17 menambahkan anggota data statis sebaris, yang memecahkan masalah alamat yang diangkat di atas, dan dapat digunakan dengan constexpr.

Riwayat metaprogramming

Contoh metaprogram yang terdokumentasi paling awal adalah oleh Erwin Unruh, kemudian mewakili Siemens di komite standardisasi C ++. Dia mencatat kelengkapan komputasi dari proses pembuatan template dan mendemonstrasikan maksudnya dengan mengembangkan metaprogram pertama. Dia menggunakan kompiler Metaware dan membujuknya untuk mengeluarkan pesan kesalahan yang akan berisi bilangan prima yang berurutan. Berikut adalah kode yang diedarkan pada rapat komite C ++ tahun 1994 (diubah sehingga sekarang dikompilasi pada penyusun yang sesuai standar):

meta / unruh.cpp // komputasi bilangan prima // (dimodifikasi dengan izin dari aslinya dari tahun 1994 oleh Erwin Unruh) template
   
     struct is_prime {enum ((p% i) && is_prime2? p: 0), i-1> :: pri); }; template struct is_prime {enum {pri = 1}; }; template struct is_prime {enum {pri = 1}; }; template
    
      struct D {D (void *); }; template
     
       struct CondNull {static int const value = i; }; template struct CondNull {static void * value; }; void * CondNull :: value = 0; template
      
        struct Prime_print {
       

// template utama untuk loop untuk mencetak bilangan prima Prime_print a; enum {pri = is_prime :: pri}; void f () {D d = CondNull :: value;

// 1 adalah kesalahan, 0 baik-baik saja af (); }}; template struct Prime_print {

// spesialisasi penuh untuk mengakhiri enum perulangan {pri = 0}; batal f () {D d = 0; }; }; #ifndef TERAKHIR # Tentukan 18 TERAKHIR #endif int main () {Prime_print a; af (); }

If you compile this program, the compiler will print error messages when, in Prime_print::f(), the initialization of d fails. This happens when the initial value is 1 because there is only a constructor for void*, and only 0 has a valid conversion to void*. For example, on one compiler, we get (among several other messages) the following errors:

unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’

Note: As error handling in compilers differs, some compilers might stop after printing the first error message.

The concept of C++ template metaprogramming as a serious programming tool was first made popular (and somewhat formalized) by Todd Veldhuizen in his paper “Using C++ Template Metaprograms.” Veldhuizen’s work on Blitz++ (a numeric array library for C++) also introduced many refinements and extensions to metaprogramming (and to expression template techniques).

Both the first edition of this book and Andrei Alexandrescu’s Modern C++ Design contributed to an explosion of C++ libraries exploiting template-based metaprogramming by cataloging some of the basic techniques that are still in use today. The Boost project was instrumental in bringing order to this explosion. Early on, it introduced the MPL (metaprogramming library), which defined a consistent framework for type metaprogramming made popular also through David Abrahams and Aleksey Gurtovoy’s book C++ Template Metaprogramming.

Additional important advances have been made by Louis Dionne in making metaprogramming syntactically more accessible, particularly through his Boost.Hana library. Dionne, along with Andrew Sutton, Herb Sutter, David Vandevoorde, and others are now spearheading efforts in the standardization committee to give metaprogramming first-class support in the language. An important basis for that work is the exploration of what program properties should be available through reflection; Matúš Chochlík, Axel Naumann, and David Sankel are principal contributors in that area.

John J. Barton and Lee R. Nackman illustrated how to keep track of dimensional units when performing computations. The SIunits library was a more comprehensive library for dealing with physical units developed by Walter Brown. The std::chrono component in the standard library only deals with time and dates, and was contributed by Howard Hinnant.