Рассмотрим следующий простой (в той мере, в какой это касается шаблонных вопросов) пример:
#include <iostream>
template <typename T>
struct identity;
template <>
struct identity<int> {
using type = int;
};
template<typename T> void bar(T, T ) { std::cout << "a\n"; }
template<typename T> void bar(T, typename identity<T>::type) { std::cout << "b\n"; }
int main ()
{
bar(0, 0);
}
И clang, и gcc печатают там «a». Согласно правилам в [temp.deduct.partial] и [temp.func.order], чтобы определить частичное упорядочение, нам нужно синтезировать несколько уникальных типов. Итак, у нас есть две попытки вычета:
+---+-------------------------------+-------------------------------------------+
| | Parameters | Arguments |
+---+-------------------------------+-------------------------------------------+
| a | T, typename identity<T>::type | UniqueA, UniqueA |
| b | T, T | UniqueB, typename identity<UniqueB>::type |
+---+-------------------------------+-------------------------------------------+
Для вычета на «б», согласно Ответ Ричарда Кордена, выражение typename identity<UniqueB>::type
рассматривается как тип и не оценивается. То есть это будет синтезировано, как если бы это было:
+---+-------------------------------+--------------------+
| | Parameters | Arguments |
+---+-------------------------------+--------------------+
| a | T, typename identity<T>::type | UniqueA, UniqueA |
| b | T, T | UniqueB, UniqueB_2 |
+---+-------------------------------+--------------------+
Понятно, что вычет на «б» не удается. Это два разных типа, поэтому вы не можете вывести T
для них обоих.
Тем не менее, мне кажется, что вычет на A
должен потерпеть неудачу. Для первого аргумента, вы бы соответствовали T == UniqueA
, Второй аргумент — это не выводимый контекст, так что если бы этот вывод не был успешным, если бы UniqueA
были конвертируемы в identity<UniqueA>::type
? Последнее является ошибкой замещения, поэтому я не вижу, как этот вывод мог бы быть успешным.
Как и почему gcc и clang предпочитают перегрузку «a» в этом сценарии?
Как обсуждалось в комментариях, я полагаю, что есть несколько аспектов алгоритма частичного упорядочения шаблона функции, которые не ясны или вообще не указаны в стандарте, и это показано в вашем примере.
Чтобы сделать вещи еще более интересными, MSVC (я тестировал 12 и 14) отклоняет вызов как неоднозначный. Я не думаю, что в стандарте есть что-либо, чтобы окончательно доказать, какой компилятор прав, но я думаю, что мог бы понять, откуда взялась разница; ниже есть примечание об этом.
Ваш вопрос (и этот) попросил меня провести еще одно расследование того, как все работает. Я решил написать этот ответ не потому, что считаю его авторитетным, а скорее организовать информацию, которую я нашел в одном месте (она не помещалась бы в комментариях). Надеюсь это будет полезно.
Во-первых, предлагаемое решение для выпуск 1391. Мы много обсуждали это в комментариях и чате. Я думаю, что, хотя он и дает некоторые пояснения, он также вносит некоторые проблемы. Изменяется [14.8.2.4p4] на (новый текст, выделенный жирным шрифтом):
Каждый тип, указанный выше из шаблона параметра и
соответствующий тип из шаблона аргумента используются в качестве типов
P
а такжеA
, Если конкретныйP
не содержит Шаблон-параметры тот
участвовать в выводе аргумента шаблона, чтоP
не используется для
определить порядок.
На мой взгляд, не очень хорошая идея по нескольким причинам:
P
не зависит, он не содержит никаких параметров шаблона вообще, поэтому он также не содержит никаких параметров, которые участвуют в выводе аргумента, что могло бы применить к нему выражение bold. Тем не менее, это сделало бы template<class T> f(T, int)
а также template<class T, class U> f(T, U)
неупорядоченный, что не имеет смысла. Возможно, это вопрос интерпретации формулировки, но это может привести к путанице.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
пара).
Заметки:
P
, после подстановки выведенных значений (назовите это выведенными A
), совместим с A
. Это может вызвать путаницу в отношении того, что на самом деле происходит во время частичного упорядочения; Там нет замены происходит.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 просто пропускает его, возвращая «success» для этого конкретного шага. Это происходит не только во время частичного упорядочения, но и для всех типов вычетов, и не только на верхнем уровне в списке параметров функции, но и рекурсивно всякий раз, когда встречается невыгруженный контекст в форме составного типа. Почему-то я обнаружил это удивительным, когда впервые увидел это. Размышляя об этом, это, конечно, имеет смысл, и в соответствии со стандартом ([…] не участвует в выводе типа […] в [14.8.2.5p4]).
Это согласуется с Ричард Корден комментарии к его ответ, но я должен был на самом деле увидеть код компилятора, чтобы понять все последствия (не ошибка его ответа, а скорее мой собственный — мыслитель программиста в коде и все такое).
Я включил немного больше информации о реализации Clang в этот ответ.
Я считаю, что ключ к следующему утверждению:
Второй аргумент — это не выводимый контекст — так что, если бы UniqueA не был преобразован в identity :: type, этот вывод не был бы успешным?
Тип удержания не выполняет проверку «преобразований». Эти проверки выполняются с использованием реальных явных и выводимых аргументов как часть разрешения перегрузки.
Вот мое резюме шагов, которые предпринимаются для выбора шаблона функции для вызова (все ссылки взяты из N3937, ~ C ++ ’14):
Компилятор уже знает на шаге 4, что обе специализации могут быть вызваны при использовании реальных аргументов. Шаги 5 и 6 используются для определения того, какая из функций является более специализированной.