Какова процедура частичного заказа в вычете шаблона
Читая стандарт C++11, я не могу полностью понять смысл следующего утверждения. Пример очень приветствуется.
Два набора типов используются для определения частичного упорядочения. Для каждого из задействованных шаблонов существует исходный тип функции и преобразованный тип функции. [Примечание: создание преобразованного типа описано в 14.5.6.2. - примечание конца] В процессе вывода используется преобразованный тип в качестве шаблона аргумента и исходный тип другого шаблона в качестве шаблона параметра. Этот процесс выполняется дважды для каждого типа, участвующего в сравнении частичного упорядочения: один раз с использованием преобразованного шаблона-1 в качестве шаблона аргумента и шаблона-2 в качестве шаблона параметра и снова с использованием преобразованного шаблона-2 в качестве шаблона аргумента и шаблона-1 в качестве шаблона параметра
- N3242 14.8.2.4.2
1 ответ
Хотя Xeo дал довольно хорошее описание в комментариях, я постараюсь дать пошаговое объяснение с рабочим примером.
Прежде всего, первое предложение из абзаца, который вы цитировали, гласит:
Для каждого из задействованных шаблонов существует исходный тип функции и преобразованный тип функции. [...]
Подожди, что это за "преобразованный тип функции"? Пункт 14.5.6.2/3 объясняет, что:
Чтобы создать преобразованный шаблон, для каждого типа, не типового или шаблонного параметра шаблона (включая его пакеты параметров шаблона (14.5.3)) синтезируйте уникальный тип, значение или шаблон класса соответственно и подставьте его для каждого вхождения этого параметра. в типе функции шаблона [...]
Это формальное описание может показаться неясным, но на самом деле это очень просто на практике. Давайте возьмем этот шаблон функции в качестве примера:
template<typename T, typename U>
void foo(T, U) // #1
Сейчас с T
а также U
являются параметрами типа, вышеприведенный абзац просит нас выбрать соответствующий аргумент типа для T
(что угодно) и заменить его везде в сигнатуре функции где T
затем сделать то же самое для U
,
Теперь "синтезировать уникальный тип" означает, что вам нужно выбрать вымышленный тип, который вы нигде не использовали, и мы могли бы назвать это P1
(а затем выбрать P2
за U
), но это сделало бы наше обсуждение бесполезным формальным.
Давайте просто упростим вещи и выберем int
за T
а также bool
за U
- мы не используем эти типы где-либо еще, поэтому для наших целей они так же хороши, как P1
а также P2
,
Итак, после преобразования имеем:
void foo(int, bool) // #1b
Это преобразованный тип функции для нашего оригинала foo()
шаблон функции.
Итак, давайте продолжим толковать абзац, который вы цитировали. Второе предложение гласит:
В процессе вывода используется преобразованный тип в качестве шаблона аргумента, а исходный тип другого шаблона - в качестве шаблона параметра. [...]
Подождите, что за "другой шаблон"? У нас только одна перегрузка foo()
до сих пор. Правильно, но для того, чтобы установить порядок между шаблонами функций, нам нужно как минимум два из них, поэтому нам лучше создать второй. Давайте использовать:
template<typename T>
void foo(T const*, X<T>) // #2
куда X
это какой-то наш классовый шаблон.
Что теперь с этим вторым шаблоном функции? Ах, да, нам нужно сделать то же самое, что мы делали ранее для первой перегрузки foo()
и преобразовать его: так что давайте снова выберем аргумент типа для T
и заменить T
везде. Я выберу char
на этот раз (мы не будем использовать его где-либо еще в этом примере, так что это так же хорошо, как некоторые вымышленные P3
):
void foo(char const*, X<char>) #2b
Отлично, теперь у него есть два шаблона функций и соответствующие преобразованные типы функций. Так как же определить #1
более специализированный, чем #2
или наоборот?
Из вышеприведенного предложения мы знаем, что исходные шаблоны и их преобразованные типы функций должны как-то совпадать. Но как? Вот что объясняет третье предложение:
Этот процесс выполняется дважды для каждого типа, участвующего в сравнении частичного упорядочения: один раз с использованием преобразованного шаблона-1 в качестве шаблона аргумента и шаблона-2 в качестве шаблона параметра и снова с использованием преобразованного шаблона-2 в качестве шаблона аргумента и шаблона-1 в качестве шаблона параметра
Так что в основном преобразованный тип функции первого шаблона (#1b
) сопоставляется с типом функции исходного второго шаблона (#2
). И конечно наоборот, преобразованный тип функции второго второго шаблона (#2b
) сопоставляется с типом функции исходного первого шаблона (#1
).
Если сопоставление будет успешным в одном направлении, но не в другом, то мы будем знать, что один из шаблонов более специализирован, чем другой. В противном случае, ни один из них не является более специализированным.
Давайте начнем. Прежде всего, мы должны соответствовать:
void foo(int, bool) // #1b
против:
template<typename T>
void foo(T const*, X<T>) // #2
Есть ли способ, которым мы можем выполнить вывод типа на T
чтобы T const*
становится точно int
а также X<T>
становится точно bool
? (на самом деле точное совпадение не является обязательным, но из этого правила действительно мало исключений, и они не имеют отношения к иллюстрации механизма частичного упорядочения, поэтому мы их проигнорируем).
Едва. Итак, давайте попробуем сопоставить наоборот. Мы должны соответствовать:
void foo(char const*, X<char>) // #2b
против:
template<typename T, typename U>
void foo(T, U) // #1
Можем ли мы вывести T
а также U
здесь, чтобы произвести точное совпадение для char const*
а также X<char>
соответственно? Конечно! Это тривиально. Мы просто выбираем T = char const*
а также U = X<char>
,
Итак, мы выяснили, что преобразованный тип функции нашей первой перегрузки foo()
(#1b
) не может быть сопоставлено с исходным шаблоном функции нашей второй перегрузки foo()
(#2
); с другой стороны, преобразованный тип функции второй перегрузки (#2b
) можно сопоставить с исходным шаблоном функции первой перегрузки (#1
).
Заключение? Вторая перегрузка foo()
более специализирован, чем первый.
Чтобы выбрать контрпример, рассмотрим эти два шаблона функций:
template<typename T, typename U>
void bar(X<T>, U)
template<typename T, typename U>
void bar(U, T const*)
Какая перегрузка более специализированная, чем другая? Я не буду повторять всю процедуру снова, но вы можете сделать это, и это должно убедить вас, что совпадение не может быть произведено ни в одном направлении, поскольку первая перегрузка является более специализированной, чем вторая, в отношении первого параметра, но второй более специализирован, чем первый, в отношении второго параметра.
Заключение? Ни одна из функций шаблона не является более специализированной, чем другая.
Теперь в этом объяснении я проигнорировал множество деталей, исключений из правил и загадочных отрывков в Стандарте, но механизм, описанный в приведенном вами абзаце, действительно такой.
Также обратите внимание, что тот же механизм, описанный выше, используется для установления "более специализированного, чем" порядка между частичными специализациями шаблона класса, сначала создав соответствующий шаблон фиктивной функции для каждой специализации, а затем упорядочив эти шаблоны функций через Алгоритм описан в этом ответе.
Это указано в пункте 14.5.5.2/1 стандарта C++11:
Для двух частичных специализаций шаблона класса первая является, по меньшей мере, такой же специализированной, как и вторая, если, учитывая следующее переписывание двух шаблонов функций, первый шаблон функции является, по меньшей мере, такой же специализированной, как и вторая, согласно правилам упорядочения для шаблонов функций (14.5.6.2):
- первый шаблон функции имеет те же параметры шаблона, что и первая частичная специализация, и имеет единственный параметр функции, тип которого является специализацией шаблона класса с аргументами шаблона первой частичной специализации, и
- второй шаблон функции имеет те же параметры шаблона, что и вторая частичная специализация, и имеет единственный параметр функции, тип которого является специализацией шаблона класса с аргументами шаблона второй частичной специализации.
Надеюсь, это помогло.