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>> {};
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
Pno de tipo que se declara con un tipo dependiente se deduce de una expresión, los parámetros de plantilla en el tipo dePse 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
Nen el tipoT[N]esstd::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]