Должен ли следующий код компилироваться в соответствии со стандартом С++?

#include <type_traits>

template <typename T>
struct C;

template<typename T1, typename T2>
using first = T1;

template <typename T>
struct C<first<T, std::enable_if_t<std::is_same<T, int>::value>>>
{
};

int main ()
{
}

Результаты компиляции разными компиляторами:

MSVC:

ошибка C2753: 'C': частичная специализация не может соответствовать списку аргументов для первичного шаблона

НКУ-4,9:

ошибка: частичная специализация "C" не специализируется на аргументах шаблона

clang все версии:

ошибка: частичная специализация шаблона шаблона не специализируется ни на одном аргументе шаблона; для определения первичного шаблона, удалите список аргументов шаблона

GCC-5 +: успешно компилирует

И еще я хочу отметить, что тривиальная специализация вроде:

template<typename T>
struct C<T>
{
};

успешно не скомпилируется gcc. Похоже, он выясняет, что специализация в моем первоначальном примере нетривиальна. Поэтому мой вопрос - это шаблон, подобный этому, явно запрещенный стандартом С++ или не?

Ответы

Ответ 1

Ключевым пунктом является [temp.class.spec]/(8.2), который требует, чтобы частичная специализация была более специализированной, чем первичный шаблон. То, о чем действительно жалуется Кланг, это список аргументов, идентичный первому шаблону: это было удалено из [temp.class.spec]/(8.3) by issue 2033 (в котором говорилось, что требование было избыточным) довольно недавно, так еще не реализовано в Clang. Однако, по-видимому, он был реализован в GCC, учитывая, что он принимает ваш фрагмент; он даже компилирует следующие, возможно, по той же причине он компилирует ваш код (он также работает только с версии 5 и далее):

template <typename T>
void f( C<T> ) {}

template <typename T>
void f( C<first<T, std::enable_if_t<std::is_same<T, int>::value>>> ) {}

т.е. он признает, что декларации отличаются друг от друга, поэтому должно было выполнить некоторое решение проблемы 1980. Он не обнаружил, что вторая перегрузка более специализирована (см. Ссылку Wandbox), однако, которая несовместима, поскольку она должна была диагностировать ваш код в соответствии с вышеупомянутым ограничением в (8.2).

Можно утверждать, что текущая формулировка делает ваш пример частичной упорядочивания по желанию & dagger;: [temp.deduct.type]/1 упоминает, что в выводе из типов

Аргументы шаблона могут быть выведены в нескольких разных контекстах, но в каждом случае тип, указанный в терминах параметров шаблона (назовите его P), сравнивается с фактическим типом (назовите его A) и делается попытка найти значения аргументов шаблона [...], которые будут делать P, после подстановки вычисленных значений (назовем его выведенным A), совместимым с A.

Теперь через [temp.alias]/3 это будет означать, что на этапе частичного заказа, в котором шаблон функции частичной специализации шаблон параметра, подстановка в is_same дает значение false (поскольку для реализации общей библиотеки просто используется частичная специализация, которая должна завершиться неудачей), а enable_if терпит неудачу. & Dagger; Но эта семантика не удовлетворяет в в общем случае, потому что мы могли бы построить условие, которое, как правило, преуспевает, поэтому встречается уникальный синтезированный тип, и вывод преуспевает в обоих направлениях.

Предположительно, самым простым и надежным решением является игнорирование отброшенных аргументов при частичном упорядочении (что делает ваш пример плохо сформированным). В этом случае можно также ориентироваться на поведение реализаций (аналогично выпуску 1157):

template <typename...> struct C {};

template <typename T>
void f( C<T, int> ) = delete;

template <typename T>
void f( C<T, std::enable_if_t<sizeof(T) == sizeof(int), int>> ) {}

int main() {f<int>({});}

Оба Clang и GCC диагностируют это как вызывая удаленную функцию, то есть соглашайтесь с тем, что первая перегрузка является более специализированной, чем другая. Критическое свойство # 2, по-видимому, состоит в том, что второй аргумент шаблона зависит еще и T появляется только в невыводимых контекстах (если мы изменим int на T в # 1, ничего не изменится). Таким образом, мы могли бы использовать существование отброшенных (и зависимых?) Аргументов шаблона в качестве тай-брейкеров: таким образом нам не нужно рассуждать о природе синтезированных ценностей, что является статус-кво, а также получить разумное поведение в вашем случае, который был бы хорошо сформирован.


& dagger; @T.C. что шаблоны, сгенерированные через [temp.class.order], в настоящее время будут интерпретироваться как один многократно объявленный объект — опять же, см. вопрос 1980, Это не имеет прямого отношения к стандартным в этом случае, поскольку в формулировке никогда не упоминается, что эти шаблоны функций объявлены, не говоря уже о той же программе; он просто указывает их, а затем возвращается к процедуре для шаблонов функций.

& Dagger; Не совсем понятно, какие глубинные реализации необходимы для выполнения этого анализа. Проблема 1157 показывает, какой уровень детализации требуется для "правильного" определения того, является ли домен шаблона надлежащим подмножеством другого. Практически и разумно реализовать частичный заказ, чтобы быть таким сложным. Тем не менее, в разделе footnoted просто указывается, что этот вопрос не обязательно указан ниже, но является дефектным.

Ответ 2

Я думаю, вы могли бы упростить свой код - это не имеет никакого отношения к type_traits. Вы получите те же результаты со следующим:

template <typename T>
struct C;

template<typename T>
using first = T;

template <typename T>
struct C<first<T>>  // OK only in 5.1
{
};

int main ()
{
}

Проверьте онлайн-компилятор (компилируется под 5.1, но не с 5.2 или 4.9, так что это, вероятно, ошибка) - https://godbolt.org/g/iVCbdm

Я думаю, что int GCC 5 они перемещались по функциональности шаблона, и даже возможно создать две специализации того же типа. Он будет скомпилирован, пока вы не попытаетесь его использовать.

template <typename T>
struct C;

template<typename T1, typename T2>
using first = T1;

template<typename T1, typename T2>
using second = T2;

template <typename T>
struct C<first<T, T>>  // OK on 5.1+
{
};

template <typename T>
struct C<second<T, T>>  // OK on 5.1+
{
};

int main ()
{
   C<first<int, int>> dummy; // error: ambiguous template instantiation for 'struct C<int>'
}

https://godbolt.org/g/6oNGDP

Возможно, это связано с добавлением поддержки шаблонов переменных С++ 14. https://isocpp.org/files/papers/N3651.pdf