Restringir el parámetro de plantilla de C ++ a subclase

80

¿Cómo puedo forzar que un parámetro de plantilla Tsea ​​una subclase de una clase específica Baseclass? Algo como esto:

template <class T : Baseclass> void function(){
    T *object = new T();

}
phant0m
fuente
3
¿Qué está tratando de lograr al hacer esto?
sth
2
Solo quiero asegurarme de que T sea en realidad una instancia de una subclase o la clase en sí. El código dentro de la función que he proporcionado es bastante irrelevante.
phant0m
6
al contrario, es muy relevante. Determina si es una buena idea o no poner trabajo en esa prueba. En muchos (¿todos?) Casos, no hay absolutamente ninguna necesidad de imponer tales restricciones usted mismo, sino dejar que el compilador lo haga al crear una instancia. Por ejemplo, para la respuesta aceptada, sería bueno marcar si se Tderiva de Baseclass. A partir de ahora, esa verificación está implícita y no es visible para sobrecargar la resolución. Pero si en ninguna parte se aplica tal restricción implícita, no parece haber razón para una restricción artificial.
Johannes Schaub - litb
1
Sí estoy de acuerdo. Sin embargo, solo quería saber si hay una manera de lograr esto o no :) Pero, por supuesto, tiene un punto muy válido y gracias por la información.
phant0m

Respuestas:

53

En este caso puedes hacer:

template <class T> void function(){
    Baseclass *object = new T();

}

Esto no se compilará si T no es una subclase de Baseclass (o T es Baseclass).

sepp2k
fuente
ah sí, es una buena idea. ¡Gracias! Supongo que no hay forma de definirlo en la definición de la plantilla.
phant0m
2
@ phant0m: Correcto. No puede restringir explícitamente los parámetros de la plantilla (excepto mediante el uso de conceptos, que se consideraron para c ++ 0x pero luego se eliminaron). Todas las restricciones ocurren implícitamente por las operaciones que realiza en él (o en otras palabras, la única restricción es "El tipo debe admitir todas las operaciones que se realizan en él").
sepp2k
1
ah ic. ¡Muchas gracias por la aclaración!
phant0m
8
Eso ejecuta el constructor T () y requiere la existencia del constructor T (). Vea mi respuesta para ver una forma que evita esos requisitos.
Douglas Leeder
3
Agradable y claro, pero esto es un problema si T es una clase "pesada".
3Dave el
84

Con un compilador compatible con C ++ 11, puede hacer algo como esto:

template<class Derived> class MyClass {

    MyClass() {
        // Compile-time sanity check
        static_assert(std::is_base_of<BaseClass, Derived>::value, "Derived not derived from BaseClass");

        // Do other construction related stuff...
        ...
   }
}

Probé esto usando el compilador gcc 4.8.1 dentro de un entorno CYGWIN, por lo que también debería funcionar en entornos * nix.

Vish Desai
fuente
Para mí también funciona así: template<class TEntity> class BaseBiz { static_assert(std::is_base_of<BaseEntity, TEntity>::value, "TEntity not derived from BaseEntity");...
Matthias Dieter Wallnöfer
1
Creo que esta es la respuesta más legible que evita código adicional en tiempo de ejecución.
Kyle
50

Para ejecutar código menos inútil en tiempo de ejecución, puede consultar: http://www.stroustrup.com/bs_faq2.html#constraints que proporciona algunas clases que realizan la prueba de tiempo de compilación de manera eficiente y producen mensajes de error más agradables.

En particular:

template<class T, class B> struct Derived_from {
        static void constraints(T* p) { B* pb = p; }
        Derived_from() { void(*p)(T*) = constraints; }
};

template<class T> void function() {
    Derived_from<T,Baseclass>();
}
Douglas Leeder
fuente
2
Para mí, esta es la mejor y más interesante respuesta. Asegúrese de consultar las preguntas frecuentes de Stroustrup para leer más sobre todo tipo de restricciones que podría aplicar de manera similar.
Jean-Philippe Pellet
1
De hecho, ¡esta es una respuesta increíble! Gracias. El sitio mencionado se mueve aquí: stroustrup.com/bs_faq2.html#constraints
Jan Korous
Esta es una respuesta genial. ¿Existen buenas formas de evitar las advertencias unused variable 'p'y unused variable 'pb'?
Filip S.
@FilipS. añadir (void)pb;después B* pb = p;.
bit2shift
11

No necesita conceptos, pero puede usar SFINAE:

template <typename T>
boost::enable_if< boost::is_base_of<Base,T>::value >::type function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Tenga en cuenta que esto creará una instancia de la función solo cuando se cumpla la condición, pero no proporcionará un error sensible si no se cumple la condición.

David Rodríguez - dribeas
fuente
¿Qué pasa si envuelve todas las funciones de esta manera? por cierto, ¿qué devuelve?
the_drow
El enable_iftoma un segundo parámetro de tipo predeterminado void. La expresión enable_if< true, int >::typerepresenta el tipo int. Realmente no puedo entender cuál es su primera pregunta, puede usar SFINAE para lo que desee, pero no entiendo muy bien qué pretende hacer con esto en todas las funciones.
David Rodríguez - dribeas
7

Desde C ++ 11 no necesita Boost o static_assert. C ++ 11 introduce is_base_of y enable_if. C ++ 14 introduce el tipo de conveniencia enable_if_t, pero si está atascado con C ++ 11, simplemente puede usar enable_if::typeen su lugar.

Alternativa 1

La solución de David Rodríguez se puede reescribir de la siguiente manera:

#include <type_traits>

using namespace std;

template <typename T>
enable_if_t<is_base_of<Base, T>::value, void> function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Alternativa 2

Desde C ++ 17, tenemos is_base_of_v. La solución se puede volver a escribir en:

#include <type_traits>

using namespace std;

template <typename T>
enable_if_t<is_base_of_v<Base, T>, void> function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Alternativa 3

También puede limitar la plantilla completa. Puede utilizar este método para definir clases completas. Observe cómo enable_if_tse ha eliminado el segundo parámetro de (anteriormente se había configurado como nulo). Su valor predeterminado es en realidad void, pero no importa, ya que no lo estamos usando.

#include <type_traits>

using namespace std;

template <typename T,
          typename = enable_if_t<is_base_of_v<Base, T>>>
void function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

De la documentación de los parámetros de la plantilla, vemos que typename = enable_if_t...es un parámetro de la plantilla con un nombre vacío. Simplemente lo usamos para asegurarnos de que existe una definición de tipo. En particular, enable_if_tno se definirá si Baseno es una base de T.

La técnica anterior se da como ejemplo en enable_if.

justinpc
fuente
¿No sería bueno si fuera posible escribir la Alternativa 3 de la siguiente manera? template <class T : Base>
Macsinus
4

Se podría usar Boost Concepto Comprobar 's BOOST_CONCEPT_REQUIRES:

#include <boost/concept_check.hpp>
#include <boost/concept/requires.hpp>

template <class T>
BOOST_CONCEPT_REQUIRES(
    ((boost::Convertible<T, BaseClass>)),
(void)) function()
{
    //...
}
Daniel Trebbien
fuente
0

Llamando a funciones dentro de su plantilla que existen en la clase base.

Si intenta crear una instancia de su plantilla con un tipo que no tiene acceso a esta función, recibirá un error en tiempo de compilación.

DanDan
fuente
3
Esto no asegura que T sea ​​un BaseClass porque los miembros declarados en BaseClasspodrían repetirse en la declaración de T.
Daniel Trebbien