c++ - plantillas - programacion ats funciones
Especificar un concepto para un tipo que tenga una plantilla de función miembro utilizando Concepts Lite (2)
Estoy tratando de especificar un concepto para restringir un tipo de kinded más alto que tiene una plantilla de función miembro utilizando Concepts Lite. Sin embargo, no puedo encontrar dentro de la especificación técnica o la cláusula tutorial una cláusula que trate con declaraciones de plantilla dentro de un concepto.
¿Cómo se hace esto?
Ejemplo: supongamos que tengo el tipo KKD más alto HKT
con una plantilla de función miembro F
:
template<class T>
struct HKT {
template<class U> // this looks like e.g. rebind in std::allocators
auto F(U) -> HKT<U>;
};
y que ahora quiero especificar un concepto para restringir estos tipos de kinded superiores:
template <template <class> class HKT, class T>
concept HKTWithTemplateMemberFunctionF {
return requires(HKT<T> h) { // HKT<T> is a type, h is an object
// HKT<T> needs to have a member function template that
// returns HTK<U> where the type U is to be deduced and
// it can be any type (it is unconstrained)
template<class U> // is there a syntax for this?
h.F(std::declval<U>()) -> HKT<U>;
}
}
Tenga en cuenta que podría hacer algo como:
template <template <class> class HKT, class T, class U>
concept HKTWithTemplateMemberFunctionF {
return requires(HKT<T> h) {
h.F(std::declval<U>()) -> HKT<U>;
}
}
pero esto significa que necesito saber U
en el sitio de restricción.
Realmente no me importa si la sustitución de un U
dado falla o no, aunque puedo ver por qué esto podría ser un problema: por ejemplo, aplicar una restricción para asegurarse de que tu función no falla y luego falla porque se cumplió la restricción, pero en la instanciación la sustitución de tiempo falló en la plantilla de función de miembro (¿ayudaría si la plantilla de función miembro estuviera restringida?).
Para resumir, en este momento usted (¿yo?) Tiene que proporcionar una U
específica:
template <template <class> class HKT, class T, class U = T>
concept HKTWithTemplateMemberFunctionF {
return requires(HKT<T> h) { // HKT<T> is a type, h is an object
h.F(std::declval<U>()) -> HKT<U>;
}
}
dado que el compilador no puede probar para todos los tipos U
que alguna vez exista, la plantilla de función de miembro funcionará, es decir, lo siguiente es inútil:
template <template <class> class HKT, class T>
concept HKTWithTemplateMemberFunctionF {
return requires(HKT<T> h) {
template<class U> // for all those Us that haven''t been written yet...
h.F(std::declval<U>()) -> HKT<U>;
}
}
En una implementación hipotética diseñada en 5 minutos conceptos lite, donde podemos restringir U
solo un poco :
template <template <class> class HKT, class T,
InputIterator U = InputIterator() /* imaginary syntax */ >
concept HKTWithTemplateMemberFunctionF {
return requires(HKT<T> h) {
h.F(std::declval<U>()) -> HKT<U>; // Is InputIterator enough to instantiate F?
}
}
el compilador solo necesitaría verificar si un modelo de InputIterator
es suficiente para crear instancias de hF
, lo que es posible incluso si hF
no está restringido. Además, al proporcionar U
solo se comprueba que modela el InputIterator
, ni siquiera es necesario intentar comprobar hF
para U
dado que InputIterator
es suficiente. Esto podría usarse para optimizar el rendimiento en tiempo de compilación y ...
... probablemente interactuaría de manera sorprendente con SFINAE, ya que AFAIK puede tener una función sobrecargada de conceptos (por ejemplo, para InputIterator
) que acepta todos los iteradores de entrada excepto esa (SFINAE! ¿por qué alguien haría eso ?!), y así podría pasar la verificación de conceptos pero golpear en el momento de la instanciación ... triste.
Pensemos en los requisitos que desea de su comentario:
// HKT<T> needs to have a member function template that // returns HTK<U> where the type U is to be deduced and // it can be any type (it is unconstrained)
Si bien Concepts requiere que basemos nuestras restricciones en torno a los tipos de concreto, podemos ser inteligentes en nuestra selección de los tipos concretos que utilizamos. Lo que quieres decir con U
es cualquier tipo. ¿Realmente algún tipo en absoluto, en absoluto? Piensa en el conjunto de restricciones más pequeño posible que tienes en U
y construyamos un tipo que los satisfaga. Esto se conoce como un arquetipo de U
Mi primer pensamiento para "cualquier tipo" sería en realidad un tipo semirregular. Un tipo que se puede construir, copiar y asignar por defecto. Todas las golosinas normales:
namespace archetypes {
// private, only used for concept definitions, never in real code
struct Semiregular { };
}
archetypes::Semiregular
es un tipo concreto, por lo que podemos usarlo para construir un concepto:
template <template <class> class HKT, class T>
concept bool HKTWithTemplateMemberFunctionF =
requires(HKT<T> h, archetypes::Semiregular r) {
{h.F(r)} -> HKT<archetypes::Semiregular>
};
archetypes::Semiregular
es un tipo privado. No debería ser conocido por HKT
, por lo que si hF(r)
está bien formado y devuelve un tipo convertible a HKT<archetypes::Semiregular>
, es casi seguro que sea una plantilla de función miembro.
La pregunta es, ¿es esto un buen arquetipo? ¿Necesitamos que U
sea semirregular, o los tipos irregulares también funcionarían? Cuantas menos operaciones necesite , menos estarán presentes en su arquetipo. Quizás todo lo que necesitas es que U
sea movible:
namespace archetypes {
// private, only used for concept definitions, never in real code
struct Semiregular { };
struct Moveable {
Moveable() = delete;
Moveable(Moveable&& ) noexcept(false);
Moveable(Moveable const& ) = delete;
~Moveable() = default;
Moveable& operator=(Moveable const& ) = delete;
Moveable& operator=(Moveable&& ) noexcept(false);
};
}
template <template <class> class HKT, class T>
concept bool HKTWithTemplateMemberFunctionF =
requires(HKT<T> h, archetypes::Moveable m) {
{ h.F(m) } -> HKT<archetypes::Moveable>
};
Estamos probando la misma idea: invocar a F()
con un tipo que no es conocido y exceptuando el tipo de devolución para reflejar eso, por lo tanto, es necesario que sea una plantilla de función. Pero ahora le estamos dando menos funcionalidades al tipo. Si F()
funciona en cualquiera , funcionará en archetypes::Moveable
.
Siga iterando sobre esta idea hasta que haya reducido realmente la funcionalidad requerida al mínimo. ¿Tal vez ni siquiera necesitas que el arquetipo sea destructible? Escribir arquetipos es difícil, pero en casos como este, es importante hacerlo bien.