Подтвердить что ты не робот

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

Правила выбора специализированной специализации шаблонов предпочтительнее включают переписывание специализаций в шаблоны функций и определение того, какой шаблон функции более специализирован с помощью правил упорядочения для шаблонов функций [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 печатают 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. Но почему? Синтезированные шаблоны функций не являются двусмысленными. Какая разница между этими двумя случаями?

4b9b3361

Ответ 1

Clang является GCC-совместимым (и совместим с существующим кодом, который зависит от обоих этих типов поведения).

Рассмотрим [temp.deduct.type] p1:

[...] делается попытка найти значения аргументов шаблона (тип для параметра типа, значение для параметра не-типа или шаблон для параметра шаблона), который сделает P после замены выведенные значения (назовем его выведенным A), совместимым с A.

Суть проблемы в том, что здесь означает "совместимый".

При частичном упорядочении шаблонов функций Clang просто выводит в обоих направлениях; если вычет преуспевает в одном направлении, но не в другом, он предполагает, что результат будет "совместим" и использует его как результат упорядочивания.

При частичном упорядочении частичных специализированных шаблонов классов Кланг интерпретирует "совместимый" как означающий "тот же". Поэтому он рассматривает только одну частичную специализацию как более специализированную, чем другую, если подставить выведенные аргументы из одного из них в другое, воспроизвести оригинальную частичную специализацию.

Изменение одного из этих двух в соответствии с другим сломает существенные суммы реального кода.: (