Язык программирования C++. Вводный курс

Разрешение имен в определениях шаблонов А


Внутри определения шаблона смысл некоторых конструкций может различаться в зависимости от конкретизации, тогда как смысл других всегда остается неизменным. Главную роль играет наличие в конструкции формального параметра шаблона:

template <typename Type>

   Type min( Type* array, int size )

{

   Type min_val = array[0];

   for (int i = 1; i < size; ++i)

       if ( array[i] < min_val )

          min_val = array[i];

   print( "Minimum value found: ");

   print( min_val );



   return min_val;

}

В функции min()

типы переменных array и min_val

зависят от фактического типа, которым будет заменен Type при конкретизации шаблона, тогда как тип переменной size

останется int при любом типе параметра шаблона. Следовательно, типы array и min_val в разных конкретизациях различны. Поэтому мы говорим, что типы этих переменных зависят от параметра шаблона, тогда как тип size от него не зависит.

Так как тип min_val

неизвестен, то неизвестна и операция, которая будет использоваться при появлении min_val в выражении. Например, какая функция print() будет вызвана при обращении print(min_val)? С типом аргумента int? Или float? Будет ли вызов ошибочным, поскольку не существует функции, которая может быть вызвана с аргументом того же типа, что и min_val? Принимая все это во внимание, мы говорим, что и вызов print(min_val)

зависит от параметра шаблона.

Такие вопросы не возникают для тех конструкций внутри min(), которые не зависят от параметров шаблона. Например, всегда известно, какая функция должна быть вызвана для print( "Minimum value found: "). Это функция печати строк символов. В данном случае print()

остается одной и той же при любой конкретизации шаблона, то есть не зависит от его параметров.

В главе 7 мы видели, что в C++ функция должна быть объявлена до ее вызова. Нужно ли объявлять функцию, вызываемую внутри шаблона, до того, как компилятор увидит его определение? Должны ли мы объявить функцию print()  в предыдущем примере до определения шаблона min()? Ответ зависит от особенностей имени, на которое мы ссылаемся. Конструкцию, не зависящую от параметров шаблона, следует объявить перед ее использованием в шаблоне. Представленное выше определение шаблона функции min() некорректно. Поскольку вызов


Если вы проектируете шаблон функции, то, вероятно, хотели бы сохранить контроль над тем, когда разрешаются имена в его определении. Предположим, что шаблон min() – это часть библиотеки, в которой определены и другие шаблоны и функции. Желательно, чтобы реализации min() по возможности использовали другие компоненты нашей же библиотеки. В предыдущем примере интерфейс библиотеки определен в заголовочном файле <primer.h>. Как объявление функции print(const char*), так и определение функции min()

являются частями интерфейса. Мы хотим, чтобы конкретизации шаблона min()

пользовались функцией print() из нашей библиотеки. Первый этап разрешения имени это гарантирует. Если имя, использованное в определении шаблона, не зависит от его параметров, то оно обязательно будет относиться к компоненту внутри библиотеки, т.е. к тому объявлению, которое включено в один пакет с этим определением в заголовочном файле <primer.h>.

На самом деле автор шаблона должен позаботиться о том, чтобы были объявлены все имена, использованные в определениях и не зависящие от параметров. Если этого нет, то определение шаблона вызовет ошибку. При конкретизации шаблона компилятор ее не исправляет:



// ---- primer.h ----

template <typename Type>

   Type min( Type* array, int size )

{

   Type min_val = array[0];

   // ...

   // ошибка: функция print( const char* ) не найдена

   print( "Minimum value found: " );

   // правильно: зависит от параметра шаблона

   print( min_val );

   // ...

}

// ---- user.C ----

#include <primer.h>

// это объявление print( const char* ) игнорируется

void print( const char* );

void print( int );

int ai[4] = {12, 8, 73, 45 };

int main() {

   int size = sizeof(ai) / sizeof(int);

   // конкретизируется min( int*, int )

   min( &ai[0], size );
}

Объявление функции

print( const char* ) в файле user.C

невидимо в том месте, где появляется определение шаблона. Однако оно видимо там, где конкретизируется шаблон min(int*,int), но это объявление не рассматривается при компиляции вызова print("Minimum value found: "), так как последний не зависит от параметров шаблона. Если некоторая конструкция в определении шаблона не зависит от его параметров, то имена разрешаются в контексте самого определения, и результат разрешения в дальнейшем не пересматривается. Поэтому на программиста возлагается ответственность за то, чтобы объявления имен, встречающихся в определении, были включены в интерфейс библиотеки вместе с шаблоном.



расположена сразу после функции main() в области видимости пространства имен:



// ...

int main() {

   // ...

   // использование min(SmallInt*,int)

   min( &asi[0], size );

}

// точка конкретизации min(SmallInt*,int)

// как будто объявление конкретизированной функции выглядит так:

SmallInt min( SmallInt* array, int size )
   { /* ... */ }

Но что, если конкретизация шаблона случается в одном исходном файле несколько раз? Где тогда будет точка конкретизации? Вы можете спросить: “А какая, собственно, разница?” В нашем примере для SmallInt

разница есть, поскольку объявление функции print(const SmallInt &)

должно появиться перед точкой конкретизации min(SmallInt*,int):



#include <primer.h>

void another();

SmallInt asi[4];

int main() {

   // задать значения элементов массива asi

   int size = sizeof(asi) / sizeof(SmallInt);

   min( &asi[0], size );

   another();

   // ...

}

// точка конкретизации здесь?

void another() {

   int size = sizeof(asi) / sizeof(SmallInt);

   min( &asi[0], size );

}
// или здесь?

В действительности точка конкретизации находится после определения каждой функции, в которой используется конкретизированный экземпляр. Компилятор может выбрать любую из этих точек, чтобы конкретизировать в ней шаблон. Отсюда следует, что при организации кода программы надо быть внимательным и помещать все объявления, необходимые для разрешения имен, зависящих от параметров некоторого шаблона, перед первой точкой. Поэтому разумно поместить их в заголовочный файл, который включается перед любой возможной конкретизацией шаблона:



#include <primer.h>

// user.h содержит объявления, необходимые при конкретизации

#include "user.h"

void another();

SmallInt asi[4];

int main() {

   // ...

}

// первая точка конкретизации min(SmallInt*,int)

void another() {

   // ...

}
// вторая точка конкретизации min(SmallInt*,int)

А если конкретизация шаблона происходит в нескольких файлах? Например, что будет, если функция another()



находится в другом файле, нежели main()? Тогда точка конкретизации есть в каждом файле, где используется конкретизированная из шаблона функция. Компилятор свободен в выборе любой из них, так что нам снова придется проявить аккуратность и включить файл "user.h" во все исходные файлы, где используются конкретизированные функции. Тем самым гарантируется, что реализация min(SmallInt*,int)

будет ссылаться именно на нашу функцию print(const SmallInt &) вне зависимости от того, какую из точек конкретизации выберет компилятор.

Упражнение 10.13

Назовите два шага разрешения имени в определениях шаблона. Объясните, каким образом первый шаг отвечает потребностям разработчика библиотеки, а второй обеспечивает гибкость, необходимую пользователям шаблонов.

Упражнение 10.14

На какие объявления ссылаются имена display и SIZE в реализации max(LongDouble*,SIZE)?



// ---- exercise.h ----

void display( const void* );

typedef unsigned int SIZE;

template <typename Type>

   Type max( Type* array, SIZE size )

{

   Type max_val = array[0];

   for ( SIZE i = 1; i < size; ++i )

      if ( array[i] > max_val )

         max_val = array[i];

   display( "Maximum value found: " );

   display( max_val );

   return max_val;

}

// ---- user.h ----

class LongDouble { /* ... */ };

void display( const LongDouble & );

void display( const char * );

typedef int SIZE;

// ---- user.C ----

#include <exercize.h>

#include "user.h"

LongDouble ad[7];

int main() {

   // задать значения элементов массива ad

   // конкретизируется max( LongDouble*, SIZE )

   SIZE size = sizeof(ad) / sizeof(LongDouble);

   max( &ad[0], size );
}


Содержание раздела