Red de conocimiento informático - Material del sitio web - Cómo obtener el nombre completo del tipo en C++

Cómo obtener el nombre completo del tipo en C++

Todo el mundo sabe que existe un operador typeid en C++, que se puede utilizar para obtener el nombre de un tipo/expresión:

std::cout << typeid(int).name() << std::endl;

Pero el valor de retorno de name() depende del compilador

y se imprime de la siguiente manera en vc y gcc:

int // vc

i // gcc

Nombre de tipo un poco más largo, por ejemplo:

class Foo {};

std::cout <<. typeid(Foo*[10]).name()<

El efecto de escribir es así:

clase Foo * [10] // vc

A10_P3Foo // gcc

(En otras palabras, el resultado devuelto por gcc es verdadero).

Por supuesto, hay otra manera de hacerlo en gcc. Para obtener casi el mismo efecto de visualización que Microsoft, use

__cxa_demangle

:

char* name = abi:: __cxa_demangle(typeid(Foo*[10 ]).name( ), nullptr, nullptr, nullptr

std::cout << nombre << std::endl;

free(name);

Mostrar:

Foo* [10]

Ahora, dejemos de lado los diferentes compiladores y miremos la siguiente impresión:

// vc

std::cout << typeid(const int&).name() << std::endl;

// gcc

char* nombre = abi::__cxa_demangle(typeid(const int&).name(), nullptr, nullptr, nullptr);

std::cout << nombre << std::endl ;

free(name);

Efecto de visualización:

int // vc

int // gcc

Lindo cv Los calificadores y las referencias se descartan=.=

Si agrega información descartada directamente al resultado de typeid, no obtendrá el nombre de tipo correcto para algunos tipos (por ejemplo, referencias de puntero de función).

¿Cómo obtengo el nombre completo de un tipo que debe ser el nombre de tipo correcto?

I. Cómo verificar tipos en C++

Necesitamos una clase general que utilice el mecanismo de especialización/desespecialización para verificar estáticamente varios tipos en C++ y no se ignoren los especificadores y declaradores de tipos. .

Primero considere la plantilla de clase más simple:

plantilla

struct check

{

// ...

};

Si quisiéramos especializarnos en ello, ¿cuántas versiones necesitaríamos escribir? Podemos intentar implementarlo:

template struct check;

template struct check

plantilla struct check;

plantilla struct check;

plantilla struct check;

plantilla ;

plantilla struct check;

plantilla struct check;

plantilla struct check<.T * const volatile>;

plantilla struct check;

plantilla struct check;

plantilla struct check<T * const volatile>;

plantilla struct check;

plantilla struct check;

plantilla struct check<T volatile []>;

plantilla struct check;

plantilla struct check ;;;

plantilla ;

plantilla struct comprobar;

>template struct check;

// ......

Esto está lejos del final. Algunas personas pueden decir: "Tenemos macros geniales, todas parecen talladas en el mismo molde, simplemente genere una macro por lotes".

De hecho, cuando realmente comenzamos a escribir estas macros con confianza, descubrimos que las adaptaciones sutiles harían que la escritura de macros fuera muy dolorosa (como la diferencia entre & y *, [] y [N]

, así como tipos de funciones, punteros de función, referencias de punteros de función, matrices de punteros de función, punteros de miembros de clase,...). Mientras exponemos los detalles que requieren especialización, no podemos evitar lamentar la complejidad y la maraña del sistema de tipos C++

.

Pero las razones anteriores no son la debilidad fatal de esta idea.

Aquí es donde no funciona: podemos escribir punteros multidimensionales o matrices multidimensionales, y los tipos pueden anidarse. Es imposible diseñar una plantilla específica para cada dimensión.

Sin embargo, debido a que los tipos en realidad están anidados, podemos resolver este problema mediante la idea básica de la metaprogramación de plantillas:

plantilla struct check : check ;

plantilla struct check : check;

plantilla struct check : check< T>;

plantilla struct check : check;

plantilla : check;

plantilla estructura check : check;

// ......

Herencia simple facilita la especialización. Porque cuando extraemos un tipo (como T *), el T posterior es en realidad un tipo que contiene toda la información de otro tipo excepto *. Luego, esa T se vuelve a poner en el cheque y una y otra vez continúa extrayendo su siguiente tipo de especialización.

Puede comenzar con la extracción de puntero y referencia para ver el efecto:

#include

template

struct check

{

check( void) { std::cout << typeid(T).name() }

~check(void; ) { std::cout << std::endl; }

};

#define CHECK_TYPE__(OPT)\

plantilla \

struct check :check \

{ \

check(void) { std::cout << " "#OPT ; }

};

CHECK_TYPE__(const)

CHECK_TYPE__(volatil)

CHECK_TYPE__(const volátil)

CHECK_TYPE__(&

CHECK_TYPE__(&&)

CHECK_TYPE__(*)

int main(void)

{

check();().

system("pause");

return 0;

}

Salida (vc):

void const volatile * const * &

Hermoso, ¿verdad, por supuesto, en la salida de gcc, void? se convertirá en v, por lo que gcc debe escribir la plantilla de verificación de esta manera:

plantilla

struct check

{

check( void)

{

char* nombre_real = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);

std:: cout < < nombre_real;

gratis(nombre_real);

}

~check(void) { std::cout << std::endl; }

};

Dos: guardar y generar cadenas

Podemos admitir tanto vc como gcc simplemente modificando check:

plantilla

struct check

{

check(void)

{

# si está definido (__GNUC__)

char* nombre_real = abi ::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);

std::cout << nombre_real ;

gratis(r

eal_name);

# else

std::cout << typeid(T).name();

# endif

}

~check(void) { std::cout << std::endl; }

Pero hasta ahora, el resultado de check no se ha guardado. Un mejor enfoque es devolver una cadena como typeid(T).name(). Esto requiere que la verificación pueda guardar el resultado en un objeto std::string.

Por supuesto, también podemos proporcionar un constructor de tipo "std::string&

out" para verificación, pero esto unirá la gestión del estado de salida, la lógica de caracteres de impresión, etc. . Por lo tanto, un mejor enfoque de diseño es implementar una clase de salida que sea responsable de generar y mantener el estado. Veremos los beneficios de hacer esto más adelante.

La implementación de la clase de salida puede ser la siguiente:

salida de clase

{

bool is_compact_ = true;

plantilla

bool check_ vacío(const T&) { return false }

bool check_empty(const char* val)

{

retorno (!val) | (val[0] == 0);

}

plantilla

anular(const T& val)

{

if (check_empty(val)) return;

if (!is_compact_) sr_ += " ";

p>

usando ss_t = std::ostringstream;

sr_ += static_cast(ss_t() << val).str();

is_compact_ = false ;

}

std::string& sr_;

público:

salida(std::string& sr): sr_(sr ) {}

salida& operador()(void) { return (* this }

plantilla

salida& operador() (const T1& val, const T&... args)

{

salida(val);

return operador()(args. ..)

}

salida& compact(void)

{

is_compact_ = true;

retorno (*this) ;

}

}

};

La clase de micro salida es responsable de gestionar automáticamente el estado de la salida ( si se deben agregar espacios) y el tipo de conversión de salida (usando std::ostringstream).

Hay dos cosas interesantes en la implementación anterior.

Uno es el método operator(), que utiliza una plantilla variada. Con este enfoque, podemos usar la salida de esta manera:

salida out(str);

out("Hello", "World", 123, "!") ;

Esta forma de escribir es mucho más cómoda que el operador de flujo de cout.

En segundo lugar, el valor de retorno de operator() es muy compacto. Por supuesto, también puedes usar void directamente aquí, pero esto impondría algunas limitaciones.

Por ejemplo, ¿qué pasa si queremos usar operator() inmediatamente después de compactar? Puedes hacer que la salida sea muy eficiente si haces que la función devuelva una referencia a su propio objeto:

salida out(str);

out.compact()("Hola", " World", 123, "!") .compact()("?") ;

Para utilizar la clase de salida, solo necesita modificar ligeramente la definición de check y la macro CHECK_TYPE__:

plantilla

comprobación de estructura

{

salida out_; ): out_(out)

{

# si está definido(__GNUC__)

char* real_name = abi::__cxa_demangle(typeid(T).name( ), nullptr, nullptr, nullptr);

out_(real_name);

free(real_name);

# else

out_ (typeid (T).name());

# endif

}

};

#define CHECK_TYPE__( OPT)\

plantilla \

estructura check :check \

{ \

usando base_t = verificar; \

usando base_t::out_ \

verificar(salida constante y salida): base_t(salida) { salida_(#OPT) } \

};

Para mantener el uso externo simple, es natural implementar una plantilla de función externa:

plantilla

inline std::string check_type(void)

{

std::string str;

check { str } ;

return std::move(str);

}

int main(void)

{

std:: cout <<.check_type() << std::endl;

system("pausa");

return 0;

}