Приоритет/неопределенность специализации шаблона класса
При попытке реализовать несколько вещей, основанных на вариативных шаблонах, я наткнулся на то, что я не могу объяснить. Я сместил проблему на следующий фрагмент кода:
template <typename ... Args>
struct A {};
template <template <typename...> class Z, typename T>
struct test;
template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
static void foo() {
std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
}
};
template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
static void foo() {
std::cout << "I'm variadic!" << std::endl;
}
};
int main() {
test<A, A<int>>::foo();
}
В gcc возникает ошибка, потому что он рассматривает обе специализации в равной степени специализированными при попытке создать экземпляр test<A, A<int>>
:
main.cpp: In function 'int main()':
main.cpp:25:24: error: ambiguous template instantiation for 'struct test<A, A<int> >'
test<A, A<int>>::foo();
^~
main.cpp:11:12: note: candidates are: template<template<class ...> class Z, class T> struct test<Z, Z<T> > [with Z = A; T = int]
struct test<Z, Z<T>> {
^~~~~~~~~~~~~
main.cpp:18:12: note: template<template<class ...> class Z, class T, class ... Args> struct test<Z, Z<T, Args ...> > [with Z = A; T = int; Args = {}]
struct test<Z, Z<T, Args...>> {
Тем не менее, clang считает первую специализацию "более специализированной" (посредством частичного упорядочения: см. следующий раздел), поскольку она компилируется и печатает:
Я более специализирован, чем вариационная спецификация, хе-хе!
A живая демонстрация можно найти на Coliru. Я также попытался использовать версию gcc HEAD и получил те же ошибки.
Мой вопрос здесь: так как эти два известных компилятора ведут себя по-другому, какой из них прав, и этот кусок кода правильный С++?
Из разделов §14.5.5.1 и 14.5.5.2 стандартного черновика С++ 14 инициируется частичное упорядочение, чтобы определить, какая специализация должна быть выбрана:
(1.2). Если найдено более одной соответствующей специализации, правила частичного порядка (14.5.5.2) используются для определения является ли одна из специализаций более специализированной, чем другие. Если ни одна из специализаций является более специализированным, чем все другие соответствующие специализации, тогда использование шаблона класса неоднозначно, и программа плохо сформирована.
Теперь, согласно §14.5.5.2, специализации шаблона шаблона преобразуются в шаблоны функций с помощью этой процедуры:
Для двухчастичных частных специализированных спецификаций первая более специализированная, чем вторая, если, учитывая после перезаписи на два функциональных шаблона, первый шаблон функции более специализирован, чем второй в соответствии с правилами упорядочения шаблонов функций (14.5.6.2):
(1.1) - первый шаблон функции имеет те же параметры шаблона, что и первая частичная специализация, и имеет один параметр функции, тип которого является специализацией шаблона класса с аргументами шаблона первой частичной специализации и
(1.2) - второй шаблон функции имеет те же параметры шаблона, что и вторая частичная специализация и имеет один параметр функции, тип которого является специализацией шаблона класса с шаблоном аргументы второй частичной специализации.
Поэтому я попытался воспроизвести проблему с перегрузками шаблонов функций, которые должны генерировать преобразование, описанное выше:
template <typename T>
void foo(T const&) {
std::cout << "Generic template\n";
}
template <template <typename ...> class Z, typename T>
void foo(Z<T> const&) {
std::cout << "Z<T>: most specialized overload for foo\n";
}
template <template <typename ...> class Z, typename T, typename ... Args>
void foo(Z<T, Args...> const&) {
std::cout << "Z<T, Args...>: variadic overload\n";
}
Теперь, пытаясь использовать его вот так:
template <typename ... Args>
struct A {};
int main() {
A<int> a;
foo(a);
}
дает ошибку компиляции [неоднозначный вызов] как в clang, так и в gcc: живая демонстрация. Я ожидал, что у clang будет по крайней мере поведение, соответствующее случаю шаблона класса.
Тогда это моя интерпретация стандарта (который, как мне кажется, передается с @Danh), поэтому на этом этапе нам нужен language-lawyer, чтобы подтвердить это.
Примечание. Я просмотрел несколько трекеров LLVM и не смог найти билет на поведение, наблюдаемое при перегрузках шаблонов функций в этом вопросе.
Ответы
Ответ 1
От temp.class.order:
Для двух специализированных специализированных шаблонов шаблона первая более специализированная, чем вторая, если, учитывая следующую перестройку двух шаблонов функций, первый шаблон функции более специализирован, чем второй в соответствии с правилами упорядочения для шаблонов функций ([temp.func.order]
):
-
Каждый из двух шаблонов функций имеет те же параметры шаблона, что и соответствующая частичная специализация.
-
Каждый шаблон функции имеет один параметр функции, тип которого является специализацией шаблона класса, где аргументы шаблона являются соответствующими параметрами шаблона из шаблона функции для каждого аргумента шаблона в шаблоне-аргументе-списке простого шаблона -для частичной специализации.
Порядок:
template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
static void foo() {
std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
}
};
template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
static void foo() {
std::cout << "I'm variadic!" << std::endl;
}
};
зависит от порядка:
template <template <typename...> class Z, typename T>
void bar(test<Z, Z<T>>); // #1
template <template <typename...> class Z, typename T, typename ... Args>
void bar(test<Z, Z<T, Args...>>); // #2
От [temp.func.order]
:
Частичное упорядочение выбирает, какой из двух шаблонов функций более специализирован, чем другой, путем преобразования каждого шаблона поочередно (см. следующий параграф) и выполнения вывода аргумента шаблона с использованием типа функции. Процесс дедукции определяет, является ли один из шаблонов более специализированным, чем другой. Если это так, более специализированным шаблоном является тот, который выбран процессом частичного заказа.
Для создания преобразованного шаблона для каждого типа параметр непигового или шаблонного шаблона (включая пакеты параметров шаблона ([temp.variadic])) синтезирует уникальный шаблон типа, значения или класса соответственно и заменяет его на каждое вхождение этого параметра в тип функции шаблона.
Используя тип функции шаблона преобразованной функции, выполните вывод типа против другого шаблона, как описано в [temp.deduct.partial]
.
В этом параграфе для любой функции, преобразованной из любого синтезированного шаблона Z0
и типа T0
, который может образовать #1
, мы можем сделать вывод типа с #2
. Но функции, преобразованные из #2
с фиктивным шаблоном Z2
с любым типом T2
, и любой непустой набор Args2
не может быть выведен из #1
. #1
, очевидно, более специализирован, чем #2
.
clang++ в этом случае прав.
Собственно, этот и этот не удалось скомпилировать (из-за неоднозначности) как в g++, так и в clang. Кажется, что оба компилятора имеют трудное время с параметрами шаблона шаблона. (Последний явно упорядочен, потому что его порядок не имеет никакого вызова функции).