Подтвердить что ты не робот

Соответствие нестандартных шаблонов

Скажем, у меня две структуры: Foo и Bar:

template<int...>
struct Foo{};

template<unsigned long...>
struct Bar{};

Я хочу создать признак типа (назовите его match_class), который возвращает true, если я передаю два типа Foo<...> или два типа Bar<...>, но false, если я попытаюсь их смешать:

int main()
{
    using f1 = Foo<1, 2, 3>;
    using f2 = Foo<1>;
    using b1 = Bar<1, 2, 3>;
    using b2 = Bar<1>;
    static_assert(match_class<f1, f2>::value, "Fail");
    static_assert(match_class<b1, b2>::value, "Fail");
    static_assert(!match_class<f1, b1>::value, "Fail");
}

Для С++ 1z (clang 5.0.0 и gcc 8.0.0) достаточно сделать это (Demo):

template<class A, class B>
struct match_class : std::false_type{};

template<class T, template<T...> class S, T... U, T... V>
struct match_class<S<U...>, S<V...>> : std::true_type{};

Но в С++ 14 я получаю следующую ошибку (такие же компиляторы *Demo):

error: class template partial specialization contains a template parameter that cannot be deduced; this partial specialization will never be used [-Wunusable-partial-specialization]
struct match_class<S<U...>, S<V...>> : std::true_type{};
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: non-deducible template parameter 'T'
template<class T, template<T...> class S, T... U, T... V>

Вопрос: что такое обходной путь для этого в С++ 14?

В идеале синтаксис для проверки черты типа должен оставаться тем же.

Вторичный вопрос: Правильно ли поведение для С++ 14? (или, альтернативно, поведение, которое я вижу для С++ 17, не указано?)

* Обратите внимание, что MSVC 19.00.23506 имеет такой же отказ Демо

4b9b3361

Ответ 1

В С++ 14 вы не можете вывести T в:

template<class T, template<T...> class S, T... U, T... V>
struct match_class<S<U...>, S<V...>> : std::true_type{};

но в С++ 17 вы можете. Поведение, которое вы видите, верное.

В С++ 14, так как вы не можете вывести T, вам нужен способ явно предоставить его. Таким образом, вы можете потребовать, чтобы сами шаблоны классов указывали свой тип параметра шаблона непигового типа:

template <int...> struct Foo { using type = int; };
template <unsigned long...> struct Bar { using type = unsigned long; };

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

template <class... Ts> struct make_void { using type = void; };
template <class... Ts> using void_t = typename make_void<Ts...>::type;

template <class T1, class T2, class A, class B>
struct match_class_impl : std::false_type { };

template <class T, template <T...> class S, T... U, T... V>
struct match_class_impl<T, T, S<U...>, S<V...>> : std::true_type{};

template <class A, class B, class=void>
struct match_class : std::false_type { };

template <class A, class B>
struct match_class<A, B, void_t<typename A::type, typename B::type>>
    : match_class_impl<typename A::type, typename B::type, A, B>
{ };

Это является следствием добавления поддержки template auto. В С++ 14 параметр [temp.deduct.type] содержит:

Аргумент типа шаблона не может быть выведен из типа аргумента шаблона, отличного от типа. [Пример:

template<class T, T i> void f(double a[10][i]);
int v[10][20];
f(v); // error: argument for template-parameter T cannot be deduced

-end пример]

Но в С++ 17 он теперь читает:

Когда значение аргумента, соответствующего шаблону шаблона P, объявленному с зависимым типом, выводится из выражения, параметры шаблона в типе P выводятся из типа стоимость. [Пример:

template<long n> struct A { };

template<typename T> struct C;
template<typename T, T n> struct C<A<n>> {
  using Q = T;
};

using R = long;
using R = C<A<2>>::Q;           // OK; T was deduced to long from the
                                // template argument value in the type A<2>

- конец примера] Тип N в типе T[N] равен std​::​size_­t. [Пример:

template<typename T> struct S;
template<typename T, T n> struct S<int[n]> {
  using Q = T;
};

using V = decltype(sizeof 0);
using V = S<int[42]>::Q;        // OK; T was deduced to std​::​size_­t from the type int[42]

- конец примера]

Ответ 2

Вопрос: что такое обходной путь для этого в С++ 14?

Возможное обходное решение в С++ 14 основано на чертах.
Как минимальный рабочий пример (может быть, даже глупый, но он помогает получить идею):

#include <type_traits>
#include <utility>

template<int...>
struct Foo{};

template<unsigned long...>
struct Bar{};

template<typename>
struct traits;

template<int... V>
struct traits<Foo<V...>> { using type = Foo<0>; };

template<unsigned long... V>
struct traits<Bar<V...>> { using type = Bar<0>; };

template<typename T, typename U>
constexpr bool match = std::is_same<typename traits<T>::type, typename traits<U>::type>::value;

int main() {
    using f1 = Foo<1, 2, 3>;
    using f2 = Foo<1>;
    using b1 = Bar<1, 2, 3>;
    using b2 = Bar<1>;

    static_assert(match<f1, f2>, "Fail");
    static_assert(match<b1, b2>, "Fail");
    static_assert(!match<f1, b1>, "Fail");
}

В качестве дополнительной заметки в С++ 17 вы можете упростить все, как следует:

template<template<auto ...> class S, auto... U, auto... V>
struct match_class<S<U...>, S<V...>> : std::true_type{};

О причинах, лежащих за ошибкой, @Barry answer содержит все, что вам нужно, чтобы понять это (как обычно).

Ответ 3

Вот общее решение С++ 14, которое не полагается на специализированные черты вручную или расширения Foo и Bar.

Метафолма шаблона, которая получает тип, представляющий шаблон класса его типа аргумента:

namespace detail
{
    // Type representing a class template taking any number of non-type template arguments.
    template <typename T, template <T...> class U>
    struct nontype_template {};
}

// If T is an instantiation of a class template U taking non-type template arguments,
// this has a nested typedef "type" that is a detail::nontype_template representing U.
template <typename T>
struct nontype_template_of {};

// Partial specializations for all of the builtin integral types.
template <template <bool...> class T, bool... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<bool, T>; };
template <template <char...> class T, char... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<char, T>; };
template <template <signed char...> class T, signed char... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<signed char, T>; };
template <template <unsigned char...> class T, unsigned char... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<unsigned char, T>; };
template <template <short...> class T, short... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<short, T>; };
template <template <unsigned short...> class T, unsigned short... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<unsigned short, T>; };
template <template <int...> class T, int... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<int, T>; };
template <template <unsigned int...> class T, unsigned int... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<unsigned int, T>; };
template <template <long...> class T, long... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<long, T>; };
template <template <unsigned long...> class T, unsigned long... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<unsigned long, T>; };
template <template <long long...> class T, long long... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<long long, T>; };
template <template <unsigned long long...> class T, unsigned long long... Vs>
struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<unsigned long long, T>; };

Шаблон псевдонимов для удобства использования:

// Alias template for nontype_template_of.
template <typename T>
using nontype_template_of_t = typename nontype_template_of<T>::type;

Затем вы можете реализовать свою черту match_class так:

template <class A, class B>
struct match_class : std::is_same<nontype_template_of_t<A>, nontype_template_of_t<B>> {};

DEMO