Правила выбора предпочитаемой специализации шаблона класса включают переписывание специализаций в шаблоны функций и определение того, какой шаблон функции является более специализированным, с помощью правил упорядочения для шаблонов функций [temp.class.order]. Рассмотрим этот пример:
#include <iostream>
template <class T> struct voider { using type = void; };
template <class T> using void_t = typename voider<T>::type;
template <class T, class U> struct A { };
template <class T> int foo(A<T, void_t<T>> ) { return 1; }
template <class T> int foo(A<T*, void> ) { return 2; }
int main() {
std::cout << foo(A<int*, void>{});
}
Оба gcc и clang print 2
Вот. Это имеет смысл с некоторыми предыдущими примерами — вывод на основе невыгруженного контекста (void
против void_t<T>
) просто игнорируется, поэтому выводит <T, void_t<T>>
против <X*, void>
удается, но выводит <T*, void>
против <Y, void_t<Y>>
не в обоих аргументах. Хорошо.
Теперь рассмотрим это обобщение:
#include <iostream>
template <class T> struct voider { using type = void; };
template <class T> using void_t = typename voider<T>::type;
template <int I> struct int_ { static constexpr int value = I; };
template <class T, class U> struct A : int_<0> { };
template <class T> struct A<T, void_t<T>> : int_<1> { };
template <class T> struct A<T*, void> : int_<2> { };
int main() {
std::cout << A<int*, void>::value << '\n';
}
И clang, и gcc сообщают об этой специализации как неоднозначной, между 1
а также 2
, Но почему? Синтезированные шаблоны функций не являются неоднозначными. В чем разница между этими двумя случаями?
Clang совместим с GCC (и совместим с существующим кодом, который зависит от обоих этих поведений).
Рассматривать [Temp.deduct.type] p1:
[…] предпринята попытка найти значения аргументов шаблона (тип для параметра типа, значение для параметра не типового типа или шаблон для параметра шаблона), который даст P после подстановки выведенных значений (назовите это выведенным A), совместимым с A.
Суть вопроса в том, что здесь означает «совместимость».
Частично упорядочивая шаблоны функций, Clang просто выводит в обоих направлениях; если вычет успешен в одном направлении, но не в другом, он предполагает, что результат будет «совместимым», и использует его в качестве результата упорядочения.
Однако при частичном упорядочении частичных специализаций шаблона класса «Clang» интерпретирует «совместимый» как «тот же». Поэтому он считает только одну частичную специализацию более специализированной, чем другую, если замена выведенных аргументов из одного из них в другой воспроизведет исходную частичную специализацию.
Изменение одного из этих двух в соответствии с другим нарушает существенный объем реального кода. 🙁
Других решений пока нет …