Что процедура частичного заказа в шаблоном выводе

Я читал о стандарте С++ 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 стандарта С++ 11:

Для двухчастичных специализированных специализированных шаблонов первая, по крайней мере, такая же специализированная, как и вторая, если после перезаписи на два функциональных шаблона, первый шаблон функции, по меньшей мере, такой же специализированный, как второй в соответствии с правилами упорядочения для шаблонов функций (14.5.6.2): ​​

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

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

Надеюсь, что это помогло.