|
Программирование >> Обобщенные обратные вызовы
именно эта специализация; в противном случае будет выполнено инстанци-рование первичного шаблона с корректными типами. В противном случае, если имеется несколько таких наиболее специализированных первичных шаблонов функций, то вызов неоднозначен, так как компилятор не в состоянии выбрать наилучший из них. Программист при этом должен самостоятельно сделать выбор и предпринять некоторые уточняющие действия, которые скорректируют работу компилятора. Если ни один первичный шаблон функции не подходит, вызов считается некорректным и программист должен исправить данный код. Ниже представлен результат применения этих правил. пример 7-1, продолжение: разрешение перегрузки ЬооТ Ь; int i ; doublе d; f( b ); вызов (б) с т = 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( т ); templateo void f<int*>( int* ); tempiate<class т> void f( T* ); ... int *p; f( p ); какая функция будет вызвана? Ответ в данном случае - ... третья функция f. Давайте еще раз рассмотрим прел-ставленный код, комментируя его так, как .это было сделано в примере 7-2(а), чтобы сравнить и противопоставить эти два примера. template<class Т> (а) такой же обычный первичный void f( т ); шаблон, как и ранее templateo (в) явная специализация - на этот void f<int*>( int* ); раз это явная специализация (а) tempiate<class т> (б) второй первичный шаблон, void f( т* ); перегружающий (а) ... int р; f( р ); вызов (6)! Разрешение перегрузки игнорирует специализацию и работает только с базовыми шаблонами функций. Если изложенное удивляет вас - вы не одиноки в своем удивлении. В свое время это удивляло немалое количество экспертов. Ключ к пониманию приведенного кода прост, и он состоит в том, что специализации не перегружают функции. Перегружаться могут только первичные шаблоны (конечно, наряду с обычными нешаблонными функциями). Рассмотрим еще раз вторую часть правил разрешения перегрузки, но на этот раз в них будут особо акцентированы некоторые моменты, Если такой привилегированной функции нет, в качестве претендентов рассматриваются первичные шаблоны функций. То, какой именно первичный шаблон будет выбран, зависит от того обстоятельства, какой из них в наибольшей степени соответствует аргументам функции и является наиболее специализированным [..,] Очевидно, что если имеется только один наиболее специализированный первичный шаблон функции, то используется именно он. Если первичный шаблон специализирован для используемых типов, то будет использоваться именно эта специализация; в противном случае будет выполнено инстанци-рование первичного шаблона с корректными типами. Компилятор выбирает только первичный шаблон (или нешаблонную функцию, если таковая имеется). И только после того, как первичный шаблон выбран, компилятор начинает анализировать, есть ли подходящая специализация выбранного шаблона, и если находит таковую, то использует ее. > Рекомендация Стоит запомнить, что специализации шаблона функции не участвуют в разрешении перегрузки. Специализация используется только тогда, когда первичный шаблон функции уже выбран, причем на этот выбор не влияет наличие или отсутствие специализации шаблона. Мораль сей басни такова... Если ваша логика подобна моей, то первый ваш вопрос после этого будет таким: Но ведь я написал конкретную специализацию для случая, когда параметр функиии имеет тип int*, и этот int* совершенно точно соответствует типу аргумента в вызове функции - так почему бы не использовать именно эту специализацию? Увы, здесь вы ошибаетесь. Если вы хотите гарантировать вызов вашей функции в случае точного соответствия, вы должны сделать ее обычной нешаблонной функцией, а не специализацией шаблона. Объяснение, почему специализации не участвуют в перегрузке, очень простое. Комитет по стандарту указал, что было бы удивительно, если бы только из-за того, что вы написали специализацию для некоторого шаблона, изменялся выбор используемого шаблона. Это объяснение вкупе с наличием способа, гарантирующего использование нашей версии функции (достаточно сделать ее не специализацией, а обычной функцией), позволяет нам более точно и ясно понять, почему специализации не влияют на выбор шаблонов. > Рекомендация Мораль №1. Если вы хотите настроить первичный шаблон функции так, чтобы при разрешении перегрузки (или всегда в случае точного соответствия типов) использовалась ваша функция, делайте се не специализацией, а обычной нешаблонной функцией. Следствие. Если вы используете перегрузку шаблона функции, избегайте его специализаций. Но что если вы один из тех, кто не только использует, но и пишет шаблоны функций? Можете ли вы найти лучшее решение и заранее избежать этой и других проблем как для себя, так и для ваших пользователей? Да, можете. пример 7-2(в): иллюстрация к морали №2 tempiate<class т> struct Fimpl; tempiate<class T> void f(T t) {FImpl<T>::f( t );} Руками не трогать! :) tempiate<class T> struct Fimpl { static void f( T t ); Специализируйте здесь > Рекомендация Мораль №2. Если вы пишете первичный шаблон функции, который с большой вероятностью будет специализирован, лучше писать его как отдельный шаблон функции, который никогда не специализируется и не перегружается и будет просто перенаправлять вызов шаблону класса со статической функцией с той же сигнатурой. Таким образом, все пользователи получают возможность специализировать этот класс как полностью, так и частично, никак не влияя при этом на разрешение перегрузки. Резюме Перегрузка шаблонов функций - вполне корректная вещь. Разрешение перегрузки рассматривает все первичные шаблоны функций в качестве равных претендентов,
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.063
При копировании материалов приветствуются ссылки. |