Как разрешается конфликт между полной и частичной специализациями шаблона класса, если обе подходят одному типу?
Полная специализация имеет приоритет над частичной, если она объявлена и видима в точке использования. Частичная специализация участвует в выборе только тогда, когда подходящей полной специализации нет; среди нескольких частичных выбирается наиболее специализированная.
Шаблоны позволяют описать общее поведение для семейства типов, а специализации — адаптировать его для особых случаев. Полная специализация решает задачу точечного переопределения поведения для одного конкретного типа, тогда как частичная специализация задаёт правило для целого подмножества типов.
Такое разделение позволяет сохранить общий шаблон для большинства типов и отдельно оптимизировать или изменить семантику для заранее известного типа.
Одна и та же специализация может подходить типу по нескольким правилам. Например, тип может одновременно удовлетворять частичной специализации для всех указателей и иметь собственную полную специализацию.
Ошибочно считать, что полная специализация сравнивается с частичными как ещё один кандидат частичного упорядочивания. Это отдельный приоритет: сначала учитывается наличие полной специализации для точного набора аргументов, а частичные специализации рассматриваются как шаблонные правила.
Важно также, чтобы полная специализация была объявлена до использования, которое могло бы вызвать неявное инстанцирование основной версии. Позднее объявление может привести к некорректной программе, а в некоторых случаях диагностика не обязана появиться.
Рассмотрим минимальный пример:
Для double* подходит только частичная специализация Trait<T*>, поэтому результатом будет 1. Для int* подходят и частичная специализация, и полная специализация Trait<int*>, но используется полная; результатом будет 2.
Механизм можно представить так:
Полная специализация не создаёт нового шаблонного правила: она описывает конкретную уже существующую специализацию шаблона. Поэтому у неё нет параметров, которые нужно выводить или сравнивать при частичном упорядочивании.
Полную специализацию обычно объявляют в заголовочном файле, если она должна быть видна во всех единицах трансляции. Определение следует делать с учётом правила единственного определения, например через inline для переменных или размещение определения в подходящем заголовочном интерфейсе.
В библиотеке есть признак для типов-указателей. Для всех указателей включается общий быстрый путь, но для char* требуется специальная обработка, например интерпретация значения как строки.
Вариант с изменением основной специализации неудобен: он усложняет общий код и может повлиять на несвязанные типы. Вариант с ещё одной частичной специализацией для указателей на char задаёт правило для семейства типов, но не выражает намерение обработать ровно char* и может конфликтовать с правилами для const char* или других квалификаций.
Выбранная полная специализация для char* явно фиксирует исключение. Общая частичная специализация обслуживает остальные указатели, а точечное поведение для char* выбирается автоматически. Если нужны одинаковые правила для char*, const char* и volatile char*, лучше использовать отдельную частичную специализацию с соответствующей структурой параметров, а не несколько несвязанных полных специализаций.
Обычно нельзя, если первое использование уже могло вызвать неявное инстанцирование основной версии. Явная специализация должна быть объявлена до такой точки использования. Иначе программа становится некорректной; при размещении кода в разных единицах трансляции проблема может проявляться непредсказуемо для разработчика и не всегда сопровождаться обязательной диагностикой.
Компилятор применит правила частичного упорядочивания. Если одна специализация строго более специализирована, будет выбрана она. Если ни одна не является более специализированной, возникает неоднозначность, даже если обе специализации по отдельности корректны.
Trait<int*> на const int*?Нет. Полная специализация соответствует точному набору аргументов шаблона. int* и const int* — разные типы, поэтому для const int* полная специализация Trait<int*> не подходит. В таком случае компилятор будет искать подходящую частичную специализацию, например правило для указателя на произвольный тип.