Pengenalan kepada pengaturcaraan metaprogram dalam C ++

Sebelumnya 1 2 3 Halaman 3 Halaman 3 daripada 3
  • Nyatakan pemboleh ubah: Parameter templat
  • Konstruk gelung: Melalui rekursi
  • Pemilihan jalan pelaksanaan: Dengan menggunakan ungkapan atau pengkhususan bersyarat
  • Aritmetik integer

Sekiranya tidak ada had untuk jumlah contoh rekursif dan jumlah pemboleh ubah keadaan yang dibenarkan, ini cukup untuk mengira apa sahaja yang dapat dikira. Namun, mungkin tidak mudah untuk melakukannya menggunakan templat. Tambahan pula, kerana instantiasi templat memerlukan sumber penyusun yang besar, instansiasi rekursif yang luas dengan cepat melambatkan penyusun atau bahkan menghabiskan sumber yang ada. Piawaian C ++ mengesyorkan tetapi tidak mewajibkan 1,024 tahap contoh rekursif dibenarkan minimum, yang mencukupi untuk kebanyakan tugas (tetapi tidak semua) tugas pemrograman templat.

Oleh itu, dalam praktiknya, metaprogram templat harus digunakan dengan hemat. Namun, ada beberapa situasi ketika mereka tidak dapat diganti sebagai alat untuk menerapkan templat yang mudah. Secara khusus, kadangkala mereka dapat disembunyikan di dalam templat yang lebih konvensional untuk meningkatkan prestasi daripada pelaksanaan algoritma kritikal.

Instantiasi rekursif berbanding argumen templat rekursif

Pertimbangkan templat rekursif berikut:

templat templat Doublify {}; Templat struct Trouble {menggunakan LongType = Doublify
   
    ; }; Masalah template templat {menggunakan LongType = double; }; Masalah :: Aduh LongType;
   

Penggunaan Trouble::LongTypebukan sahaja mencetuskan merta rekursi daripada Trouble, Trouble, ..., Trouble, tetapi ia juga instantiates Doublifylebih jenis semakin kompleks. Jadual menggambarkan seberapa cepat ia tumbuh.

Pertumbuhan Trouble::LongType

 
Taipkan Alias Jenis yang mendasari
Trouble::LongType double
Trouble::LongType Doublify
Trouble::LongType Doublify

   Doublify>

Trouble::LongType Doublify

     Doublify>,

  

     Doublify>>

Seperti yang ditunjukkan oleh jadual, kerumitan jenis keterangan ekspresi Trouble::LongTypeberkembang dengan cepat N. Secara umum, keadaan seperti itu menekankan penyusun C ++ lebih daripada sekadar contoh rekursif yang tidak melibatkan argumen templat rekursif. Salah satu masalah di sini ialah penyusun menyimpan representasi nama yang tidak sesuai untuk jenisnya. Nama yang kacau ini mengekod pengkhususan templat yang tepat dalam beberapa cara, dan pelaksanaan C ++ awal menggunakan pengekodan yang kira-kira sebanding dengan panjang templat-id. Penyusun ini kemudian menggunakan lebih daripada 10,000 watak untuk Trouble::LongType.

Pelaksanaan C ++ yang lebih baru mengambil kira hakikat bahawa templat-id bersarang cukup umum dalam program C ++ moden dan menggunakan teknik pemampatan pintar untuk mengurangkan pertumbuhan pengekodan nama (misalnya, beberapa ratus aksara untuk Trouble::LongType). Penyusun yang lebih baru ini juga mengelakkan daripada menghasilkan nama yang tidak sesuai jika sebenarnya tidak diperlukan kerana tidak ada kod tahap rendah yang sebenarnya dihasilkan untuk contoh templat. Walaupun begitu, semua perkara lain sama, mungkin lebih baik untuk mengatur instansiasi rekursif sedemikian rupa sehingga argumen templat tidak perlu disarang secara berulang.

Nilai penghitungan berbanding pemalar statik

Pada hari-hari awal C ++, nilai-nilai penghitungan adalah satu-satunya mekanisme untuk membuat "pemalar benar" (disebut pemalar tetap ) sebagai anggota yang dinamakan dalam deklarasi kelas. Dengan mereka, anda boleh, misalnya, menentukan Pow3metaprogram untuk menghitung kekuatan 3 seperti berikut:

meta / pow3enum.hpp // templat primer untuk mengira 3 hingga templat ke-9 struktur Pow3 {enum {value = 3 * Pow3 :: value}; }; // pengkhususan penuh untuk menamatkan templat rekursi struct Pow3 {enum {value = 1}; };

Piawaian C ++ 98 memperkenalkan konsep pemula pemalar statik dalam kelas, sehingga metaprogram Pow3 dapat dilihat seperti berikut:

templat utama meta / pow3const.hpp // untuk mengira 3 hingga templat ke-9 struktur Pow3 {static int const value = 3 * Pow3 :: value; }; // pengkhususan penuh untuk menamatkan templat rekursi struct Pow3 {static int const value = 1; };

Walau bagaimanapun, terdapat kelemahan dengan versi ini: Anggota tetap statik adalah nilai. Jadi, jika anda mempunyai perisytiharan seperti

kekosongan (int const &);

dan anda lulus hasil metaprogram:

foo (Pow3 :: nilai);

pengkompil mesti lulus alamat untuk Pow3::value, dan bahawa kuasa-kuasa pengkompil untuk instantiate dan memperuntukkan takrifan bagi ahli statik. Hasilnya, pengiraan tidak lagi terbatas pada kesan "waktu kompilasi" yang murni.

Nilai penghitungan bukan nilai (yaitu, mereka tidak mempunyai alamat). Oleh itu, apabila anda menyampaikannya dengan rujukan, memori statik tidak akan digunakan. Ini hampir sama seperti anda melepasi nilai yang dikira sebagai literal.

C ++ 11, bagaimanapun, memperkenalkan constexpranggota data statik, dan itu tidak terhad kepada jenis integral. Mereka tidak menyelesaikan masalah alamat yang dibangkitkan di atas, tetapi terlepas dari kekurangan itu, mereka sekarang merupakan cara biasa untuk menghasilkan hasil metaprogram. Mereka memiliki kelebihan memiliki jenis yang benar (sebagai lawan dari jenis enum buatan), dan jenis itu dapat disimpulkan ketika anggota statis dinyatakan dengan penentu jenis automatik. C ++ 17 menambahkan ahli data statik sebaris, yang dapat menyelesaikan masalah alamat yang dibangkitkan di atas, dan dapat digunakan dengan constexpr.

Sejarah pengaturcaraan metaprogram

Contoh metaprogram yang paling awal didokumentasikan adalah oleh Erwin Unruh, yang kemudian mewakili Siemens di jawatankuasa standardisasi C ++. Dia mencatat kelengkapan komputasi proses instansi templat dan menunjukkan maksudnya dengan mengembangkan metaprogram pertama. Dia menggunakan penyusun Metaware dan membujuknya untuk mengeluarkan mesej ralat yang akan mengandungi nombor perdana berturut-turut. Berikut adalah kod yang diedarkan pada mesyuarat jawatankuasa C ++ pada tahun 1994 (diubah sehingga kini disusun pada penyusun pematuhan standard):

meta / unruh.cpp // pengiraan nombor perdana // (diubah suai dengan kebenaran dari asal dari tahun 1994 oleh Erwin Unruh) templat
   
     struct is_prime {enum ((p% i) && is_prime2? p: 0), i-1> :: pri); }; template templat is_prime {enum {pri = 1}; }; template templat is_prime {enum {pri = 1}; }; templat
    
      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 {
       

// primary template for loop to print prime numbers Prime_print a; enum { pri = is_prime::pri }; void f() { D d = CondNull::value;

// 1 is an error, 0 is fine a.f(); } }; template struct Prime_print {

// full specialization to end the loop enum {pri=0}; void f() { D d = 0; }; }; #ifndef LAST #define LAST 18 #endif int main() { Prime_print a; a.f(); }

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.