Ответ 1
Как обсуждалось в комментариях, я считаю, что есть несколько аспектов алгоритма частичного упорядочения шаблонов функций, которые неясны или вообще не указаны в стандарте, и это показано в вашем примере.
Чтобы сделать вещи еще более интересными, MSVC (я тестировал 12 и 14) отклонил вызов как неоднозначный. Я не думаю, что в стандарте нет ничего, чтобы окончательно доказать, какой компилятор прав, но я думаю, что я мог бы понять, откуда эта разница; есть примечание об этом ниже.
Ваш вопрос (и этот) заставил меня сделать еще одно исследование того, как все работает. Я решил написать этот ответ не потому, что считаю его авторитетным, а скорее организовывать информацию, которую я нашел в одном месте (это не соответствовало комментариям). Надеюсь, это будет полезно.
Во-первых, предлагаемая резолюция для issue 1391. Мы подробно обсуждали его в комментариях и чатах. Я думаю, что, хотя он и дает некоторые разъяснения, он также вводит некоторые проблемы. Он изменяет [14.8.2.4p4] на (новый текст выделен жирным шрифтом):
Каждый тип, назначенный выше из шаблона параметра и соответствующий тип из шаблона аргумента используется как типы
P
иA
. Если конкретныйP
не содержит шаблонных параметров, которые участвовать в выводе аргумента шаблона, чтоP
не используется для определить порядок.
Не очень хорошая идея, на мой взгляд, по нескольким причинам:
- Если
P
не является зависимым, он не содержит никаких параметров шаблона вообще, поэтому он не содержит никого, участвующего в выводе аргумента, что сделало бы смелое выражение применительно к нему. Тем не менее, это сделало быtemplate<class T> f(T, int)
иtemplate<class T, class U> f(T, U)
неупорядоченными, что не имеет смысла. Это, вероятно, вопрос толкования формулировки, но это может вызвать путаницу. - Это противоречит понятию используемого для определения порядка, которое влияет на [14.8.2.4p11]. Это делает ошибки
template<class T> void f(T)
иtemplate<class T> void f(typename A<T>::a)
неупорядоченными (вывод выполняется с первого по второй, посколькуT
не используется в типе, используемом для частичного упорядочения в соответствии с новым правилом, поэтому он может оставаться без значения). В настоящее время все компиляторы, которые я тестировал, считают второй более специализированным. -
В следующем примере
#2
будет более специализированным, чем#1
:#include <iostream> template<class T> struct A { using a = T; }; struct D { }; template<class T> struct B { B() = default; B(D) { } }; template<class T> struct C { C() = default; C(D) { } }; template<class T> void f(T, B<T>) { std::cout << "#1\n"; } // #1 template<class T> void f(T, C<typename A<T>::a>) { std::cout << "#2\n"; } // #2 int main() { f<int>(1, D()); }
(
#2
второй параметр не используется для частичного упорядочения, поэтому вывод удаляется от#1
до#2
, но не наоборот). В настоящее время вызов неоднозначен и, вероятно, останется таким.
Посмотрев на реализацию алгоритма частичного упорядочения Clang, я думаю, что стандартный текст можно изменить, чтобы отразить то, что на самом деле происходит.
Оставьте [p4] как есть и добавьте следующее между [p8] и [p9]:
Для пары
P
/A
:
- Если
P
не зависит, вывод считается успешным тогда и только тогда, когдаP
иA
являются одним и тем же типом.- Замещение выведенных параметров шаблона в невыводимые контексты, появляющиеся в
P
, не выполняется и не влияет на результат процесса дедукции.- Если значения аргументов шаблона успешно выведены для всех параметров шаблона
P
, кроме тех, которые отображаются только в невыводимых контекстах, то вывод считается успешным (даже если некоторые параметры, используемые вP
, остаются без значения при конец процесса дедукции для этой парыP
/A
).
Примечания:
- О второй маркерной точке: [14.8.2.5p1] говорит о поиске значений аргументов шаблона, которые сделают
P
, после подстановки выведенных значений (назовите его выведеннымA
), совместимым сA
. Это может вызвать путаницу в том, что на самом деле происходит при частичном заказе; нет никакой замены. - В некоторых случаях MSVC, похоже, не реализует третий маркер. Подробнее см. Следующий раздел.
- Вторая и третья пулевые точки предназначены также для случаев, когда
P
имеет формы типаA<T, typename U::b>
, которые не охватываются формулировкой в номере 1391.
Измените текущий [p10] на:
Шаблон функции
F
по меньшей мере такой же специализированный, как шаблон функцииG
тогда и только тогда, когда:
- для каждой пары типов, используемых для определения порядка, тип из
F
по меньшей мере такой же специализированный, как тип изG
, и- при выполнении вычета с использованием преобразованного
F
в качестве шаблона аргумента иG
в качестве шаблона параметра после завершения вычитания для всех пар типов все параметры шаблона, используемые в типах изG
, которые используются для определения порядка, имеют значения, а те значения соответствуют всем парам типов.
F
более специализирован, чемG
, еслиF
по крайней мере, как специализированный посколькуG
иG
не являются, по меньшей мере, такими же специализированными, какF
.
Сделайте весь текущий [p11] заметкой.
(примечание, добавленное в резолюции от 1391 до [14.8.2.5p4], должно быть скорректировано также - оно отлично подходит для [14.8.2.1], но не для [14.8.2.4].)
Для MSVC в некоторых случаях все параметры шаблона в P
должны получать значения во время вычитания для этой конкретной пары P
/A
для того, чтобы вычет преуспел от A
до P
. Я думаю, что это может быть причиной возникновения расхождения в вашем примере и других, но я видел, по крайней мере, один случай, когда вышеприведенное не применяется, поэтому я не уверен, чему верить.
Другой пример, в котором, как представляется, применяется вышеприведенное выражение: изменение template<typename T> void bar(T, T)
до template<typename T, typename U> void bar(T, U)
в ваших примерах свопирует результаты вокруг: вызов неоднозначен в Clang и GCC, но разрешается b
в MSVC.
В одном примере, где это не так:
#include <iostream>
template<class T> struct A { using a = T; };
template<class, class> struct B { };
template<class T, class U> void f(B<U, T>) { std::cout << "#1\n"; }
template<class T, class U> void f(B<U, typename A<T>::a>) { std::cout << "#2\n"; }
int main()
{
f<int>(B<int, int>());
}
Это выбирает #2
в Clang и GCC, как и ожидалось, но MSVC отклоняет вызов как неоднозначный; не знаю, почему.
Алгоритм частичного упорядочения, описанный в стандарте, говорит о синтезе уникального типа, значения или шаблона класса для генерации аргументов. Клэнг управляет этим... не синтезируя ничего. Он просто использует оригинальные формы зависимых типов (как заявлено) и соответствует им в обоих направлениях. Это имеет смысл, поскольку замена синтезированных типов не добавляет никакой новой информации. Он не может изменять формы типов A
, поскольку вообще невозможно определить, какие конкретные типы могут быть заменены замещаемыми формами. Синтезированные типы неизвестны, что делает их очень похожими на параметры шаблона.
При столкновении с P
, который является не выводимым контекстом, алгоритм дедукции аргумента шаблона Клана просто пропускает его, возвращая "успех" для этого конкретного шага. Это происходит не только во время частичного упорядочения, но и для всех типов вычетов, а не только на верхнем уровне в списке параметров функции, но рекурсивно всякий раз, когда невыводимый контекст встречается в виде составного типа. По какой-то причине я обнаружил, что это удивительно в первый раз, когда я это увидел. Думая об этом, оно, конечно, имеет смысл и соответствует стандарту ([...] не участвует в выводе типа [...] в [14.8.2.5p4]).
Это согласуется с комментариями Ричарда Кордена к его ответу, но мне нужно было увидеть компилятор кода, чтобы понять все последствия (не по вине его ответа, а скорее из моего собственного - программиста, думающего в коде и всего этого).
Я добавил дополнительную информацию о реализации Clang в этом ответе.