Cómo obtener el nombre completo del tipo en C++
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 p>
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: p > // 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 template plantilla plantilla plantilla plantilla plantilla plantilla plantilla plantilla plantilla plantilla plantilla plantilla plantilla plantilla plantilla plantilla plantilla >template // ...... 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 plantilla plantilla plantilla plantilla plantilla // ...... 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(void) { std::cout << " "#OPT ; } }; CHECK_TYPE__(const) CHECK_TYPE__(volatil) CHECK_TYPE__(const volátil) CHECK_TYPE__(& CHECK_TYPE__(&&) CHECK_TYPE__(*) int main(void) { p> 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_ += " "; usando ss_t = std::ostringstream; sr_ += static_cast 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 { \ 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 return std::move(str); } int main(void) { std:: cout <<.check_type system("pausa"); return 0; }