Почему компилятор не дает ошибок при определении похожих шаблонов?

Какова процедура сравнения классов template? Стандарт не детализирован по этому вопросу (или мне не хватает места).
Мой вопрос НЕ НРАВИТСЯ, чтобы решить, какую специализацию использовать во время создания. Пожалуйста, не комментируйте это. Речь идет о сравнении специализаций друг с другом, чтобы решить, определена ли определенная специализация или нет.

Рассмотрим этот пример кода:

template <class x1, class x2>
struct CoreTemplate { };

template <class x1, class x2>
struct CoreTemplate<x1*, x2*> { int spec; CoreTemplate() { spec = 1; } };

template <class x1, class x2>
struct CoreTemplate<x2*, x1*> { int spec; CoreTemplate() { spec = 2; } };

int main(int argc, char* argv[])
{
    CoreTemplate<int*, int*> qq;
    printf("var=%d.\r\n", qq.spec);
}

Когда я пытаюсь скомпилировать этот код с помощью MSVC, я получаю ошибку для попытки создания экземпляра внутри функции main:

cpptest1.cxx(15): ошибка C2752: 'CoreTemplate<x1,x2>': более одной частичной специализации соответствует список аргументов шаблона

Для меня было бы логичнее выпустить ошибку для попытки объявления идентичных специализированных шаблонов. Я не вижу разницы между специализациями выше.

Итак, кто-нибудь знает правила сравнения специализированных шаблонов? Статьи, ссылки, книги и т.д. Также помогут.

Ответ 1

В стандарте указано, что это происходит только при попытке создать экземпляр шаблона (§14.5.4.1/1):

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

К сожалению, на оставшуюся часть вашего вопроса нельзя ответить, не обсуждая, как решить, какую специализацию использовать во время создания экземпляра. Здесь текст из стандарта (продолжение из выдержки выше):

Это делается путем сопоставления аргументов шаблона специализации шаблона шаблона с списками аргументов шаблона частичных специализаций.

  • Если найдена только одна соответствующая специализация, экземпляр создается из этой специализации.
  • Если найдено более одной соответствующей специализации, правила частичного заказа (14.5.4.2) используются для определения того, является ли одна из специализаций более специализированной, чем другие. Если ни одна из специализаций не является более специализированной, чем все другие соответствующие специализации, использование шаблона класса неоднозначно и программа плохо сформирована.

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

Теперь, конечно, верно, что ни одна из этих специализаций никогда не может быть использована, поскольку всегда будет двусмысленность - если они совпадают, то другой, очевидно, одинаково хорошо соответствует. Для компилятора просто нет необходимости обнаруживать или диагностировать это. В этом конкретном случае (по существу идентичные специализации), что, вероятно, будет легко, но есть почти наверняка другие случаи, когда это было бы намного сложнее, поэтому (по-видимому) комитет решил, что компилятор даже не должен был попробовать.

Ответ 2

Ah, но они не совпадают, поскольку они не используют одни и те же параметры. Использование clang и ваш оригинальный пример:

#include <cstdio>

template <class x1, class x2>
struct CoreTemplate { };

template <class x1, class x2>
struct CoreTemplate<x1*, x2*> { int spec; CoreTemplate() { spec = 1; } };
// note: partial specialization matches [with x1 = int, x2 = int]

template <class x1, class x2>
struct CoreTemplate<x2*, x1*> { int spec; CoreTemplate() { spec = 2; } };
// note: partial specialization matches [with x1 = int, x2 = int]

int main()
{
    CoreTemplate<int*, int*> qq;
    // error: ambiguous partial specializations of 'CoreTemplate<int *, int *>'
    std::printf("var=%d.\r\n", qq.spec);
}

Однако, если мы подберем частичные специализации, чтобы они точно соответствовали:

template <class x1, class x2>
struct Core { };

template <class x1>
struct Core<x1*, x1*> { int spec; Core() { spec = 1; } };
// note: previous definition is here

template <class x1>
struct Core<x1*, x1*> { int spec; Core() { spec = 2; } };
// error: redefinition of 'Core<type-parameter-0-0 *, type-parameter-0-0 *>'

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

Ответ 3

Для меня было бы логичнее выпустить ошибку для попытки объявить идентичные специализированные шаблоны.

Это не произойдет, потому что CoreTemplate<int*, double*> и CoreTemplate<double*, int*> будут генерировать разные типы.

Ниже мое предположение:
Компилятор не может выполнять дополнительные проверки здравомыслия/здравого смысла для тел template.
Когда вы создаете экземпляр template, компилятор в это время ищет подходящий тип и выбирает лучший. Если он не соответствует только одному, то он дает ошибку компилятора либо для несоответствия, либо для множественного соответствия.

Например:

template<typename T>
void foo ()
{
  T::x();
}

int main ()
{
}

Будет компилироваться отлично, хотя мы знаем, что в целом на С++-программе нет ни одного имени функции x(). Компилятор выдаст ошибку только тогда, когда вы попытаетесь создать экземпляр foo<T>.

Также, если вы немного покрутите свой пример, поставив main() между двумя специализациями, он компилируется отлично.