При чтении другого вопроса я столкнулся с проблемой частичного упорядочивания, которую я сократил до следующего тестового случая
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*)'