При чтении другого вопроса я столкнулся с проблемой частичного упорядочивания, которую я сократил до следующего тестового случая
template<typename T>
struct Const { typedef void type; };
template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1
template<typename T>
void f(T, void*) { cout << "void*"; } // T2
int main() {
// GCC chokes on f(0, 0) (not being able to match against T1)
void *p = 0;
f(0, p);
}
Для обоих шаблонов функций тип функции специализации, которая вводит разрешение перегрузки, составляет void(int, void*). Но частичный заказ (согласно comeau и GCC) теперь говорит о том, что второй шаблон более специализирован. Но почему?
Позвольте мне пройти частичный заказ и показать, где у меня есть вопросы. May Q является уникальным составом, используемым для определения частичного упорядочения в соответствии с 14.5.5.2.
- Преобразованный список параметров для
T1(вставлен Q):(Q, typename Const<Q>::type*). Типы аргументовAT=(Q, void*) - Преобразованный список параметров для
T2(вставлен Q):BT=(Q, void*), которые также являются типами аргументов. - Непереданный список параметров для
T1:(T, typename Const<T>::type*) - Непереведенный список параметров для
T2:(T, void*)
Так как С++ 03 недоопределяет это, я использовал намерение, о котором я читал в нескольких отчетах о дефектах. Перечисленный выше список параметров для T1 (называемый мной AT) используется как список аргументов для 14.8.2.1 "Вывод аргументов шаблона из вызова функции".
14.8.2.1 больше не нужно преобразовывать AT или BT (например, удаление ссылочных деклараторов и т.д.) и перейти прямо к 14.8.2.4, который независимо для каждой пары A/P делает вывод типа:
-
ATпротивT2:{(Q, T),(void*, void*)},Tявляется единственным параметром шаблона здесь, и он найдет, чтоTдолжен бытьQ. Типовой вывод преуспевает тривиально дляATпротивT2. -
BTпротивT1:{(Q, T),(void*, typename Const<T>::type*)}, Он найдет, чтоTтожеQ.typename Const<T>::type*- это не выведенный контекст, и поэтому он не будет использоваться для вывода чего-либо.
Вот мой первый вопрос: будет ли теперь использовать значение T для первого параметра? Если ответ отрицательный, тогда первый шаблон более специализирован. Это не может быть так, потому что как GCC, так и Comeau говорят, что второй шаблон более специализирован, и я не верю, что они ошибаются. Поэтому мы принимаем "да" и вставляем void* в T. В параграфе (14.8.2.4) говорится: "Дедукция выполняется независимо для каждой пары, и результаты затем объединяются", а также "В определенных контекстах, однако, значение не участвует в выводе типа, но вместо этого использует значения аргументов шаблона, которые были либо выведены в другом месте, либо явно указаны". Это похоже на "да".
Таким образом, для каждой пары A/P это также удается. Теперь каждый шаблон, по крайней мере, так же специализирован, как и другой, поскольку вычет также не зависит от каких-либо неявных преобразований и преуспел в обоих направлениях. В результате, вызов должен быть неоднозначным.
Итак, мой второй вопрос: теперь, почему в реализациях говорят, что второй шаблон более специализирован? Какую точку зрения я не заметил?
Изменить. Я тестировал явную специализацию и создание экземпляра, и оба в последних версиях GCC (4.4) говорят мне, что ссылка на специализацию неоднозначна, а более старая версия GCC (4.1) не вызывает этой ошибки двусмысленности. Это говорит о том, что недавние версии GCC имеют непоследовательный частичный порядок для шаблонов функций.
template<typename T>
struct Const { typedef void type; };
template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1
template<typename T>
void f(T, void*) { cout << "void*"; } // T2
template<> void f(int, void*) { }
// main.cpp:11: error: ambiguous template specialization
// 'f<>' for 'void f(int, void*)'