template c++ templates c++14 variadic-templates non-type

c++ - template - Coincidencia de plantillas variables sin tipo



variadic c++ (3)

Pregunta: ¿Qué es una solución para esto en C ++ 14?

Una posible solución en C ++ 14 se basa en rasgos.
Como un ejemplo mínimo y funcional (tal vez incluso estúpido, pero ayuda a entender la idea):

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

Como nota al margen, en C ++ 17 puedes simplificar las cosas como sigue:

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

Sobre los motivos que están detrás del error, la respuesta de @Barry contiene todo lo que necesita para entenderlo (como de costumbre).

Digamos que tengo dos estructuras, Foo y Bar :

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

Quiero crear un rasgo de tipo (llamarlo match_class ) que devuelve verdadero si paso dos tipos Foo<...> o dos tipos Bar<...> , pero falso si intento mezclarlos:

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

Para C ++ 1z (clang 5.0.0 y gcc 8.0.0) es suficiente para hacer esto ( 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{};

Pero en C ++ 14 me sale el siguiente error (los mismos compiladores * 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>

Pregunta: ¿Qué es una solución para esto en C ++ 14?

Lo ideal sería que la sintaxis para probar el rasgo de tipo permanezca igual.

Pregunta secundaria: ¿es correcto el comportamiento de C ++ 14? (¿O alternativamente es el comportamiento que veo para C ++ 17 no especificado?)

* Tenga en cuenta que MSVC 19.00.23506 tiene el mismo tipo de falla Demo


Aquí hay una solución general de C ++ 14 que no se basa en rasgos de tipo especializados manualmente o que extiende Foo y Bar .

Una metafunción de plantilla que obtiene un tipo que representa la plantilla de clase de su tipo de argumento:

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

Una plantilla de alias para facilitar su uso:

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

Luego puede implementar su rasgo match_class siguiente manera:

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

MANIFESTACIÓN


En C ++ 14, no se puede deducir T en:

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

pero en C ++ 17, puedes. El comportamiento que ves es correcto.

En C ++ 14, dado que no puede deducir T , necesita una forma de proporcionarlo explícitamente. Por lo tanto, es posible que requiera que las propias plantillas de clase indiquen su tipo de parámetro de plantilla sin tipo:

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

O tiene un rasgo externo para esto. Y luego explícitamente escriba todo: dos plantillas de clase coinciden si tienen el mismo parámetro de plantilla sin tipo y luego también tienen la misma plantilla de clase, en ese orden:

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

Esto es una consecuencia de agregar soporte para la template auto . En C ++ 14, [temp.deduct.type] contenía:

Un argumento de tipo de plantilla no se puede deducir del tipo de una plantilla-argumento sin tipo. [Ejemplo:

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

-Final ejemplo]

Pero en C ++ 17, ahora dice :

Cuando el valor del argumento correspondiente a un parámetro de plantilla P no de tipo que se declara con un tipo dependiente se deduce de una expresión, los parámetros de plantilla en el tipo de P se deducen del tipo del valor. [Ejemplo:

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>

- end example] El tipo de N en el tipo T[N] es std​::​size_t . [Ejemplo:

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]

- ejemplo final]