Может ли параметр шаблона шаблона varadic быть частично-специализированным?

Рассмотрим следующую программу:

template<template<typename ...> class>
struct foo {};

template<template<typename> class C>
struct foo<C> {};

int main() {}

Clang отклоняет его с ошибкой:

Частичная специализация шаблона шаблона не специализируется ни на одном аргументе шаблона

даже в последнем clang 7.0 HEAD, см. демо здесь. Однако gcc принимает его.

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

EDIT:

Однако поведение gcc также является ненормальным, рассмотрим следующую программу:

#include <iostream>

template<template<typename ...> class>
struct foo { void show() { std::cout << "Primary.\n"; } };

template<template<typename> class C>
struct foo<C> { void show() { std::cout << "Specialized.\n"; } };

template<class...> struct bar {};

int main() {
    foo<bar> f;
    f.show();
}

Оказывается, что в этом случае gcc использует специализированную версию, см. здесь.

Теперь я хочу спросить:

  • - это частичная специализация, разрешенная стандартным?

  • какой компилятор прав? (один/все/ни один из них?)

Ответ 1

Это окончательно похоже на ошибку GCC в реализации нового шаблона шаблона шаблона шаблона С++ частичная поддержка этой функции.

Чтобы определить, является ли частичная специализация более специализированной, чем шаблон класса, частичное упорядочение применяется к 2 соответствующим синтезированным функциям:

//template class:
template<template<class...>class P> void f_foo0(foo<P>);

//Partial specialization
template<template<class P> class P> void f_foo_partial0(foo<P>);

Затем он пытается выполнить вывод аргумента шаблона, вызывая каждую функцию с аргументом, соответствующим другому параметру функции (см. [temp.func.order ])

template<class P> struct Atype{};
template<class ...P> struct ATypePack{};

//Is f_foo_partial at least as specialized as f_foo?
f_foo(foo<AType>{});

//Is f_foo at least as specialized as f_foo_partial?
f_foo_partial(foo<ATypePack>{});

Если аргумент шаблона преуспеть для (1), то f_foo_partial по меньшей мере такой же специализированный, как f_foo. Если аргумент шаблона преуспевает для (2), то f_foo по меньшей мере такой же специализированный, как f_foo_partial. (См. [temp.deduct.partial]). Тогда, если только один, по крайней мере, такой же специализированный, как другой, то он является более специализированным.

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

Затем для выполнения этого соответствия введенное правило в С++ 17 применяется [temp.arg.template]/3:

Аргумент шаблона соответствует шаблону-шаблону шаблона P, если P по меньшей мере такой же специализированный, как шаблон-аргумент A. [...]

И [temp.arg.template]/4 указывают, что это упорядочение будет выполняться аналогично предыдущему случаю, используя эти изобретенные две функции:

template<class...> struct X{};
//for the argument
template<class...P> void f_targ(X<P...>);

//Partial specialization
template<class P> void f_tparam(X<P>);

struct Atype{};
struct ATypePack{};

//Is template template parameter at least as specialized template template arg?
f_targ(X<AType>{});

//Is template template arg at least as specialized as template template parameter?
f_tparam(X<ATypePack>{});
  • для (1) аргумент шаблона успешно выводит аргумент для ...P`` is AType`.

  • для (2) существует специальное правило, которое применяется только в случае частичного заказа шаблона [temp.deduct.type]/9.2:

[...] При частичном упорядочении, если Ai первоначально было расширением пакета:

  • если P не содержит аргумент шаблона, соответствующий Ai, тогда Ai игнорируется,

  • в противном случае, если Pi не является расширением пакета, вывод аргумента шаблона не выполняется.

Здесь Ai ATypePack, а Pi - P в аргументе функции template<class P> void p_foo_partial(foo<P>).

Поэтому из-за этого правила, выделенного жирным шрифтом, вывод аргумента шаблона не выполняется для (2), поэтому параметр шаблона шаблона "класс P" более специализирован, чем его аргумент. Таким образом, вызов f_foo_partial(foo<ATypePack>{}) хорошо сформирован.

С другой стороны, те же правила, вызов f_foo(AType{}) хорошо сформирован из-за [temp.arg.temp]/3:

[...] Если P содержит пакет параметров, тогда A также соответствует P, если каждый из параметров шаблона A соответствует соответствующему параметру шаблона в списке параметров шаблона P. [...]

поэтому f_foo_partial не является более специализированным, чем f_foo, поэтому template<class<class > class C> struct foo<C>; не является частичной специализацией шаблона foo.