// правильно: T2 - это char, T3 - unsigned int
// T2 и T3 выведены из типа pf
ui_type (*pf)( char, ui_type ) = sum ui_type ;
// ошибка: опускать можно только “хвостовые” аргументы
ui_type loc4 = sum ui_type, , ui_type ( ch, ui );
Встречаются ситуации, когда невозможно вывести аргументы шаблона в контексте, где конкретизируется шаблон функции; следовательно, необходимо их явно задать. Именно выявление таких ситуаций и необходимость решить проблему послужила причиной поддержки явного задания аргументов шаблона в стандартном C++.
В следующем примере берется адрес конкретизированной функции sum() и передается в качестве аргумента перегруженной функции manipulate(). Как мы показали в разделе 10.2, невозможно понять, как именно нужно конкретизировать sum(), если есть только списки параметров функций manipulate(). Имеется две разных функции sum(), и обе удовлетворяют условиям вызова. Следовательно, вызов manipulate() неоднозначен. Одним из способов разрешения такой неоднозначности является явное приведение типов. Однако лучше использовать явное задание аргументов шаблона: оно позволяет указать, как именно конкретизировать sum(), и, следовательно, выбрать нужный вариант перегруженной функции manipulate(). Например:
template class T1, class T2, class T3
T1 sum( T2 op1, T3 op2 ) { /* ... */ }
void manipulate( int (*pf)( int,char ) );
void manipulate( double (*pf)( float,float ) );
int main()
{
// ошибка: какой из возможных экземпляров sum:
// int sum( int,char ) или double sum( float, float )?
manipulate( sum );
// берется адрес конкретизированного экземпляра
// double sum( float, float )
// вызывается: void manipulate( double (*pf)( float, float ) );
manipulate( sum double, float, float );
}
Отметим, что явное задание аргументов шаблона следует использовать только тогда, когда это абсолютно необходимо для разрешения неоднозначности или для конкретизации шаблона функции в контексте, где вывести аргументы невозможно. Во-первых, определение типов и значений аргументов шаблона проще оставить компилятору. А во-вторых, если мы модифицируем объявления в программе, так что типы аргументов функции при вызове конкретизированного шаблона изменятся, то компилятор автоматически скорректирует вызов без нашего вмешательства. С другой стороны, если аргументы шаблона заданы явно, необходимо проверить, что они по-прежнему отвечают новым типам аргументов функции. Поэтому мы рекомендуем избегать явного задания аргументов шаблона.
Назовите две ситуации, когда использование явного задания аргументов шаблона необходимо.
Пусть дано следующее определение шаблона функции sum():
template class T1, class T2, class T3
T1 sum( T2, T3 );
Какие из приведенных ниже вызовов ошибочны? Почему?
double dobj1, dobj2;
float fobj1, fobj2;
char cobj1, cobj2;
(a) sum( dobj1, dobj2 );
(b) sumdouble,double,double( fobj1, fobj2 );
(c) sumint( cobj1, cobj2 );
(d) sumdouble, ,double( fobj2, dobj2 );
10.5. Модели компиляции шаблонов А
Шаблон функции задает алгоритм для построения определений множества экземпляров функций. Сам шаблон не определяет никакой функции. Например, когда компилятор видит шаблон:
template typename Type
Type min( Type t1, Type t2 )
{
return t1 t2 ? t1 : t2;
}
он сохраняет внутреннее представление min(), но и только. Позже, когда встретится ее реальное использование, скажем:
int i, j;
double dobj = min( i, j );
компилятор строит определение min() по сохраненному внутреннему представлению.
Здесь возникает несколько вопросов. Чтобы компилятор мог конкретизировать шаблон функции, должно ли его определение быть видимо при вызове экземпляра этой функции? Например, нужно ли определению шаблона min() появиться до ее конкретизации c целыми параметрами при инициализации dobj? Следует ли помещать шаблоны в заголовочные файлы, как мы поступаем с определениями встроенных (inline) функций? Или в заголовочные файлы можно помещать только объявления шаблонов, оставляя определения в файлах исходных текстов?
Чтобы ответить на эти вопросы, нам придется объяснить принятую в C++ модель компиляции шаблонов, сформулировать требования к организации определений и объявлений шаблонов в программах. В C++ поддерживаются две таких модели: модель с включением и модель с разделением. В данном разделе описываются обе модели и объясняется их использование.