Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 [ 12 ] 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

Задача 7. Почему не специализируются

шаблоны функций? Сложность: 8

Хотя заголовком этой задачи является вопрос, его легко можно переформулировать в указание. Данная задача отвечает на вопрос о том, когда и почему не следует специализировать шаблоны.

Вопрос для новичка

1. Какие два основных вида шаблонов имеются в С++, и как они могут быть специализированы?

Вопрос для профессионала

2. Какая из версий функции f будет вызвана в последней строке приведенного кода? Почему?

tempiate<class Т> void f( т );

templateo

void f<int*>( int* );

tempiate<class T> void f( T* );

int *p;

f( p ); какая функция будет вызвана?

Решение

Перегрузка и специализация

Очень важно убедиться в правильном использовании терминологии при обсуждении данного вопроса, поэтому сначала - небольшой обзор.

1. Какие два основных вида шаблонов характерны для С++ и как они могут быть специализированы?

В С++ предусмотрено два типа шаблонов - шаблоны классов и шаблоны функций. Эти два типа шаблонов работают не совсем одинаково, и наиболее очевидное отличие заключается в перегрузке (overloading): обычные классы С++ не могут быть перегружены, так что не могут быть перегружены и их шаблоны. С другой стороны, функции в С+ + , имеющие одинаковые имена, могут быть перегружены, так что могут бьггь перегружены и шаблоны функций. Это вполне понятно и естественно, что демонстрируется следующим примером.

Пример 7-1: шаблоны классов и функций и перегрузка шаблон класса

tempiate<class т> class х { /* ...*/ }; (а)

Шаблон функции с перегрузкой

tempiate<c1ass т> void f( т ); (б)

tempiate<class T> void f( int, T, double ); (в)

Такие неспециализированные шаблоны называются также первичными шаблонами (primary templates).



Первичные шаблоны могут быть специализированы. В этом шаблоны классов и шаблоны функций сушественно отличаются, как вы увидите далее в данной задаче. Шаблон класса может быть частично (partially) или полностью специализирован (fully specialized). Шаблон функции может быть специализирован только полностью, но, поскольку шаблоны функции могут быть перегружены, мы можем получить при помоши перегрузки тот же эффект, что и при помоши частичной специализации. Эти отличия продемонстрированы в следующем примере.

Пример 7-1, продолжение: специализированные шаблоны

частичная специализация (а) для указателей tempiate<class т> class х<т*> {/*...*/};

полная специализация (а) для типа i nt templateo class X<int> { /* ... */ };

Отдельный первичный шаблон, перегружающий (б) and (в) - не частичная специализация (б), поскольку понятия частичной специализации шаблона функции не существует! tempiate<class Т> void f( т* ); (г)

Полная специализация (б) для типа int

templateo void f<int>( int ); (д)

Обычная функция, представляющая перегрузку функций (б), (в) и (г), но не (д) (об этом будет сказано немного позже)

void f( double ); (е)

> Рекомендация

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

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

• Функции, не являющиеся шаблонами, являются "привилегированными". При

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

• Если такой "привилегированной" функции нет, в качестве претендентов рассматриваются первичные шаблоны функций. То, какой именно первичный шаблон будет выбран, зависит от того обстоятельства, какой из них в наибольшей степени соответствует аргументам функции и является "наиболее специализированным" в соответствии с некими "загадочными" правилами. (Примечание: использование термина "специализированный" в данном контексте не имеет никакого отношения к специализации шаблонов.)

• Очевидно, что если имеется только один "наиболее специализированный" первичный шаблон функции, то используется именно он. Если первичный шаблон специализирован для используемых типов, то будет использоваться

В стандарте полная специализация называется "явной специализацией" (explicit specialization).



именно эта специализация; в противном случае будет выполнено инстанци-рование первичного шаблона с корректными типами.

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

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

Ниже представлен результат применения этих правил.

пример 7-1, продолжение: разрешение перегрузки

bool b; int i ; double d;

f( b ); вызов (6) с T = bool

f( i , 42, d ); вызов (в) с Т = int f( &i ); вызов (г) с т = int

fС i ); вызов (д)

f( d ); Вызов (е)

До сих пор я сознательно выбирал простейшие случаи; теперь можно перейти к более сложным вариантам.

Пример Димова-Абрамса Рассмотрим следующий код.

пример 7-2(а): явная специализация

tempiate<class Т> (а) первичный шаблон void f( т );

tempiate<class Т> (б) первичный шаблон, перегружающий void f( Т* ); (а) - шаблон функции не может быть

частично специализирован, возможна

только перегрузка

tempiateo (в) явная специализация (б)

void f<i nt>(i nt*);

... int *p;

f( p ); вызов (в)

Вызов в последней строке в примере 7-2(а) именно такой, как мы и ожидали. Вопрос в том, почему мы ожидали именно такой результат. Если это ожидание - следствие неправильного представления, то следующая информация может неприятно вас удивить. "Вряд ли, - можете сказать вы, - Я написал специализацию для указателя на int, так что очевидно, что именно она и должна быть вызвана". И будете совершенно неправы.

Обратимся ко второму вопросу, представив его так, как было предложено Питером Димовым (Peter Dimov) и Дэвидом Абрамсом (David Abrahams).

2. Какая из версий функции f будет вызвана в последней строке приведенного кода? Почему? пример 7-2(6): пример Димова-Абрамса

tempiate<class т> void f( т );



0 1 2 3 4 5 6 7 8 9 10 11 [ 12 ] 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85