Modèles de non-type variadiques correspondants

Imaginons que j'ai deux struct Foo et Bar:

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

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

Je veux créer un type de trait (appelons - match_class) renvoie true si je passe deux Foo<...> types ou deux Bar<...> types, mais fausse, si j'essaie de les mélanger:

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");
}

Pour C++1z (clang 5.0.0 et gcc 8.0.0) il suffit de le faire (Démo):

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{};

, Mais en C++14 j'obtiens l'erreur suivante (même les compilateurs*Démo):

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>

Question: Qu'est-ce qu'une solution de contournement pour cela dans C++14?

Idéalement, la syntaxe pour tester le trait de type devrait rester la même.

Question secondaire: le comportement de C++14 est-il correct? (ou alternativement le comportement que je vois pour C++17 est-il non spécifié?)

*Notez que MSVC 19.00.23506 a le même type de panne Démo

21
demandé sur AndyG 2017-05-30 16:21:52

3 réponses

En C++14, vous ne pouvez pas déduire T dans:

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

Mais en C++17, Vous pouvez. Le comportement que vous voyez est correct.

En C++14, puisque vous ne pouvez pas déduire T, Vous avez besoin d'un moyen de le fournir explicitement. Vous pouvez donc exiger que les modèles de classe eux-mêmes indiquent leur type de paramètre de modèle non-type:

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

Ou avoir un trait externe pour cela. Et puis écrivez explicitement tout-deux modèles de classe correspondent s'ils ont le même modèle non-type les paramètres et ont alors également le même modèle de classe, dans cet ordre:

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>
{ };

Ceci est une conséquence de l'ajout du support pour template auto. En C++14, [temp.déduire.type] contenu:

Un argument de type de modèle ne peut pas être déduit du type d'un argument de modèle non de type. [Exemple:

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

-exemple de fin]

, Mais en C++17, il lit maintenant:

Lorsque la valeur du argument correspondant à un paramètre de modèle non-type P qui est déclaré avec un type dépendant est déduit d'une expression, les paramètres de modèle dans le type de P sont déduits du type de la valeur. [ Exemple:

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>

- fin de l'exemple ] Le type de N de type T[N] est std​::​size_­t. [ Exemple:

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]

- exemple de fin]

17
répondu Barry 2017-05-30 15:18:08

Question: Qu'est-ce qu'une solution de contournement pour cela en C++14?

Une solution de contournement possible en C++14 est basée sur des traits.
Comme un exemple de travail minimal (peut-être même stupide, mais cela aide à avoir l'idée):

#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");
}

Comme note de côté, en C++17, Vous pouvez simplifier les choses comme suit:

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

Sur les raisons qui sont derrière l'erreur, la réponse de @ Barry contient tout ce dont vous avez besoin pour le comprendre (comme d'habitude).

6
répondu skypjack 2017-05-30 14:43:08

Voici une solution générale C++14 qui ne repose pas sur des traits de type spécialisés manuellement ou s'étendant Foo et Bar.

Une métafonction de modèle qui obtient un type représentant le modèle de classe de son type d'argument:

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>; };

Un modèle d'alias pour faciliter l'utilisation:

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

Ensuite, vous pouvez implémenter votre trait match_class comme ceci:

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

DÉMO

1
répondu Oktalist 2017-05-30 17:46:01