Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 61
Текст из файла (страница 61)
Если шаблон вызывается только с одним аргументом, то по умолчанию принимается, что квадратный корень не может быть меньшим единины и большим самого значения этого параметра. Рекурсия осуществляется по методу бинарного поиска, который часто называют мевюдом деления лоиолам. Сначала числовой отрезок, на котором ведется поиск результата, делится пополам. Затем в шаблоне проводится проверка, в какой из образовавшихся Глава 17. Метапрограммы 330 // вета/вс(гс1.срр №1пс1ис)е <1овегеат> №1пс1ис)е "вс(гг1.Ьрр" 1пе лабп() ( вес)::соиг « "Яс(ге<16>::геви1г = " « Яс(ге<16>::геви1г « '~п'; « "Яг(ге<25>::геви1Г = " « Яцгс<25>::геви1Г « '~п'; « "Яс(ге<42>::геви1Г = " « Яс(ге<42>::геви1Г « '1п' г « "Яс(гг<1>::геви1е = " « Яс(ге<1>::геви1г « '1п'; вес)::соиг вес)::соиг вес)::соиг В процессе анализа выражения Яс)ге<16>:: геви1г компилятор расширяет его до Яс)ге<16, 1, 16>:: геви1г В шаблоне метапрограммы вычисляется выражение Яцгг<16, 1, 16>:: геви1е.
гаЫ = (1+16+1)/2 = 9 геви1Г = (16<9*9) 2 Яс(ге<16,1,8>::геви1Г Ясугк<16,9,16>::геви1Г. (16<81) 7 Яс)ге<16, 1, 8>:: геви1Т Яс(ге<16,9,16>::геви1с = Яс(ге<16,1,8>::геви1Г После этого вычисляется значение Яс(ге<16, 1, 8>:: геви1 с.
гаЫ = (1+8+1) /2 5 половин интервала находится результат, и в зависимости от этого переменным 1 О и Н1 присваиваются те или иные новые значения. Это осуществляется с помощью условного оператора 2:. Если возведенное в квадрат значение переменной юЫ (середины отрезка) больше И, поиск результата продолжается в первой половине отрезка.
В противном сл)ь чае тот же шаблон применяется для поиска результата во второй половине отрезка. Конечная специализация, завершающая рекурсивную процедуру, вызывается, если значения переменных 1 О и Н1 равны одной и той же величине М, которая и является конечным результатом. Рассмотрим простую программу, использующую метапрограмму, с которой вы только что ознакомились. 17.3. Второй пример: вычисление квадратного корня 331 гези1С = (16<5*5) 7 Янга<16,1,4>::гези1С Яцко<16,5,8>::гези1с (16<25) ? Янке<16, 1, 4»: гезп1Г Яцгс<16,5,8>::гези1С = Яцгс<16,1,4>::гези1Г Аналогично, на следующем этапе нужно вычислить значение выражения Яцгг<16, 1, 4>:: гези1г.
Подробно процесс этого вычисления выглядит так, как показано ниже. тЫ = (1+4+1)/2 3 гези1с = (16<3*3) 2 ЯЧгс<16,1,2>::гезц1С Яс(ге<16, 3, 4>:: гези1г (16<9] 7 Яс(ге<16, 1, 2>:: гези1Г Яцгг<16,3,4>::гези1г = Яс(ге<16,3,4>::гезц1г Наконец, приходим к вычислению выражения ЯЧгг<1 6, 3, 4 >:: гези1г.
шйа = (3+4+ИУг 4 гези1Г = (16<4*4) 7 Яс(ге<16,3,3>::гезп1С Яцгс<16, 4, 4>:: гези1Г (16<16) 7 Яс(ге<16,3,3>::гези1Г Яс(ге<16,4,4>::гези1Г = ЯЧгс<16, 4, 4>:: гези1г Когда дело доходит до выражения яс(ге<16, 4, 4>:: гези1г, рекурсия заканчивается, поскольку это выражение подходит для явной специализации, в которой верхняя и нижняя границы интервала поиска совпадают. Таким образом, конечный результат таков: гези1с = 4 Отслеживание инстанцирований шаблона Рассматривая предыдущий пример, мы останавливались на реализации важнейшиз( этапов вычисления квадратного корня из 16.
Однако при вычислении выражения гезц1с = (16<9*9) 7 ЯЧгс<16,1,8>::гези1Г Яс)ге<16, 9, 16>:: гезц1г «омпилятор инстанпирует шаблоны не только первой, но и второй ветви (яЧгг<16, 9, 16>). Яолее того, поскольку доступ к переменной-члену результирукицего класса осуществляется с помол(ью оператора::, происходит внстанцирование всех переменных-членов внутри класса Это означает, что полное инстаицироааиие шаблона яс(ге<16, 9, 16> приводит к полному инстанцированию шаблонов яЧгг<16, 9, 12> и яс(ге<16, 13, 16>. Чтобы полностью проследить за всем процессом, пришлось бы рассматривать несколько десятков инстаицирований.
Их количество почти в два раза превышает И. Глава 17. Мстапротраммы 332 Для большинства компиляторов зто приводит к неоправданно большому потреблению ресурсов (особенно оперативной памяти). К счастью, существуют методы, которые помогают обуздать такое лавинообразное увеличение количества инстанцироааний. Для выбора отрезка, на котором будет проводиться дальнейший поиск результата, воспользуемся не условным оператором 7:, а специализацией шаблона.
Чтобы продемонстрировать сформулированное утверждение, перепишем метапрограмму ЯЧгш // шега/вйгс2.)зрр ))Ьпс1цс)е "зйсЬепе1ве. Ьрр" // Первичный шаблон, задающий основной шаг рекурсии. Сешр1асе <1пг И, Тпг Ь0=1, Тпс Н1=И> с1авв Ячгг ( риЬ11с: // Вычисление округленного среднего значения. епша ( ш16 = (ЬО+Н1+1)/2 // Определяем, в какой половине числового отрезка // находится искомое значение. курейшей Гурепаше 1йТЬепЕ1ве<(Н<шЫ*шйс(), ЯЧгс<Н,ЬС,шйб-1>, ЯЧгг<Н, шЫ, Н1»:: Ееви1сТ ЯцЬТ; епиш ( гевц1С = ЯцЬТ::гевц1Г )з // Частичная специализация шаблона для завершения рекурсии сешр1асе <1пг И, Тпг Я> с1авв ЯЧгс<И, Я, Я> ( рцЬ11с: епцш ( гевц1с = Я ); Главное отличие этой версии программы от предыдущей заключается в применении шаблона 1 ЕТЬепЕ1ве из раздела 15.2.4, стр.
298. // шеса/1гсЬепе1ве. Ьрр ФТйпс)ей 1РТНЕИЕЬЯЕ НРР Вс)ей1пе 1РТНЕНЕЬЯЕ НРР // Первичный шаблон; в зависимости от значения первого // аргумента возвращает значение второго или третьего // аргумента. сешр1асе<Ьоо1 с, сурепаше та, гурепаше тЬ> с1авв 1ЙТЬепЕ1ве; // Частичная специализация: если значение первого аргумента 17.4. Применение переменных индукции 333 // равно сгие, то возвращается второй аргумент Сещр1аге<аурепаше Та, Сурепаще ТЬ> с1авв 1ЙТЬепЕ1ве<сгие, Та, ТЬ> ( риЬ11с: сурес(еЕ Та Кеви1СТ; // Частичная специализация: если значение первого аргумента // равно Йа1ве, то возвращается третий аргумент. Сепр1аге<аурепате Та, куренное ТЬ> с1авз 1ЕТЬепЕ1ве<йа1ве, Та, ТЬ> ( риЬ11с: сурес(ей ТЬ Кеви1ст; «еп61Е // 1РТНЕНЕГ ЯЕ НРР Напомним.
что шаблон 1ЕТЬепЕ1ве — это конструкция, с помощью которой выбирается один из двух типов. Выбор осуществляется на основе значения логической константы. Если эта константа равна сгие, тип Кеви1ст с помощью инструкции сурес(ей объявляется синонимом типа Та; в противном случае он обьявлястся синонимом типа ТЬ. Здесь важно отмеппь, что применение инструкции сурес(ей к шаблону класса нс приводит к инстанцированию компилятором С++ тела данного экземпляра.
Поэтому, если в программе встречается приведенный ниже фрагмент кода, то ни выражение Яйгт.<Н, 1 О, щйс(-1>, ни выражение ЯЧга<Н, тЫ, Н1> не инстанцнруются в полной мере. сурес(еб Сурепаще ИТЬепЕ1ве<(Н<тЫ*щЫ], ЯЧГС<Н,ЬО,щйсг 1>, ЯЧгк<Н,щйс(,Н1»::Кеви1СТ Яиьт; Какой бы из типов не оказался синонимом типа ЯиЬТ, полное инстанцированис происходит лишь при вычислении выражения ЯиЬТ:: геви1Ш Такая стратегия, в отличие от предпринятой перед этим, приводит к тому, что количество инстанцирований становится пропорциональным (ойз(Н).
Это значительно сокращает затраты вычислительных ресурсов, особенно при достаточно больших № 17.4. Применение переменных индукции Можно возразить, что мстапрограмма, рассмотренная в качестве предыдущего примера, выглядит слишком сложно и нс очень понятно, где и как применить изученный материал на практике. Поэтому рассмотрим более простую и более "итерационную" реализацию метапрограммы, вычисляющей квадратный корень. Этот простой итеративный алгоритм можно сформулировать так. Чтобы вычислить квадратный корень из Н, организуется цикл, в котором переменная 1 изменяется от 1 до Глава 17.
Метапрограммы 334 значения, квадрат которого равен или не превышает И. Это значение переменной 1 и будет искомым квадратным корнем из И. Сформулированный алгоритм, выраженный обычными средствами С++, выглядит, как показано ниже. 1пс 1/ бог (1=1; 1*1<И; ++1)( ) // Значение переменной 1 равно квадратному корню из И Однако для организации того же алгоритма в виде метапрограммы цикл нужно сформулировать с помощью рекурсии, после чего понадобится указать специализацию, завершающую процесс рекурсии.