Argumento de plantilla de plantilla variable de C++ que coincide con cualquier tipo de parámetro

8 minutos de lectura

avatar de usuario
Janick Bernet

Me preguntaba si es posible escribir una función de plantilla que pueda tomar cualquier otra plantilla arbitraria como parámetro y hacer coincidir correctamente el nombre de la plantilla (es decir, no solo la clase resultante). Lo que sé que funciona es esto:

template<template<typename ...> class TemplateT, typename... TemplateP>
void f(const TemplateT<TemplateP...>& param);

Que coincidirá, por ejemplo, para f(std::vector<int>()) o f(std::list<int>()) pero no funcionará para f(std::array<int, 3>())ya que el segundo parámetro es un size_t y ningún tipo.

Ahora supongo que uno podría hacer algo loco como:

template<template<typename ...> class TemplateT, size... Sizes, typename... TemplateP>
void f(const TemplateT<Sizes..., TemplateP...>& param);

Con la esperanza de que el compilador derivara correctamente el TemplateP puntos suspensivos o el Sizes puntos suspensivos para estar vacío. Pero no solo es feo, también funcionará para plantillas que toman tipos o size_t parámetros Todavía no coincidirá con plantillas arbitrarias, por ejemplo, con bool parámetros

Lo mismo ocurre con un enfoque con sobrecarga:

template<template<typename ...> class TemplateT, typename... TemplateP>
void f(const TemplateT<TemplateP...>& param);

template<template<typename ...> class TemplateT, size... Sizes>
void f(const TemplateT<Sizes...>& param);

Además, tal enfoque no funcionará si quisiéramos mezclar size_t y typenames. Entonces, lo que se requeriría para hacer coincidir cualquier cosa sería algo como esto, donde no hay restricciones en absoluto a lo que se permite en los puntos suspensivos:

template<template<...> class TemplateT, ... Anything>
void f(const TemplateT<Anything...>& param);

Esa sintaxis no funciona, pero tal vez haya otra sintaxis para definir algo como esto.

Principalmente me pregunto qué es posible en el idioma, pensé que en realidad podría tener un uso, si tiene diferentes plantillas donde el primer parámetro siempre está fijo y le gustaría cambiarlo según el tipo de retorno y mantener todo lo demás . Algo como esto:

template<
    template<typename ValueT, ...> class TemplateT,
    ... Anything,
    typename ValueT,
    typename ResultT = decltype(some_operation_on_value_t(std::declval<ValueT>())>
TemplateT<ResultT, Anything...> f(const TemplateT<ValueT, Anything...>& in);

Entonces, ¿alguna forma de hacer que esto funcione de una manera completamente genérica usando la coincidencia de patrones?

Esto no es puramente un experimento mental, ya que el caso de uso para esto en el que estaba atascado era crear primitivos funcionales puros que operan en contenedores y construirán implícitamente contenedores de resultados inmutables. Si el contenedor de resultados tiene un tipo de datos diferente, necesitamos saber el tipo en el que opera el contenedor, por lo que el único requisito en cualquier contenedor sería que el primer parámetro de la plantilla debe ser el tipo de entrada para que pueda ser reemplazado por un diferente tipo de salida en el resultado, pero el código debe ignorar cualquier argumento de plantilla que venga después de eso y no debe importarle si es un tipo o un valor.

  • No hay forma de igualar una parámetro de plantilla sin tipo si no conoce su tipo. Me temo que estás pidiendo demasiadas cosas demasiado buenas.

    – norte 1.8e9-dónde-está-mi-participación m.

    17/09/2014 a las 15:13


  • @nm O dicho de otra manera, los argumentos de valor son argumentos de plantilla de segunda clase. (Utilice un envoltorio (std::integral_constant) para evitar las desventajas. (Deberían haber implementado el ajuste automático allí…)

    – Deduplicador

    17/09/2014 a las 16:13


  • @nm: De acuerdo, ya no es posible para los parámetros ‘normales’. Por otro lado, se agregaron parámetros variados para poder especificar plantillas más genéricas, por lo que existe la esperanza de que se agregue o se agregue algo como esto.

    – Janick Bernet

    17/09/2014 a las 16:18

  • Posiblemente te interesen las respuestas a esta pregunta.

    – Constructor

    17/09/2014 a las 18:37

  • Creo que los problemas que resolverías con esto generalmente se pueden resolver usando la tipificación pato (por ejemplo, “si actúa como un contenedor” + auto/decltype(*begin(param))en lugar de verificar los parámetros del parámetro directamente) o crear subclases (hacer una nueva clase derivada para cambiar los parámetros de la plantilla).

    – leewz

    2 de marzo de 2015 a las 3:23

avatar de usuario
usuario1978011

Su construcción interesante tiene dos niveles con plantillas variadas.

  • Una lista de parámetros de plantilla variádica externa TemplateP & Sizes para plantilla de función
  • Un paquete de parámetros internos como los parámetros de plantilla de su parámetro de plantilla de plantilla TemplateTa plantilla de clase

Primero, veamos el interior TemplateT clase: ¿por qué el operador de puntos suspensivos no puede coincidir con algo como TemplateT< int, 2 >? Bueno, el estándar define plantillas variadas en §14.5.3 como

template<class ... Types> struct Tuple { };
template<T ...Values> struct Tuple2 { };

donde el paquete de argumentos de la plantilla en el primer caso puede solamente tipos de coincidencias y en la segunda versión solamente valores de tipo T. En particular,

Tuple < 0 >    error;  // error, 0 is not a type!
Tuple < T, 0 > error2; // T is a type, but zero is not!
Tuple2< T >    error3; // error, T is not a value
Tuple2< T, 0 > error4; // error, T is not a value

están todos malformados. Además, no es posible recurrir a algo como

template<class ... Types, size_t ...Sizes> struct Tuple { };

porque la norma establece en §14.1.11:

Si un parámetro de plantilla de una plantilla de clase primaria o una plantilla de alias es un paquete de parámetros de plantilla, será el último parámetro de plantilla. Un paquete de parámetros de plantilla de una plantilla de función no debe ir seguido de otro parámetro de plantilla a menos que ese parámetro de plantilla pueda deducirse de la lista de tipos de parámetros de la plantilla de función o tenga un argumento por defecto (14.8.2).

En otras palabras, solo para plantillas de clase una paquete de parámetros variados puede aparecer en la definición. Por lo tanto, la definición de clase (doble) variádica anterior está mal formada. Debido a que la clase interna siempre necesita tal combinación, es imposible escribir algo tan general como lo concibió.


¿Qué se puede rescatar? Para la plantilla de función externa, se pueden juntar algunos fragmentos, pero no le gustará. Siempre que el segundo paquete de parámetros se pueda deducir del primero, pueden aparecer dos paquetes de parámetros (en una plantilla de función). Por lo tanto, una función como

template < typename... Args, size_t... N > void g(const std::array< Args, N > &...arr);
g(std::array< double, 3 >(), std::array< int, 5>());

está permitido, porque los valores enteros se pueden deducir. Por supuesto, esto tendría que ser especializado para cada tipo de contenedor y está lejos de lo que habías imaginado.

  • Mmm. Acabo de leer la sección también y de acuerdo con esa sección int… o algo así ni siquiera está permitido en absoluto, estoy bastante seguro de que clang al menos lo respalda.

    – Janick Bernet

    28 de abril de 2015 a las 15:29

  • Gracias por señalar esto. Investigué más en esto y creo que la respuesta está ahí ahora.

    – usuario1978011

    28/04/2015 a las 20:44

Debe tener una metafunción que vuelva a vincular el tipo de contenedor. Porque no puede simplemente reemplazar el primer parámetro de plantilla:

vector<int, allocator<int> > input;
vector<double, allocator<int> > just_replaced;
vector<double, allocator<double> > properly_rebound;

Entonces, simplemente escriba una metafunción de este tipo para un conjunto conocido de contenedores.

template<class Container, class NewValue> class rebinder;

// example for vectors with standard allocator
template<class V, class T> class rebinder< std::vector<V>, T > {
public:
  typedef std::vector<T> type;
};
// example for lists with arbitrary allocator
template<class V, class A, class T> class rebinder< std::list<V,A>, T > {
  typedef typename A::template rebind<T>::other AT; // rebind the allocator
public:
  typedef std::list<T,AT> type; // rebind the list
};
// example for arrays
template<class V, size_t N> class rebinder< std::array<V,N>, T > {
public:
  typedef std::array<T,N> type;
};

Las reglas de reencuadernación pueden variar para diferentes contenedores.

También es posible que necesite una metafunción que extraiga el tipo de valor de un contenedor arbitrario, no solo conforme a std (typedef *unspecified* value_type)

template<class Container> class get_value_type {
public:
  typedef typename Container::value_type type; // common implementation
};
template<class X> class get_value_type< YourTrickyContainer<X> > {
  ......
public:
  typedef YZ type;
};

  • Sí, eso funcionaría para ese caso específico de contenedores STl. Pero los contenedores Qt, por ejemplo, no tienen asignadores. También estaba buscando algo más genérico de todos modos, los contenedores eran solo un ejemplo.

    – Janick Bernet

    28/04/2015 a las 15:30

avatar de usuario
bit2shift

Sería increíble si tuviéramos tal cosa, ya que nos permitiría escribir un is_same_template rasgo en una brisa.
Hasta entonces, nos especializamos hasta el final.

¿Ha sido útil esta solución?