Въведение в метапрограмирането в C ++

Предишна 1 2 3 Страница 3 Страница 3 от 3
  • Променливи на състоянието: Параметрите на шаблона
  • Конструкции на контура: Чрез рекурсия
  • Избор на пътища за изпълнение: Чрез използване на условни изрази или специализации
  • Целочислена аритметика

Ако няма ограничения за количеството рекурсивни екземпляри и броя на допустимите променливи на състоянието, това е достатъчно за изчисляване на всичко, което е изчислимо. Възможно е обаче да не е удобно да го направите с помощта на шаблони. Освен това, тъй като инстанцирането на шаблон изисква значителни ресурси на компилатора, обширната рекурсивна инстанция бързо забавя компилатора или дори изчерпва наличните ресурси. Стандартът C ++ препоръчва, но не задължава 1024 нива на рекурсивни екземпляри да бъдат разрешени като минимум, което е достатъчно за повечето (но със сигурност не всички) задачи за метапрограмиране на шаблони.

По този начин на практика метапрограмите на шаблони трябва да се използват пестеливо. Има обаче няколко ситуации, когато те са незаменими като инструмент за внедряване на удобни шаблони. По-специално, те понякога могат да бъдат скрити във вътрешността на по-конвенционалните шаблони, за да изтласкат повече производителност от критичните реализации на алгоритъма.

Рекурсивна инстанция срещу рекурсивни аргументи на шаблон

Помислете за следния рекурсивен шаблон:

шаблонна структура Doublify {}; Проблем със структурата на шаблона {използване на LongType = Удвояване
   
    ; }; Проблем със структурата на шаблона {използване на LongType = double; }; Проблем :: LongType ouch;
   

Използването на Trouble::LongTypeне само предизвиква рекурсивни инстанциирането на Trouble, Trouble, ..., Trouble, но също така създава обект 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 към N-та структура на шаблон Pow3 {enum {стойност = 3 * Pow3 :: стойност}; }; // пълна специализация за прекратяване на шаблона за рекурсия struct Pow3 {enum {value = 1}; };

Стандартизацията на C ++ 98 въведе концепцията за инициализатори на статични константи в класа, така че метапрограмата Pow3 да изглежда по следния начин:

meta / pow3const.hpp // първичен шаблон за изчисляване на 3 до N-та структура на шаблон Pow3 {static int const value = 3 * Pow3 :: value; }; // пълна специализация за прекратяване на шаблона за рекурсия struct Pow3 {static int const value = 1; };

Има обаче недостатък при тази версия: Статичните постоянни членове са lvalues. Така че, ако имате декларация като

void foo (int const &);

и му предавате резултата от метапрограма:

foo (Pow3 :: value);

компилаторът трябва да предаде адреса на Pow3::valueи това принуждава компилатора да създаде екземпляр и да разпредели дефиницията за статичния член. В резултат на това изчислението вече не се ограничава до чист ефект „време на компилация“.

Стойностите за изброяване не са lvalues ​​(т.е. те нямат адрес). Така че, когато ги предавате по препратка, не се използва статична памет. Почти точно като че ли сте предали изчислената стойност като литерал.

C ++ 11 обаче въведе constexprстатични членове на данните и те не се ограничават до интегрални типове. Те не решават повдигнатия по-горе адресен въпрос, но въпреки този недостатък сега те са често срещан начин за получаване на резултати от метапрограми. Те имат предимството да имат правилен тип (за разлика от изкуствения тип на изброяване) и този тип може да бъде изведен, когато статичният член е деклариран със спецификатора за автоматичен тип. C ++ 17 добави вградени статични членове с данни, които решават проблема с адресите, повдигнат по-горе, и могат да се използват с constexpr.

История на метапрограмирането

Най-ранният документиран пример за метапрограма е от Erwin Unruh, който тогава представлява Siemens в комитета по стандартизация на C ++. Той отбеляза изчислителната пълнота на процеса на инстанциране на шаблона и демонстрира своята теза, като разработи първата метапрограма. Той използва компилатора на Metaware и го накара да издава съобщения за грешки, които ще съдържат последователни прости числа. Ето кода, който беше разпространен на заседание на комитет C ++ през 1994 г. (модифициран така, че сега се компилира на стандартни съответстващи компилатори):

meta / unruh.cpp // изчисляване на просто число // (модифициран с разрешение от оригинал от 1994 г. от Erwin Unruh) шаблон
   
     struct is_prime {enum ((p% i) && is_prime2? p: 0), i-1> :: pri); }; шаблонна структура is_prime {enum {pri = 1}; }; шаблонна структура is_prime {enum {pri = 1}; }; шаблон
    
      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.