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
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 deP
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 typeT[N]
eststd::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]
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).
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>> {};