С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 79
Текст из файла (страница 79)
После этого мы посмотрим, каким образом компиляторконкретизирует шаблоны и какие требования предъявляются в этой связи корганизации наших программ, а также обсудим, как определить специализациюдля такой конкретизации. Затем в данной главе будут изложены вопросы,представляющие интерес для проектировщиков шаблонов функций. Мы объясним,как можно перегружать шаблоны и как применительно к ним работает разрешениеперегрузки. Мы также расскажем о разрешении имен в определениях шаблоновфункций и покажем, как можно определять шаблоны в пространствах имен.
Главазавершается развернутым примером.10.1. Определение шаблона функцииИногда может показаться, что сильно типизированный язык создает препятствия дляреализации совсем простых функций. Например, хотя следующий алгоритм функцииmin() тривиален, сильная типизация требует, чтобы его разновидности былиint min( int a, int b ) {return a < b ? a : b;}double min( double a, double b ) {return a < b ? a : b;реализованы для всех типов, которые мы собираемся сравнивать:}Заманчивую альтернативу явному определению каждого экземпляра функции min()представляет использование макросов, расширяемых препроцессором:#define min(a, b) ((a) < (b) ? (a) : (b))Но этот подход таит в себе потенциальную опасность.
Определенный выше макросправильно работает при простых обращениях к min(), например:С++ для начинающихmin( 10, 20 );min( 10.0, 20.0 );но может преподнести сюрпризы в более сложных случаях: такой механизм ведет себя некак вызов функции, он лишь выполняет текстовую подстановку аргументов. В результатезначения обоих аргументов оцениваются дважды: один раз при сравнении a и b, а#include <iostream>#define min(a,b) ((a) < (b) ? (a) : (b))const int size = 10;int ia[size];int main() {int elem_cnt = 0;int *p = &ia[0];// подсчитать число элементов массиваwhile ( min(p++,&ia[size]) != &ia[size] )++elem_cnt;cout << "elem_cnt : "<< elem_cnt<< "\texpecting: " << size << endl;return 0;второй – при вычислении возвращаемого макросом результата:}На первый взгляд, эта программа подсчитывает количество элементов в массиве iaцелых чисел.
Но в этом случае макрос min() расширяется неверно, поскольку операцияпостинкремента применяется к аргументу-указателю дважды при каждой подстановке. Врезультате программа печатает строку, свидетельствующую о неправильныхвычислениях:elem_cnt : 5expecting: 10Шаблоны функций предоставляют в наше распоряжение механизм, с помощью которогоможно сохранить семантику определений и вызовов функций (инкапсуляция фрагментакода в одном месте программы и гарантированно однократное вычисление аргументов),не принося в жертву сильную типизацию языка C++, как в случае применения макросов.Шаблон дает алгоритм, используемый для автоматической генерации экземпляровфункций с различными типами.
Программист параметризует все или только некоторыетипы в интерфейсе функции (т.е. типы формальных параметров и возвращаемогозначения), оставляя ее тело неизменным. Функция хорошо подходит на роль шаблона,если ее реализация остается инвариантной на некотором множестве экземпляров,различающихся типами данных, как, скажем, в случае min().Так определяется шаблон функции min():468С++ для начинающих469template <class Type>Type min2( Type a, Type b ) {return a < b ? a : b;}int main() {// правильно: min( int, int );min( 10, 20 );// правильно: min( double, double );min( 10.0, 20.0 );return 0;}Если вместо макроса препроцессора min() подставить в текст предыдущей программыэтот шаблон, то результат будет правильным:elem_cnt : 10expecting: 10(В стандартной библиотеке C++ есть шаблоны функций для многих часто используемыхалгоритмов, например для min(). Эти алгоритмы описываются в главе 12.
А в даннойвводной главе мы приводим собственные упрощенные версии некоторых алгоритмов изстандартной библиотеки.)Как объявление, так и определение шаблона функции всегда должны начинаться сключевого слова template, за которым следует список разделенных запятымиидентификаторов, заключенный в угловые скобки '<' и '>', – список параметров шаблона,обязательно непустой.
У шаблона могут быть параметры-типы, представляющиенекоторый тип, и параметры-константы, представляющие фиксированное константноевыражение.Параметр-тип состоит из ключевого слова class или ключевого слова typename, закоторым следует идентификатор. Эти слова всегда обозначают, что последующее имяотносится к встроенному или определенному пользователем типу. Имя параметрашаблона выбирает программист. В приведенном примере мы использовали имя Type, ноtemplate <class Glorp>Glorp min2( Glorp a, Glorp b ) {return a < b ? a : b;могли выбрать и любое другое:}При конкретизации (порождении конкретного экземпляра) шаблона вместо параметратипа подставляется фактический встроенный или определенный пользователем тип.Любой из типов int, double, char*, vector<int> или list<double> являетсядопустимым аргументом шаблона.Параметр-константа выглядит как обычное объявление.
Он говорит о том, что вместоимени параметра должно быть подставлено значение константы из определения шаблона.Например, size – это параметр-константа, который представляет размер массива arr:С++ для начинающихtemplate <class Type, int size>Type min( Type (&arr) [size] );Вслед за списком параметров шаблона идет объявление или определение функции.
Еслине обращать внимания на присутствие параметров в виде спецификаторов типа иликонстант, то определение шаблона функции выглядит точно так же, как и для обычныхtemplate <class Type, int size>Type min( const Type (&r_array)[size] ){/* параметризованная функция для отыскания* минимального значения в массиве */Type min_val = r_array[0];for ( int i = 1; i < size; ++i )if ( r_array[i] < min_val )min_val = r_array[i];return min_val;функций:}В этом примере Type определяет тип значения, возвращаемого функцией min(), типпараметра r_array и тип локальной переменной min_val; size задает размер массиваr_array.
В ходе работы программы при использовании функции min() вместо Typeмогут быть подставлены любые встроенные и определенные пользователем типы, авместо size – те или иные константные выражения. (Напомним, что работать сфункцией можно двояко: вызвать ее или взять ее адрес).Процесс подстановки типов и значений вместо параметров называется конкретизациейшаблона. (Подробнее мы остановимся на этом в следующем разделе.)Список параметров нашей функции min() может показаться чересчур коротким.
Какбыло сказано в разделе 7.3, когда параметром является массив, передается указатель наего первый элемент, первая же размерность фактического аргумента-массива внутриопределения функции неизвестна. Чтобы обойти эту трудность, мы объявили первыйпараметр min() как ссылку на массив, а второй – как его размер. Недостаток подобногоподхода в том, что при использовании шаблона с массивами одного и того же типа int,но разных размеров генерируются (или конкретизируются) различные экземплярыфункции min().Имя параметра разрешено употреблять внутри объявления или определения шаблона.Параметр-тип служит спецификатором типа; его можно использовать точно так же, какспецификатор любого встроенного или пользовательского типа, например в объявлениипеременных или в операциях приведения типов.
Параметр-константа применяется какконстантное значение – там, где требуются константные выражения, например длязадания размера в объявлении массива или в качестве начального значения элементаперечисления.470С++ для начинающих// size определяет размер параметра-массива и инициализирует// переменную типа const inttemplate <class Type, int size>Type min( const Type (&r_array)[size] ){const int loc_size = size;Type loc_array[loc_size];// ...}Если в глобальной области видимости объявлен объект, функция или тип с тем жеименем, что у параметра шаблона, то глобальное имя оказывается скрытым.
Вследующем примере тип переменной tmp не double, а тот, что у параметра шаблонаtypedef double Type;template <class Type>Type min( Type a, Type b ){// tmp имеет тот же тип, что параметр шаблона Type, а не заданный// глобальным typedefType tm = a < b ? a : b;return tmp;Type:}Объект или тип, объявленные внутри определения шаблона функции, не могут иметь тоtemplate <class Type>Type min( Type a, Type b ){// ошибка: повторное объявление имени Type, совпадающего с именем// параметра шаблонаtypedef double Type;Type tmp = a < b ? a : b;return tmp;же имя, что и какой-то из параметров:}Имя параметра-типа шаблона можно использовать для задания типа возвращаемого// правильно: T1 представляет тип значения, возвращаемого min(),// а T2 и T3 – параметры-типы этой функцииtemplate <class T1, class T2, class T3>значения:T1 min( T2, T3 );В одном списке параметров некоторое имя разрешается употреблять только один раз.Например, следующее определение будет помечено как ошибка компиляции:471С++ для начинающих// ошибка: неправильное повторное использование имени параметра Typetemplate <class Type, class Type>Type min( Type, Type );Однако одно и то же имя можно многократно применять внутри объявления или// правильно: повторное использование имени Type внутри шаблонаtemplate <class Type>определения шаблона:template <class Type>Type min( Type, Type );Type max( Type, Type );Имена параметров в объявлении и определении не обязаны совпадать.