2012-01-03 22 views
9

Estoy tratando de usar la meta-programación de plantillas para determinar la clase base. ¿Hay alguna manera de obtener la clase base automáticamente sin especializarse explícitamente para cada clase derivada?¿Se puede obtener el tipo de una clase base de un tipo de plantilla automáticamente?

class foo { public: char * Name() { return "foo"; }; }; 
class bar : public foo { public: char * Name() { return "bar"; }; }; 

template< typename T > struct ClassInfo { typedef T Base; }; 
template<> struct ClassInfo<bar> { typedef foo Base; }; 

int main() 
{ 
    ClassInfo<foo>::Base A; 
    ClassInfo<bar>::Base B; 

    std::cout << A.Name(); //foo 
    std::cout << B.Name(); //foo 
} 

por ahora cualquier método automático tendría que seleccionar la primera base declarada y fallaría para las bases privadas.

+1

Utilice ['std :: is_base_of '] (http://stackoverflow.com/questions/2910979/how-is-base-of-works) – iammilind

+3

@iammilind: Eso es solo para probar si una clase es la base de otro, tienes que saber la clase base para probar en contra. – Xeo

+4

¿Para qué lo necesitas? No creo que sea posible, pero tal vez haya un enfoque diferente para resolver el problema real. –

Respuesta

5

Mis soluciones no son realmente automáticas, pero lo mejor que se me ocurre.solución

intrusiva C++ 03:

class B {}; 

class A : public B 
{ 
public: 
    typedef B Base; 
}; 

no intrusiva solución C++ 03:

class B {}; 

class A : public B {}; 

template<class T> 
struct TypeInfo; 

template<> 
struct TypeInfo<A> 
{ 
    typedef B Base; 
}; 
+0

que es más o menos exactamente lo que se me ocurrió. Esperaba que hubiera algún truco de plantilla hardcore que no sabía que podría extraerlo ... Probablemente usaré la forma intrusiva para las clases con control de creación completo y la forma no intrusiva para las clases que son semi-extranjeras - Gracias – VonRiphenburger

5

No conozco ninguna plantilla de selección de clase base, y no estoy seguro de que exista o incluso sea una buena idea. Hay muchas formas en que esto rompe la extensibilidad y va en contra del espíritu de la herencia. Cuando bar hereda públicamente foo, bares unfoo para todos los fines prácticos, y el código del cliente no debería necesitar distinguir la clase base y la clase derivada.

un typedef público en la clase base menudo roza la pica puede que tenga que haber rayado y es más claro:

class foo { public: typedef foo name_making_type; ... }; 

int main() { 
    Foo::name_making_type a; 
    Bar::name_making_type b; 
} 
+0

Bueno. Estaba a punto de escribir esto. – iammilind

+0

Es para un sistema de reflexión C++ muy ligero. El código "cliente" hace una serialización general de clase y mensajes con la mínima deformación posible y/o adiciones a la clase original. – VonRiphenburger

15

Es posible con C++ 11 y decltype. Para eso, explotaremos que un puntero a miembro no es un puntero en la clase derivada cuando el miembro se hereda de una clase base.

Por ejemplo:

struct base{ 
    void f(){} 
}; 
struct derived : base{}; 

El tipo de &derived::f será void (base::*)(), no void (derived::*)(). Esto ya era cierto en C++ 03, pero era imposible obtener el tipo de clase base sin especificarlo realmente. Con decltype, es fácil y sólo necesita esta pequeña función:

// unimplemented to make sure it's only used 
// in unevaluated contexts (sizeof, decltype, alignof) 
template<class T, class U> 
T base_of(U T::*); 

Uso:

#include <iostream> 

// unimplemented to make sure it's only used 
// in unevaluated contexts (sizeof, decltype, alignof) 
template<class T, class R> 
T base_of(R T::*); 

struct base{ 
    void f(){} 
    void name(){ std::cout << "base::name()\n"; } 
}; 
struct derived : base{ 
    void name(){ std::cout << "derived::name()\n"; } 
}; 

struct not_deducible : base{ 
    void f(){} 
    void name(){ std::cout << "not_deducible::name()\n"; } 
}; 

int main(){ 
    decltype(base_of(&derived::f)) a; 
    decltype(base_of(&base::f)) b; 
    decltype(base_of(&not_deducible::f)) c; 
    a.name(); 
    b.name(); 
    c.name(); 
} 

Salida:

base::name() 
base::name() 
not_deducible::name() 

Como último ejemplo muestra, es necesario utilizar un elemento que se es en realidad un miembro heredado de la clase base en la que está interesado.

The re son más defectos, sin embargo: El miembro también debe identificar de forma inequívoca un miembro de la clase base:

struct base2{ void f(){} }; 

struct not_deducible2 : base, base2{}; 

int main(){ 
    decltype(base_of(&not_deducible2::f)) x; // error: 'f' is ambiguous 
} 

Eso es lo mejor que se puede conseguir, sin embargo, sin el apoyo del compilador.

+0

Gracias Xeo, es una solución parcial muy interesante y muy similar a lo que estoy buscando. Lo investigaré más a fondo – VonRiphenburger

2

¿Qué pasa con la clase base? ¿Eres un programador de .NET o Java?

C++ admite herencia múltiple, y tampoco tiene una clase base común global. Entonces, un tipo de C++ puede tener cero, una o muchas clases base. El uso del artículo definido está por lo tanto contraindicado.

Dado que la clase base no tiene sentido, no hay forma de encontrarla.

+0

Existen fuertes razones de eficiencia Java solo permite una clase base y tiendo a reestructurar mi código C++ por las mismas razones, por lo que tiene sentido como un subconjunto útil – VonRiphenburger

+0

@VonRiphenburger: Entonces la pregunta necesita indicar : "Para una clase con exactamente una clase base inmediata, heredada públicamente y no virtualmente, ¿es posible descubrir el tipo de clase base?" Y todavía no está claro si estás buscando la clase base inmediata u otro ancestro. Y Java, que permite solo una clase base, no tiene nada que ver con la eficiencia, y tiene mucho que ver con querer el diseño del lenguaje para forzar a los programadores a bajar "el único camino verdadero hacia OOP". Lo mismo por falta de funciones gratuitas. –

+0

Hice DID para la PRIMERA base declarada. Parecía obvio que se buscaba una base inmediata, no un ancestro aleatorio, pero ese punto podría haberse expresado más claramente.Um, gracias por tu aporte, Ben. – VonRiphenburger

0

Busco a una solución portátil para problemas similares durante meses. Pero aún no lo encuentro.

G ++ tiene __bases y __direct_bases. Puede envolverlos en una lista de tipos y luego acceder a cualquiera de sus elementos, p. a std::tuple con std::tuple_element. Ver libstdc++'s <tr2/type_traits> para el uso.

Sin embargo, esto no es portátil. Clang++ currently has no such intrinsics.

1

con C++ 11, puede crear un método intrusivo para tener siempre un miembro base_t, cuando su clase sólo se hereda de uno de los padres:

template<class base_type> 
struct labeled_base : public base_type 
{ 
    using base_t = base_type; // The original parent type 
    using base::base; // Inherit constructors 

protected: 
    using base = labeled_base; // The real parent type 
}; 

struct A { virtual void f() {} }; 

struct my_class : labeled_base<A> 
{ 
    my_class() : parent_t(required_params) {} 

    void f() override 
    { 
     // do_something_prefix(); 
     base_t::f(); 
     // do_something_postfix(); 
    } 
}; 

Con esa clase, que siempre tendrá un parent_t alias, para llamar a los constructores padre como si fueran los constructores base con un nombre (probablemente) más corto y un alias base_t, para hacer que su clase no sea consciente del nombre de tipo de clase base si tiene una plantilla larga o pesada.

El alias parent_t está protegido para no exponerlo al público. Si no desea que el alias base_t sea público, siempre puede heredar labeled_base como protected o private, sin necesidad de cambiar la definición de clase labeled_base.

Esa base debe tener 0 en tiempo de ejecución o sobrecarga de espacio ya que sus métodos están en línea, no hacen nada y no tienen atributos propios.

Cuestiones relacionadas