Как обсуждалось в комментариях, я считаю, что есть несколько аспектов алгоритма частичного упорядочивания шаблонов функций, которые неясны или не указаны вообще в стандарте, и это показано в вашем примере.
Чтобы сделать вещи еще более интересными, MSVC (я тестировал 12 и 14) отклоняет вызов как неоднозначный. Я не думаю, что в стандарте есть что-нибудь, чтобы окончательно доказать, какой компилятор правильный, но я думаю, что могу иметь представление о том, откуда взялось различие; есть примечание об этом ниже.
Ваш вопрос (и этот) побудил меня провести дополнительное исследование того, как все работает. Я решил написать этот ответ не потому, что считаю его авторитетным, а скорее для того, чтобы систематизировать информацию, которую я нашел, в одном месте (она не поместится в комментариях). Надеюсь, это будет полезно.
Во-первых, предлагаемое решение для проблемы 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 отклоняет вызов как неоднозначный; не знаю почему.
Алгоритм частичного упорядочивания, описанный в стандарте, говорит о синтезе уникального типа, значения или шаблона класса для генерации аргументов. Clang справляется с этим ... ничего не синтезируя. Он просто использует исходные формы зависимых типов (как заявлено) и сопоставляет их обоими способами. Это имеет смысл, поскольку замена синтезированных типов не добавляет никакой новой информации. Он не может изменить формы A
типов, поскольку обычно нет способа определить, в какие конкретные типы могут разрешаться заменяемые формы. Синтезируемые типы неизвестны, что делает их очень похожими на параметры шаблона.
При обнаружении P
, который является невыведенным контекстом, алгоритм вывода аргументов шаблона Clang просто пропускает его, возвращая "успех" для этого конкретного шага. Это происходит не только во время частичного упорядочивания, но и для всех типов выводов, и не только на верхнем уровне в списке параметров функции, но рекурсивно всякий раз, когда невыведенный контекст встречается в форме составного типа. По какой-то причине я обнаружил это удивительным, когда впервые увидел это. Если подумать, это, конечно, имеет смысл и соответствует стандарту ([...] не участвует в выводе типов [...] в [14.8.2.5p4]] ).
Это согласуется с комментариями Ричарда Кордена к его ответ, но я должен был действительно увидеть код компилятора, чтобы понять все последствия (не ошибка его ответа, а моя собственная ошибка - программист думает в коде и все такое).
Я включил дополнительную информацию о реализации Clang в этот ответ.
person
bogdan
schedule
30.07.2015
typename identity<UniqueB>::type
как кUniqueB_2
. Но почему вычет при успешном? Я немного переформулировал вопрос. - person Barry   schedule 16.07.2015T, typename identity<T>::type
становитсяT, U
, поэтому дедукция проходит успешно. Однако алгоритм явно выполняет еще несколько вещей, которые также не указаны в стандарте. В этом случае он «запоминает», чтоT
фактически используется в конструкции, котораяU
заменена, так что в конце процесса дедукции, еслиT
не был выведен откуда-то еще, дедукция по-прежнему не выполняется (есть подсказка на это в [14.8.2.4p11]). - person bogdan   schedule 16.07.2015bar(T)
противbar(typename identity<T>::type)
). Как я уже сказал, все это плохо определено. Проблема 1391, вероятно, изменит обработку невыведенные контексты и уточняйте детали, хотя все еще далеки от совершенства. - person bogdan   schedule 16.07.2015template<typename T> void f(T, T)
более специализирован, чемtemplate<typename T, typename U> void f(T, U)
. В стандарте нигде не говорится, что выводы для разных пар типов должны быть последовательными при частичном упорядочивании; указанный алгоритм имеет дело только с парами типов по отдельности. Моя версия: для параметров, для которых вызов функции имеет аргументы, вывод должен быть успешным для вызова синтезированной функции с использованием придуманных аргументов изA
против объявления синтезированного шаблона с использованием параметров изP
. - person bogdan   schedule 16.07.2015typename identity<UniqueB>::type --> UniqueB_2
. Хотите объединить все это в ответ? - person Barry   schedule 16.07.2015T == U == UniqueA
), вывод «последнее к первому» терпит неудачу (выводT,T
противUniqueB
,UniqueC
). - person Barry   schedule 16.07.2015T
, будут разными. - person bogdan   schedule 16.07.2015typename identity<UniqueB>::type --> UniqueB_2
, это также должно быть решено с помощью того, что P не используется для определения порядка. Если я правильно понимаю, это должно означать , что параP
/A
не используется (я не понимаю, как это имело бы смысл в противном случае), и в этом случае вы не получите единственный тип для удержания наоборот. - person bogdan   schedule 16.07.2015F
имеет такой параметр, аG
нет, будет меньше пар параметров, участвующих в определении того, является лиG
по крайней мере таким же специализированным, какF
, чем в противном случае вокруг. Мне придется подумать об этом еще немного, чтобы увидеть, как это повлияет на некоторые примеры. В любом случае, если никто не даст авторитетного ответа, попробую написать, скажем, завтра? - person bogdan   schedule 16.07.2015typename identity<T>::type
из рассмотрения. - person Barry   schedule 27.07.2015