При попытке реализовать несколько вещей, основанных на вариативных шаблонах, я наткнулся на то, что я не могу объяснить. Я сместил проблему на следующий фрагмент кода:
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 текущий проект)
Из разделов §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 и не смог найти билет на поведение, наблюдаемое при перегрузках шаблонов функций в этом вопросе.