c++ templates c-preprocessor metadata stringification

c++ - Stringifying argumentos de la plantilla



templates c-preprocessor (8)

¿Es posible en C ++ para stringificar argumentos de plantilla? Intenté esto:

#define STRINGIFY(x) #x template <typename T> struct Stringify { Stringify() { cout<<STRINGIFY(T)<<endl; } }; int main() { Stringify<int> s; }

Pero lo que obtengo es una ''T'', y no una ''int''. Parece que los preprocesadores entran en acción antes de la resolución de la plantilla.

Hay alguna otra manera de hacer esto?

¿Hay alguna forma de que el preprocesamiento tenga lugar después de la resolución de la plantilla? (El compilador es VC ++).


Esto es lo que hago: tengo una función demangle() (implementada en la parte superior de abi::__cxa_demangle() que llamo con un par de sobrecargas de función de plantilla de conveniencia, nameof() , con el tipo que quiero stringified o una instancia de mismo.

Es bastante compacto, así que lo reproduciré aquí en todo su esplendor. En demangle.hh tenemos:

#pragma once #include <typeinfo> namespace terminator { /// actual function to demangle an allegedly mangled thing char const* demangle(char const* const symbol) noexcept; /// convenience function template to stringify a name of a type, /// either per an explicit specialization: /// char const* mytypename = terminator::nameof<SomeType>(); template <typename NameType> char const* nameof() { try { return demangle(typeid(NameType).name()); } catch (std::bad_typeid const&) { return "<unknown>"; } } /// … or as implied by an instance argument: /// char const* myinstancetypename = terminator::nameof(someinstance); template <typename ArgType> char const* nameof(ArgType argument) { try { return demangle(typeid(argument).name()); } catch (std::bad_typeid const&) { return "<unknown>"; } } } /* namespace terminator */

... Y luego en demangle.cpp :

#include "demangle.hh" #include <cstdlib> #include <cxxabi.h> #include <mutex> #include <memory> namespace terminator { namespace { /// define one singular, private, static std::mutex, /// to keep the demangler from reentering itself static std::mutex mangle_barrier; /// define a corresponding private and static std::unique_ptr, /// using a delete-expression to reclaim the memory malloc()''ed by /// abi::__cxa_demangle() upon its return. /// … we use clang pragmas to add flags locally for this to work: #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wglobal-constructors" #pragma clang diagnostic ignored "-Wexit-time-destructors" std::unique_ptr<char, decltype(std::free)&> demangled_name{ nullptr, std::free }; #pragma clang diagnostic pop } char const* demangle(char const* const symbol) noexcept { if (!symbol) { return "<null>"; } std::lock_guard<std::mutex> lock(mangle_barrier); int status = -4; demangled_name.reset( abi::__cxa_demangle(symbol, demangled_name.get(), nullptr, &status)); return ((status == 0) ? demangled_name.release() : symbol); } } /* namespace terminator */

Para usar esto, creo que tendrás que vincular a libc++ (o cualquiera que sea tu equivalente local) para usar abi::__cxa_demangle() . Lo que puede ser subóptimo para el OP es el hecho de que esto hace que la demanda y la stringificación en el tiempo de ejecución. Personalmente, me encantaría algo constexpr amistoso a este respecto, pero dado que sufro de una grave alergia al macroagresión, considero que esta es la solución menos razonable para este problema.

(el espacio de nombres del terminator es inconsecuente - Utilizo este código en un stacktracer basado en libunwind llamado desde el manejador de terminación - siéntase libre de s///g ese token)


Esto rompe uno de mis principios principales de escritura de código C ++: Evite usar trucos tanto en las características de la plantilla como en el preprocesador al mismo tiempo.

Parte de la razón de las plantillas y la maldad que introducen en el lenguaje fue un intento de alejar a los desarrolladores del uso del preprocesador. Si usa ambos, entonces los terroristas ganan.


No, no puedes trabajar en tipos como si fueran variables. Podría escribir el código que extrajo el typeid () de un elemento e imprimir el nombre, pero el valor resultante probablemente no sea el esperado (los nombres de los tipos no están estandarizados).

También puede trabajar con especializaciones de plantilla (y algo de macro magia) para lograr una versión más interesante si la cantidad de tipos con los que desea trabajar es limitada:

template <typename T> const char* printtype(); // not implemented // implement specializations for given types #define DEFINE_PRINT_TYPE( type ) / template<>/ const char* printtype<type>() {/ return #type;/ } DEFINE_PRINT_TYPE( int ); DEFINE_PRINT_TYPE( double ); // ... and so on #undef DEFINE_PRINT_TYPE template <typename T> void test() { std::cout << printtype<T>() << std::endl; } int main() { test<int>(); test<double>(); test<float>(); // compilation error, printtype undefined for float }

O incluso podría combinar ambas versiones: implementar la plantilla genérica printtype utilizando typeinfo y luego proporcionar especializaciones para los tipos que desee tener nombres más elegantes.

template <typename T> const char* printtype() { return typeid(T).name(); }


Puedes usar magia de plantilla.

#include <iostream> template <typename T> struct TypeName { static const char *name; }; template <typename T> const char *TypeName<T>::name = "unknown"; template <> const char *TypeName<int>::name = "int"; template <typename T> struct Stringify { Stringify() { std::cout << TypeName<T>::name << std::endl; } }; int main() { Stringify<int> s; }

Esto tiene una ventaja sobre RTTI (es decir, typeinfo ): se resuelve durante la compilación; y desventaja: debe proporcionar usted mismo la información del tipo (a menos que haya alguna biblioteca que haga eso que yo no sepa, tal vez haya algo en Boost).

O, como sugirió Matrin York en los comentarios, use plantillas de funciones en línea en su lugar:

template <typename T> inline const char* typeName(void) { return "unknown"; } template <> inline const char* typeName<int>(void) { return "int"; } // ... std::cout << typeName<T>() << std::endl;

Pero, si alguna vez necesita almacenar más información sobre ese tipo en particular, entonces las plantillas de clase probablemente serán mejores.


Si usa boost / core / demangle.hpp, puede obtener una cadena fiable legible para el ser humano.

char const * name = typeid(T).name(); boost::core::scoped_demangled_name demangled( name ); std::cout << (demangled.get() ? demangled.get() : "Failed to demangle") << std::endl;


Su código no funciona porque el preprocesador, responsable de buscar y expandir las macros que usa en su código, no conoce el lenguaje en sí. Es solo un analizador de texto. Encuentra STRINGIFY (T) en la misma plantilla de función y la expande, mucho antes de darle un tipo a esa plantilla. Como resultado, siempre obtendrás "T" en lugar del nombre tipo que esperabas, desafortunadamente.

Como litb sugirió litb , implementé (mal) esta plantilla de función `getTypeName ''que devuelve el nombre de tipo por el que la pasa:

#include <iostream> template <typename _Get_TypeName> const std::string &getTypeName() { static std::string name; if (name.empty()) { const char *beginStr = "_Get_TypeName ="; const size_t beginStrLen = 15; // Yes, I know... // But isn''t it better than strlen()? size_t begin,length; name = __PRETTY_FUNCTION__; begin = name.find(beginStr) + beginStrLen + 1; length = name.find("]",begin) - begin; name = name.substr(begin,length); } return name; } int main() { typedef void (*T)(int,int); // Using getTypeName() std::cout << getTypeName<float>() << ''/n''; std::cout << getTypeName<T>() << ''/n''; // You don''t actually need the // typedef in this case, but // for it to work with the // typeid below, you''ll need it // Using typeid().name() std::cout << typeid(float).name() << ''/n''; std::cout << typeid(T).name() << ''/n''; return 0; }

El código anterior da como resultado la siguiente salida con los distintivos GCC -s ("quitar todos los símbolos del binario") habilitados:

float void (*)(int, int) f PFviiE

Entonces, ves, getTypename () hace un trabajo bastante mejor, a costa de ese truco de análisis de cadenas fugly (LO SÉ, es muy feo).

Algunos puntos a tener en cuenta:

  • El código es solo GCC. No sé cómo portarlo a otro compilador. Probablemente solo unos pocos tengan la facilidad para producir nombres de funciones tan bonitos, y por lo que busqué, MSVC ++ no tiene uno, si se lo está preguntando.
  • Si, en una nueva versión, GCC forma __PRETTY_FUNCTION__ de manera diferente, la coincidencia de cadena puede romperse y tendrá que arreglarla. Por esta misma razón, también advierto que getTypeName () podría ser bueno para la depuración (y, aún así, tal vez ni siquiera es bueno para eso), pero seguramente es malo, malo y malo para otros fines, como comparar dos tipos en una plantilla o algo así (no lo sé, solo adivinando lo que alguien podría pensar ...). Úselo únicamente para la depuración, y preferentemente no lo llame en compilaciones de lanzamiento (use macros para deshabilitar), para que no use __PRETTY_FUNCTION__ y por lo tanto el compilador no produzca la cadena para ello.
  • Definitivamente no soy un experto, y no estoy seguro de si algún tipo extraño podría causar la falla de la coincidencia de cadenas. Me gustaría pedirle a las personas que lean esta publicación que comenten si saben de un caso así.
  • El código usa una std :: string estática. Significa que, si se lanza alguna excepción desde su constructor o destructor, no hay forma de que llegue a un bloque catch y obtendrá una excepción no controlada. No sé si std :: strings puede hacer eso, pero ten cuidado, si lo hacen, estás potencialmente en problemas. Lo usé porque necesita un destructor para liberar la memoria. Sin embargo, podría implementar su propia clase para eso, asegurándose de que no se produzca ninguna excepción además de la falla de asignación (eso es bastante fatal, ¿no? Entonces ...), y devuelva una cadena C simple.
  • Con typedefs puedes obtener resultados raros, como este (por alguna razón, el sitio rompe el formato de este fragmento, así que estoy usando este enlace de pegado): http://pastebin.com/f51b888ad

A pesar de esas desventajas, me gustaría decir que seguro es rápido. Por segunda vez que busque un mismo nombre de tipo, le costará elegir una referencia a una cadena std :: global que contenga el nombre. Y, comparativamente con los métodos de especialización de plantillas sugeridos anteriormente, no hay nada más que declarar además de la propia plantilla, por lo que es mucho más fácil de usar.


Tu podrías intentar

typeid(T).name()

Editar : corregido en función de los comentarios.


en mi código utilizo la declaración doble "terrible" del "Nombre de clase"

MqFactoryC<MyServer>::Add("MyServer").Default();

porque c ++ NO puede extraer la cadena "MyServer" de la plantilla ... la única "manera" de "deshacerse" de esto ... usando un contenedor "cpp"

#define MQ_CPPSTR(s) #s #define MqFactoryCAdd(T) MqFactoryC<T>::Add(MQ_CPPSTR(T)).Default()