2009-09-28 15 views
32

¿Es posible en C++ codificar los argumentos de la plantilla? yo probamos este:Argumentos de la plantilla Stringifying

#define STRINGIFY(x) #x 

template <typename T> 
struct Stringify 
{ 
    Stringify() 
    { 
      cout<<STRINGIFY(T)<<endl; 
    } 
}; 

int main() 
{ 
    Stringify<int> s; 
} 

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

¿Hay alguna otra manera de hacerlo?

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

+3

La resolución de la plantilla se activa * long * después de que el preprocesador hace su trabajo. De todos modos, las plantillas son mucho más que la sustitución de texto (bueno, ni siquiera es una sustitución de texto), por lo que cambiar el orden de las operaciones no resolvería su problema. –

+5

El preprocesador se activa antes de casi * todo *. De ahí el nombre ** pre ** - procesador. –

+2

He visto personas hacer 'template char const * get_type_name() {return __PRETTY_FUNCTION__; } 'y luego extrae' T = ... 'de la cadena. –

Respuesta

30

Usted podría intentar

typeid(T).name() 

Editar: fijo basado en los comentarios.

+6

Solo tenga en cuenta que los compiladores no necesariamente tienen que dar un valor de retorno de significado para 'name()', pero la mayoría lo hace. – GManNickG

+5

Esto debería ser 'typeid()', no 'typeinfo()' - este último es el nombre del encabezado '', y también 'std :: type_info' es el tipo de clase de objeto devuelto por' typeid() ' . –

11

No, no puede trabajar en los 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 un poco de magia macro) para lograr una versión más interesante si el número de tipos 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 se podría combinar las dos versiones : implemente la plantilla genérica printtype utilizando typeinfo y luego proporcione especializaciones para los tipos que desee que tengan nombres más elegantes.

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

Desea devolver "T" de type_constructor, o escriba "typeid (T) .name()". Si devuelve una referencia, typeid evaluará la llamada a la función y buscará en el vtable/etc del "objeto". –

+0

Pensé en eso, pero funcionó con las pruebas ingenuas que inventé (ninguna de ellas era referencias en una base de jerarquía con rtti), gracias. –

+0

Buen punto sobre el polimorfismo. Después de mirar hacia arriba, encontré que el estándar dice que para los valores l con tipos no polimórficos, el operando tampoco está evaluado. –

23

Puede 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 Matrin York sugerido en los comentarios, utilice función en línea Plantillas 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 tendrá que almacenar más información acerca de ese tipo determinado, plantillas de clase probablemente serán mejores.

+2

En lugar de generar variables. Construya funciones en línea que devuelvan la cadena apropiada. Entonces no tendrás el problema de las múltiples definiciones que pueden surgir con este método. –

+0

Buena idea, he editado mi respuesta. –

+0

Curiosamente, las especializaciones de plantillas para cada tipo de interés se pueden implementar con mayor facilidad y sequedad con una macro: '#define TYPE_STRING (T) template <> const char * TypeName :: name = STRINGIFY (T)' – Novelocrat

15

Su código no funciona porque el preprocesador, responsable de buscar y expandir las macros que utiliza en su código, no tiene conocimiento del idioma 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ó, (mal) He aplicado esta plantilla función `getTypeName' que devuelve el nombre de tipo se le 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 produce la siguiente salida con -s bandera GCC ("tira de todos los símbolos de binario") activado:

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

Así que, como ves, getTypename() hace bastante mejor trabajo, a costa de esa cadena analizar fugly truco (lo sé, es condenadamente feo).

Algunos puntos a tener en cuenta:

  • El código es GCC solamente. 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, los formatos de GCC __PRETTY_FUNCTION__ son diferentes, la coincidencia de cadenas se puede romper y tendrá que arreglarlo. 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 es seguramente malo, malo y malo para otros fines como comparar dos tipos en una plantilla o algo así (no sé, solo adivinando lo que alguien podría pensar ...). Úselo únicamente para la depuración, y preferentemente no lo llame en versiones de lanzamiento (use macros para deshabilitar), para que no use __PRETTY_FUNCTION__ y, por lo tanto, el compilador no produzca la cadena para él.
  • Definitivamente no soy un experto, y no estoy seguro de si algún tipo impar podría causar que la coincidencia de cadena falle. 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, usted puede obtener algunos resultados extraños, como éste (por alguna razón, el sitio rompe el formato de este fragmento, así que estoy usando este enlace pegar): http://pastebin.com/f51b888ad

A pesar de estas desventajas, yo 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.

+1

w.r.t. su comentario sobre 'strlen', ¿por qué no usar' const char beginStr [] = "_Get_TypeName ="; 'que le permitiría usar' sizeof' a menos que decaiga a un puntero. –

+0

Creo que tienes razón, podría haberlo hecho. –

+1

Esta es la mejor solución hasta el momento, pero no obtiene ecos de ida y vuelta limpios del símbolo del código fuente cuando utiliza clases con plantilla estándar, como una cadena. 'getTypeName ()' imprime 'std :: basic_string , std :: allocator >'. –

4

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 del motivo de las plantillas y la maldad que introducen en el lenguaje fue un intento de desanimar a los desarrolladores para que no usen el preprocesador. Si usa ambos, entonces los terroristas ganan.

+2

No estoy de acuerdo. Las macros pueden ser muy malas, pero también pueden ser muy poderosas. dribeas muestra esto muy bien (http://stackoverflow.com/questions/1488186/1488216#1488216), combinando las plantillas de ingenio previo al procesador. Compare eso con la idea de PiotrLegnica (http://stackoverflow.com/questions/1488186/1488250#1488250), que es, básicamente, lo mismo, pero sin las macros. Tomaría la solución de macro sobre escribir cualquier día. – sbi

+0

No es necesario tenerle miedo solo por esta razón. Las macros y las plantillas pueden crear construcciones poderosas. –

+0

"Si usas ambos, entonces los terroristas ganan". -> Estás jugando demasiado Counter-Strike. Las macros y las plantillas combinadas y utilizadas correctamente pueden ayudarlo a superar lo que hacen los tipos. No olvide que las funciones falsas que aceptan tipos como argumentos solo pueden lograrse de esta manera. :) –

0

Esto es lo que hago: Tengo una función demangle() (implementado en la parte superior de abi::__cxa_demangle() que llamo con un par de sobrecargas de función plantilla conveniencia, nameof(), ya sea con el tipo que quiero stringified o una instancia de misma

.

es bastante compacta, así que voy a 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á que enlazar a libc++ (o cualquiera que sea su 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 -friendly in leu de esto, pero dado que sufro de una alergia severa macro-abuso, me parece que esta es la solución menos razonable en general para este problema.

(terminator el espacio de nombres es intrascendente - Yo uso este código en un stacktracer basada en libunwind llamada desde el manejador de terminación - no dude en s///g que Token)

2

Si utiliza impulso/core/demangle.hpp, puede obtener una cadena de lectura humana confiable.

char const * name = typeid(T).name(); 
boost::core::scoped_demangled_name demangled(name); 

std::cout << (demangled.get() ? demangled.get() : "Failed to demangle") << std::endl; 
0

en mi código que utilice el "horrible" doble declaración de la "Clase-Nombre"

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

porque C++ no es capaz de extraer el "MiServidor" cadena de la plantilla ... la única "forma" de deshacerse de esto ... usando un contenedor "cpp"

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