מבוא למטפורוגרפיה ב- C ++

הקודם 1 2 3 עמוד 3 עמוד 3 מתוך 3
  • משתני מצב: פרמטרי התבנית
  • מבני לולאה: באמצעות רקורסיה
  • בחירת מסלולי הוצאה לפועל: באמצעות ביטויים או התמחויות מותנות
  • חשבון שלם

אם אין מגבלות לכמות האינסטינציות הרקורסיביות ולמספר משתני המצב המותרים, זה מספיק כדי לחשב כל דבר הניתן לחישוב. עם זאת, ייתכן שלא יהיה נוח לעשות זאת באמצעות תבניות. יתר על כן, מכיוון שאינסטימציה לתבנית דורשת משאבי מהדר מהותיים, מיידי רקורסיבי נרחב מאט במהירות את המהדר או אפילו ממצה את המשאבים הזמינים. תקן C ++ ממליץ אך אינו מחייב לאפשר 1,024 רמות של הפעלות רקורטיביות לכל הפחות, מה שמספיק למשימות המטא-תכנות של תבניות לרוב (אך בהחלט לא לכולן).

לפיכך, בפועל, יש להשתמש במשורה במטפורוגרמות תבניות. ישנם מספר מצבים, עם זאת, כאשר אין להם תחליף ככלי ליישום תבניות נוחות. בפרט, לפעמים הם יכולים להיות מוסתרים בפנים של תבניות קונבנציונליות יותר כדי לסחוט ביצועים רבים יותר מיישומי אלגוריתם קריטיים.

אינסטנטציה רקורסיבית לעומת טיעוני תבנית רקורסיביים

שקול את התבנית הרקורסיבית הבאה:

תבנית תבנית הכפלה {}; צורה של מבנה תבנית {באמצעות LongType = הכפל
   
    ; }; צורה של מבנה תבנית {באמצעות LongType = כפול; }; צרות :: LongType ouch;
   

השימוש Trouble::LongTypeלא רק מעורר את ידגום רקורסיבית של Trouble, Trouble, ..., Trouble, אבל זה גם instantiates Doublifyמעל סוגים מורכבים יותר ויותר. הטבלה ממחישה כמה מהר היא צומחת.

הצמיחה של Trouble::LongType

 
הקלד כינוי סוג בסיסי
Trouble::LongType double
Trouble::LongType Doublify
Trouble::LongType Doublify

   Doublify>

Trouble::LongType Doublify

     Doublify>,

  

     Doublify>>

כפי שמראה הטבלה, המורכבות של תיאור הסוג של הביטוי Trouble::LongTypeגדלה באופן אקספוננציאלי עם N. באופן כללי, מצב כזה מלחיץ מהדר C ++ אפילו יותר מאשר מיידיות רקורסיביות שאינן כוללות טיעונים של תבניות רקורסיביות. אחת הבעיות כאן היא שמהדר שומר ייצוג של השם הרעוע עבור הסוג. שם מטונף זה מקודד את ההתמחות המדויקת של התבניות בצורה כלשהי, ויישומי C ++ מוקדמים השתמשו בקידוד שביחסו אורך מזהה התבנית. מהדרים אלה השתמשו יותר מ -10,000 תווים עבור Trouble::LongType.

יישומי C ++ חדשים יותר לוקחים בחשבון את העובדה שמזהי תבנית מקוננים נפוצים למדי בתוכניות C ++ מודרניות ומשתמשים בטכניקות דחיסה חכמות כדי להפחית במידה ניכרת את הצמיחה בקידוד השמות (למשל, כמה מאות תווים עבור Trouble::LongType). מהדרים חדשים יותר אלה גם נמנעים מלייצר שם מטומטם אם אין צורך בשום דבר מכיוון שלמעשה לא נוצר קוד ברמה נמוכה עבור מופע התבנית. ובכל זאת, כל שאר הדברים שווים, ככל הנראה עדיף לארגן אינסטינציה רקורסיבית באופן שלא צריך לקנן את טיעוני התבנית גם באופן רקורסיבי.

ערכי ספירה לעומת קבועים סטטיים

בימיו הראשונים של C ++, ערכי ספירה היו המנגנון היחיד ליצירת "קבועים אמיתיים" (הנקראים ביטויים קבועים ) כחברים בשם בהצהרות כיתתיות. איתם תוכלו, למשל, להגדיר Pow3מטאוגרפיה לחישוב כוחות של 3 כדלקמן:

meta / pow3enum.hpp // תבנית ראשית לחישוב 3 לתבנית Nth struct Pow3 {enum {value = 3 * Pow3 :: value}; }; // התמחות מלאה לסיום תבנית הרקורסיה struct Pow3 {enum {value = 1}; };

התקינה של C ++ 98 הציגה את הרעיון של אתחולים קבועים סטטיים בכיתה, כך שמטאוגרפיה של Pow3 תוכל להיראות כך:

meta / pow3const.hpp // תבנית ראשית לחישוב 3 לתבנית ה- Nth struct Pow3 {static int const value = 3 * Pow3 :: value; }; // התמחות מלאה לסיום תבנית הרקורסיה struct Pow3 {ערך סטטי int const = 1; };

עם זאת, יש חסרון בגרסה זו: חברים קבועים סטטיים הם ערכים. לכן, אם יש לך הצהרה כגון

בטל foo (int const &);

ואתה מעביר את זה תוצאה של מטאוגרפיה:

foo (Pow3 :: value);

מהדר חייב להעביר את הכתובת של Pow3::valueוזה מכריח את המהדר להפעיל ולהקצות את ההגדרה לחבר הסטטי. כתוצאה מכך, החישוב אינו מוגבל עוד לאפקט "זמן קומפילציה" טהור.

ערכי ספירה אינם ערכים (כלומר, אין להם כתובת). לכן, כאשר אתה מעביר אותם לפי הפניה, לא משתמשים בזיכרון סטטי. זה כמעט כאילו העברת את הערך המחושב כמילולי.

C ++ 11, לעומת זאת, הציג constexprחברי נתונים סטטיים, ואלה אינם מוגבלים לסוגים אינטגרליים. הם לא פותרים את סוגיית הכתובת שהועלתה לעיל, אך למרות החסרון הזה הם כיום דרך נפוצה לייצר תוצאות של מטפורוגרמות. יש להם יתרון בכך שיש להם סוג נכון (להבדיל מסוג enum מלאכותי), וניתן להסיק את הסוג הזה כאשר מכריזים על החבר הסטטי בעזרת מפרט הסוג האוטומטי. C ++ 17 הוסיף חברי נתונים סטטיים מוטבעים, אשר פותרים את סוגיית הכתובת שהועלתה לעיל, וניתן להשתמש בהם עם constexpr.

היסטוריה מטא-תכנתית

הדוגמה המתועדת המוקדמת ביותר למטאוגרפיה הייתה של ארווין אנרו, שייצג אז את סימנס בוועדת התקן C ++. הוא ציין את השלמות החישובית של תהליך האינסטינציה של התבנית והוכיח את טענתו על ידי פיתוח המטאוגרפיה הראשונה. הוא השתמש במהדר Metaware ושילב אותו להוצאת הודעות שגיאה שיכילו מספרים ראשוניים עוקבים. הנה הקוד שהופץ בישיבת ועדת C ++ בשנת 1994 (שונה כך שהוא אוסף כעת על מהדרים תואמים סטנדרטיים):

meta / unruh.cpp // חישוב מספר ראשוני // (שונה באישור מהמקור משנת 1994 על ידי Erwin Unruh)
   
     struct is_prime {enum ((p% i) && is_prime2? p: 0), i-1> :: pri); }; תבנית struct is_prime {enum {pri = 1}; }; תבנית struct is_prime {enum {pri = 1}; }; תבנית
    
      מבנה D {D (ריק *); }; תבנית
     
       struct CondNull {ערך סטטי int const = i; }; תבנית struct CondNull {ערך ריק * סטטי; }; בטל * CondNull :: value = 0; תבנית
      
        struct Prime_print {
       

// תבנית ראשית ללולאה להדפסת מספרים ראשוניים פריים_דפוס a; enum {pri = is_prime :: pri}; בטל f () {D d = CondNull :: value;

// 1 היא שגיאה, 0 בסדר af (); }}; תבנית פריים_פרינט {

// התמחות מלאה לסיום enum הלולאה {pri = 0}; בטל f () {D d = 0; }; }; #ifndef LAST #define LAST 18 #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.