¿Es posible imprimir el tipo de una variable en C ++ estándar?

393

Por ejemplo:

int a = 12;
cout << typeof(a) << endl;

Rendimiento esperado:

int
Jorge Ferreira
fuente
2
A continuación se presenta un resumen de la solución de la forma larga de Howard, pero implementado con una macro de una línea herético: #define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL). Si necesita soporte multiplataforma: Uso #ifdef, #else, #endifpara proporcionar uno macros para otras plataformas como MSVC.
Trevor Boyd Smith
Con un requisito más explícito de lectura humana: stackoverflow.com/questions/12877521/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
3
Si solo usa esto para la depuración, puede considerar template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }. Luego, por ejemplo, print_T<const int * const **>();se imprimirá void print_T() [T = const int *const **]en tiempo de ejecución y conserva todos los calificadores (funciona en GCC y Clang).
Henri Menke
@Henri, __PRETTY_FUNCTION__no es C ++ estándar (el requisito está en el título de la pregunta).
Toby Speight

Respuestas:

505

Actualización de C ++ 11 a una pregunta muy antigua: escriba el tipo de variable en C ++.

La respuesta aceptada (y buena) es usar typeid(a).name(), donde aes un nombre de variable.

Ahora en C ++ 11 tenemos decltype(x), que puede convertir una expresión en un tipo. Y decltype()viene con su propio conjunto de reglas muy interesantes. Por ejemplo, decltype(a)y decltype((a))generalmente serán de diferentes tipos (y por razones buenas y comprensibles una vez que esas razones estén expuestas)

¿ typeid(a).name()Nos ayudará nuestro confiable a explorar este mundo nuevo y valiente?

No.

Pero la herramienta que lo hará no es tan complicada. Y es esa herramienta la que estoy usando como respuesta a esta pregunta. Compararé y contrastaré esta nueva herramienta typeid(a).name(). Y esta nueva herramienta en realidad está construida encima typeid(a).name().

La cuestión fundamental:

typeid(a).name()

tira los calificadores cv, referencias y lvalue / rvalue-ness. Por ejemplo:

const int ci = 0;
std::cout << typeid(ci).name() << '\n';

Para mí salidas:

i

y supongo que en las salidas de MSVC:

int

Es decir, constse ha ido. Este no es un problema de QOI (calidad de implementación). El estándar exige este comportamiento.

Lo que recomiendo a continuación es:

template <typename T> std::string type_name();

que se usaría así:

const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';

y para mí salidas:

int const

<disclaimer>No he probado esto en MSVC. </disclaimer> Pero agradezco los comentarios de quienes lo hacen.

La solución C ++ 11

Estoy usando __cxa_demanglepara plataformas que no son de MSVC como lo recomienda ipapadop en su respuesta a los tipos de demanda. Pero en MSVC estoy confiando typeiden exigir nombres (sin probar). Y este núcleo está envuelto en algunas pruebas simples que detectan, restauran e informan calificadores cv y referencias al tipo de entrada.

#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}

Los resultados

Con esta solución puedo hacer esto:

int& foo_lref();
int&& foo_rref();
int foo_value();

int
main()
{
    int i = 0;
    const int ci = 0;
    std::cout << "decltype(i) is " << type_name<decltype(i)>() << '\n';
    std::cout << "decltype((i)) is " << type_name<decltype((i))>() << '\n';
    std::cout << "decltype(ci) is " << type_name<decltype(ci)>() << '\n';
    std::cout << "decltype((ci)) is " << type_name<decltype((ci))>() << '\n';
    std::cout << "decltype(static_cast<int&>(i)) is " << type_name<decltype(static_cast<int&>(i))>() << '\n';
    std::cout << "decltype(static_cast<int&&>(i)) is " << type_name<decltype(static_cast<int&&>(i))>() << '\n';
    std::cout << "decltype(static_cast<int>(i)) is " << type_name<decltype(static_cast<int>(i))>() << '\n';
    std::cout << "decltype(foo_lref()) is " << type_name<decltype(foo_lref())>() << '\n';
    std::cout << "decltype(foo_rref()) is " << type_name<decltype(foo_rref())>() << '\n';
    std::cout << "decltype(foo_value()) is " << type_name<decltype(foo_value())>() << '\n';
}

y la salida es:

decltype(i) is int
decltype((i)) is int&
decltype(ci) is int const
decltype((ci)) is int const&
decltype(static_cast<int&>(i)) is int&
decltype(static_cast<int&&>(i)) is int&&
decltype(static_cast<int>(i)) is int
decltype(foo_lref()) is int&
decltype(foo_rref()) is int&&
decltype(foo_value()) is int

Tenga en cuenta (por ejemplo) la diferencia entre decltype(i)y decltype((i)). El primero es el tipo de declaración de i. Este último es el "tipo" de la expresión i . (las expresiones nunca tienen tipo de referencia, pero como convención decltyperepresenta expresiones de valor con referencias de valor).

Por lo tanto, esta herramienta es un excelente vehículo para aprender decltype, además de explorar y depurar su propio código.

Por el contrario, si tuviera que construir esto solo typeid(a).name(), sin volver a agregar referencias o calificadores cv perdidos, el resultado sería:

decltype(i) is int
decltype((i)) is int
decltype(ci) is int
decltype((ci)) is int
decltype(static_cast<int&>(i)) is int
decltype(static_cast<int&&>(i)) is int
decltype(static_cast<int>(i)) is int
decltype(foo_lref()) is int
decltype(foo_rref()) is int
decltype(foo_value()) is int

Es decir, se eliminan todas las referencias y calificadores cv.

Actualización de C ++ 14

Justo cuando crees que tienes una solución a un problema, alguien siempre sale de la nada y te muestra una forma mucho mejor. :-)

Esta respuesta de Jamboree muestra cómo obtener el nombre del tipo en C ++ 14 en tiempo de compilación. Es una solución brillante por un par de razones:

  1. ¡Está en tiempo de compilación!
  2. Obtiene el compilador para hacer el trabajo en lugar de una biblioteca (incluso un std :: lib). Esto significa resultados más precisos para las últimas características del lenguaje (como lambdas).

La respuesta de Jamboree no muestra todo para VS, y estoy modificando un poco su código. Pero dado que esta respuesta tiene muchas opiniones, tómese un tiempo para ir allí y votar su respuesta, sin lo cual, esta actualización nunca habría sucedido.

#include <cstddef>
#include <stdexcept>
#include <cstring>
#include <ostream>

#ifndef _MSC_VER
#  if __cplusplus < 201103
#    define CONSTEXPR11_TN
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN
#  elif __cplusplus < 201402
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN noexcept
#  else
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN constexpr
#    define NOEXCEPT_TN noexcept
#  endif
#else  // _MSC_VER
#  if _MSC_VER < 1900
#    define CONSTEXPR11_TN
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN
#  elif _MSC_VER < 2000
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN noexcept
#  else
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN constexpr
#    define NOEXCEPT_TN noexcept
#  endif
#endif  // _MSC_VER

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    CONSTEXPR11_TN static_string(const char(&a)[N]) NOEXCEPT_TN
        : p_(a)
        , sz_(N-1)
        {}

    CONSTEXPR11_TN static_string(const char* p, std::size_t N) NOEXCEPT_TN
        : p_(p)
        , sz_(N)
        {}

    CONSTEXPR11_TN const char* data() const NOEXCEPT_TN {return p_;}
    CONSTEXPR11_TN std::size_t size() const NOEXCEPT_TN {return sz_;}

    CONSTEXPR11_TN const_iterator begin() const NOEXCEPT_TN {return p_;}
    CONSTEXPR11_TN const_iterator end()   const NOEXCEPT_TN {return p_ + sz_;}

    CONSTEXPR11_TN char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

inline
std::ostream&
operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}

template <class T>
CONSTEXPR14_TN
static_string
type_name()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 31, p.size() - 31 - 1);
#elif defined(__GNUC__)
    static_string p = __PRETTY_FUNCTION__;
#  if __cplusplus < 201402
    return static_string(p.data() + 36, p.size() - 36 - 1);
#  else
    return static_string(p.data() + 46, p.size() - 46 - 1);
#  endif
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 38, p.size() - 38 - 7);
#endif
}

Este código se desactivará automáticamente constexprsi todavía está atascado en C ++ 11 antiguo. Y si está pintando en la pared de la cueva con C ++ 98/03, también noexceptse sacrifica.

Actualización de C ++ 17

En los comentarios a continuación, Lyberta señala que lo nuevo std::string_viewpuede reemplazar static_string:

template <class T>
constexpr
std::string_view
type_name()
{
    using namespace std;
#ifdef __clang__
    string_view p = __PRETTY_FUNCTION__;
    return string_view(p.data() + 34, p.size() - 34 - 1);
#elif defined(__GNUC__)
    string_view p = __PRETTY_FUNCTION__;
#  if __cplusplus < 201402
    return string_view(p.data() + 36, p.size() - 36 - 1);
#  else
    return string_view(p.data() + 49, p.find(';', 49) - 49);
#  endif
#elif defined(_MSC_VER)
    string_view p = __FUNCSIG__;
    return string_view(p.data() + 84, p.size() - 84 - 7);
#endif
}

He actualizado las constantes para VS gracias al muy buen trabajo de detective de Jive Dadson en los comentarios a continuación.

Actualizar:

Asegúrese de revisar esta reescritura a continuación, que elimina los números mágicos ilegibles en mi última formulación.

Howard Hinnant
fuente
44
VS 14 CTP imprimió los tipos correctos, solo tuve que agregar una #include <iostream>línea.
Max Galkin
3
¿Por qué plantilla <typename T> std :: string type_name ()? ¿Por qué no pasas un tipo como argumento?
moonman239
2
Creo que mi lógica era que a veces solo tenía un tipo (como un parámetro de plantilla deducido), y no quería tener que construir artificialmente uno de esos para obtener el tipo (aunque en estos días declvalharía el trabajo).
Howard Hinnant
55
@AngelusMortis: Debido a que el inglés es vago / ambiguo en comparación con el código C ++, le animo a que copie / pegue esto en su caso de prueba con el tipo específico que le interesa y con el compilador específico que le interesa, y escriba con más detalles si el resultado es sorprendente y / o insatisfactorio.
Howard Hinnant
3
@HowardHinnant puedes usar en std::string_viewlugar de static_string?
Lyberta
231

Tratar:

#include <typeinfo>

// …
std::cout << typeid(a).name() << '\n';

Es posible que deba activar RTTI en las opciones de su compilador para que esto funcione. Además, el resultado de esto depende del compilador. Puede ser un nombre de tipo sin formato o un símbolo de cambio de nombre o algo intermedio.

Konrad Rudolph
fuente
44
¿Por qué la cadena devuelta por la función name () está definida como implementación?
Destructor
44
@PravasiMeet No hay una buena razón, que yo sepa. El comité simplemente no quería forzar a los implementadores del compilador a direcciones técnicas particulares, probablemente en retrospectiva.
Konrad Rudolph
2
¿Hay alguna bandera que pueda usar para habilitar RTTI? Tal vez podrías hacer que tu respuesta sea inclusiva.
Jim
44
@Destructor Proporcionar un formato estandarizado de cambio de nombre puede dar la impresión de que la interoperabilidad entre los archivos binarios creados por dos compiladores diferentes es posible y / o segura, cuando no lo es. Debido a que C ++ no tiene un ABI estándar, un esquema de cambio de nombre estándar sería inútil y potencialmente engañoso y peligroso.
Elkvis
1
@ Jim La sección sobre los indicadores del compilador sería un orden de magnitud más largo que la respuesta misma. GCC lo compila de manera predeterminada, por lo tanto, "-fno-rtti", otros compiladores pueden elegir no hacerlo, pero no hay un estándar para los indicadores del compilador.
kfsone
82

Muy feo, pero funciona si solo quieres información de tiempo de compilación (por ejemplo, para depurar):

auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo= 1;

Devoluciones:

Compilation finished with errors:
source.cpp: In function 'int main()':
source.cpp:5:19: error: 'foo' is not a member of 'std::tuple<int, double, const char*>'
NickV
fuente
2
solo c ++ podría hacer esto tan difícil (imprimir un tipo de variables automáticas en tiempo de compilación). SOLO C ++.
Karl Pickett
3
@KarlP, bueno, para ser justos, es un poco complicado, esto también funciona :) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
NickV
En VC ++ 17, esto reduce una referencia de valor r a una referencia simple, incluso en una función de plantilla con parámetro de referencia de reenvío, y el nombre del objeto envuelto en std :: forward.
Jive Dadson el
¡Pudiste llegar al tipo sin crear ruedas nuevas!
Steven Eckhoff
1
Esta técnica también se describe en "Elemento 4: Sepa cómo ver los tipos deducidos" en C ++ moderno efectivo
lenkite
54

No olvides incluir <typeinfo>

Creo que a lo que se refiere es a la identificación del tipo de tiempo de ejecución. Puedes lograr lo anterior haciendo.

#include <iostream>
#include <typeinfo>

using namespace std;

int main() {
  int i;
  cout << typeid(i).name();
  return 0;
}
mdec
fuente
36

Según la solución de Howard , si no quieres el número mágico, creo que esta es una buena manera de representar y luce intuitivo:

#include <string_view>

template <typename T>
constexpr std::string_view 
type_name()
{
    std::string_view name, prefix, suffix;
#ifdef __clang__
    name = __PRETTY_FUNCTION__;
    prefix = "std::string_view type_name() [T = ";
    suffix = "]";
#elif defined(__GNUC__)
    name = __PRETTY_FUNCTION__;
    prefix = "constexpr std::string_view type_name() [with T = ";
    suffix = "; std::string_view = std::basic_string_view<char>]";
#elif defined(_MSC_VER)
    name = __FUNCSIG__;
    prefix = "class std::basic_string_view<char,struct std::char_traits<char> > __cdecl type_name<";
    suffix = ">(void)";
#endif
    name.remove_prefix(prefix.size());
    name.remove_suffix(suffix.size());
    return name;
}
康 桓 瑋
fuente
44
Esta es una gran destilación de esfuerzos en las últimas versiones de C ++ en algo corto y dulce. +1.
einpoklum
1
¡Este es mi favorito también!
Howard Hinnant
1
Aquí una función similar que uso, que detecta el sufijo / prefijo automáticamente: stackoverflow.com/questions/1055452/…
HolyBlackCat
22

Tenga en cuenta que los nombres generados por la función RTTI de C ++ no son portables. Por ejemplo, la clase

MyNamespace::CMyContainer<int, test_MyNamespace::CMyObject>

tendrá los siguientes nombres:

// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE

Por lo tanto, no puede usar esta información para la serialización. Pero aún así, la propiedad typeid (a) .name () todavía se puede usar para fines de registro / depuración

paercebal
fuente
19

Puedes usar plantillas.

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(float&) { return "float"; }

En el ejemplo anterior, cuando el tipo no coincide, imprimirá "desconocido".

Mella
fuente
3
¿No imprimirá "int" para cortos y caracteres? ¿Y "flotar" para dobles?
gartenriese
1
La especialización de @gartenriese no tiene ese inconveniente. Porque doublecompilaría la versión no especializada de la función de plantilla en lugar de hacer una conversión de tipo implícita para usar la especialización: cpp.sh/2wzc
chappjc
1
@chappjc: Sinceramente, no sé por qué pregunté eso en ese momento, ahora me queda bastante claro. ¡Pero gracias por responder una pregunta de un año de todos modos!
gartenriese
2
@gartenriese me lo imaginé, pero "internet" podría tener la misma pregunta en algún momento.
chappjc
18

Como se mencionó, typeid().name()puede devolver un nombre destrozado. En GCC (y algunos otros compiladores) puede solucionarlo con el siguiente código:

#include <cxxabi.h>
#include <iostream>
#include <typeinfo>
#include <cstdlib>

namespace some_namespace { namespace another_namespace {

  class my_class { };

} }

int main() {
  typedef some_namespace::another_namespace::my_class my_type;
  // mangled
  std::cout << typeid(my_type).name() << std::endl;

  // unmangled
  int status = 0;
  char* demangled = abi::__cxa_demangle(typeid(my_type).name(), 0, 0, &status);

  switch (status) {
    case -1: {
      // could not allocate memory
      std::cout << "Could not allocate memory" << std::endl;
      return -1;
    } break;
    case -2: {
      // invalid name under the C++ ABI mangling rules
      std::cout << "Invalid name" << std::endl;
      return -1;
    } break;
    case -3: {
      // invalid argument
      std::cout << "Invalid argument to demangle()" << std::endl;
      return -1;
    } break;
 }
 std::cout << demangled << std::endl;

 free(demangled);

 return 0;

}

ipapadop
fuente
10

Podrías usar una clase de rasgos para esto. Algo como:

#include <iostream>
using namespace std;

template <typename T> class type_name {
public:
    static const char *name;
};

#define DECLARE_TYPE_NAME(x) template<> const char *type_name<x>::name = #x;
#define GET_TYPE_NAME(x) (type_name<typeof(x)>::name)

DECLARE_TYPE_NAME(int);

int main()
{
    int a = 12;
    cout << GET_TYPE_NAME(a) << endl;
}

La DECLARE_TYPE_NAMEdefinición existe para facilitarle la vida al declarar esta clase de rasgos para todos los tipos que espera necesitar.

Esto podría ser más útil que las soluciones involucradas typeidporque puedes controlar la salida. Por ejemplo, usar typeidfor long longen mi compilador da "x".

Greg Hewgill
fuente
10

En C ++ 11, tenemos decltype. No hay forma en c ++ estándar para mostrar el tipo exacto de variable declarada usando decltype. Podemos usar boost typeindex, es decir type_id_with_cvr(cvr significa const, volátil, referencia) para imprimir el tipo como se muestra a continuación.

#include <iostream>
#include <boost/type_index.hpp>

using namespace std;
using boost::typeindex::type_id_with_cvr;

int main() {
  int i = 0;
  const int ci = 0;
  cout << "decltype(i) is " << type_id_with_cvr<decltype(i)>().pretty_name() << '\n';
  cout << "decltype((i)) is " << type_id_with_cvr<decltype((i))>().pretty_name() << '\n';
  cout << "decltype(ci) is " << type_id_with_cvr<decltype(ci)>().pretty_name() << '\n';
  cout << "decltype((ci)) is " << type_id_with_cvr<decltype((ci))>().pretty_name() << '\n';
  cout << "decltype(std::move(i)) is " << type_id_with_cvr<decltype(std::move(i))>().pretty_name() << '\n';
  cout << "decltype(std::static_cast<int&&>(i)) is " << type_id_with_cvr<decltype(static_cast<int&&>(i))>().pretty_name() << '\n';
  return 0;
}
abodeofcode
fuente
1
sería más simple usar una función auxiliar:template<typename T> void print_type(T){cout << "type T is: "<< type_id_with_cvr<T>().pretty_name()<< '\n';}
r0ng
6

También puede usar c ++ filt con la opción -t (tipo) para solicitar el nombre del tipo:

#include <iostream>
#include <typeinfo>
#include <string>

using namespace std;

int main() {
  auto x = 1;
  string my_type = typeid(x).name();
  system(("echo " + my_type + " | c++filt -t").c_str());
  return 0;
}

Probado solo en Linux.

Alan
fuente
1
Infierno feo pero hará lo que necesito. Y mucho más pequeño que las otras soluciones. Funciona en Mac por cierto.
Marco Luglio
6

Howard Hinnant usó números mágicos para extraer el nombre del tipo. 康 桓 瑋 sugirió el prefijo y sufijo de la cadena. Pero el prefijo / sufijo sigue cambiando. Con "probe_type" type_name calcula automáticamente los tamaños de prefijo y sufijo para "probe_type" para extraer el nombre del tipo:

#include <iostream>
#include <string_view>

using namespace std;

class probe_type;

template <typename T>
constexpr string_view type_name() {
  string_view probe_type_name("class probe_type");
  const string_view class_specifier("class");

  string_view name;
#ifdef __clang__
  name = __PRETTY_FUNCTION__;
  probe_type_name.remove_prefix(class_specifier.length());
#elif defined(__GNUC__)
  name = __PRETTY_FUNCTION__;
  probe_type_name.remove_prefix(class_specifier.length());
#elif defined(_MSC_VER)
  name = __FUNCSIG__;
#endif

  if (name.find(probe_type_name) != string_view::npos)
    return name;

  const string_view probe_type_raw_name = type_name<probe_type>();

  const size_t prefix_size = probe_type_raw_name.find(probe_type_name);

  name.remove_prefix(prefix_size);
  name.remove_suffix(probe_type_raw_name.length() - prefix_size - probe_type_name.length());

  return name;
}

class test;

int main() {
  cout << type_name<test>() << endl;

  cout << type_name<const int*&>() << endl;
  cout << type_name<unsigned int>() << endl;

  const int ic = 42;
  const int* pic = &ic;
  const int*& rpic = pic;
  cout << type_name<decltype(ic)>() << endl;
  cout << type_name<decltype(pic)>() << endl;
  cout << type_name<decltype(rpic)>() << endl;

  cout << type_name<probe_type>() << endl;
}

Salida

gcc 10.0.0 20190919 Wandbox:

 test
 const int *&
 unsigned int
 const int
 const int *
 const int *&
 constexpr std::string_view type_name() [with T = probe_type; std::string_view = std::basic_string_view<char>]

clang 10.0.0 Wandbox:

 test
 const int *&
 unsigned int
 const int
 const int *
 const int *&
 std::__1::string_view type_name() [T = probe_type]

VS 2019 versión 16.3.3:

class test
const int*&
unsigned int
const int
const int*
const int*&
class std::basic_string_view<char,struct std::char_traits<char> > __cdecl type_name<class probe_type>(void)
Val
fuente
5

Las otras respuestas que involucran RTTI (typeid) son probablemente lo que desea, siempre que:

  • puede permitirse la sobrecarga de memoria (que puede ser considerable con algunos compiladores)
  • los nombres de clase que devuelve su compilador son útiles

La alternativa, (similar a la respuesta de Greg Hewgill), es construir una tabla de rasgos en tiempo de compilación.

template <typename T> struct type_as_string;

// declare your Wibble type (probably with definition of Wibble)
template <>
struct type_as_string<Wibble>
{
    static const char* const value = "Wibble";
};

Tenga en cuenta que si ajusta las declaraciones en una macro, tendrá problemas para declarar nombres para los tipos de plantilla que toman más de un parámetro (por ejemplo, std :: map), debido a la coma.

Para acceder al nombre del tipo de una variable, todo lo que necesita es

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}
James Hopkin
fuente
1
Buen punto sobre la coma, sabía que había una razón por la que las macros eran una mala idea, ¡pero no pensé en eso en ese momento!
Greg Hewgill
2
static const char * value = "Wibble"; no puedes hacer eso amigo :)
Johannes Schaub - litb
5

Una solución más genérica sin sobrecarga de funciones que la anterior:

template<typename T>
std::string TypeOf(T){
    std::string Type="unknown";
    if(std::is_same<T,int>::value) Type="int";
    if(std::is_same<T,std::string>::value) Type="String";
    if(std::is_same<T,MyClass>::value) Type="MyClass";

    return Type;}

Aquí MyClass es una clase definida por el usuario. Aquí también se pueden agregar más condiciones.

Ejemplo:

#include <iostream>



class MyClass{};


template<typename T>
std::string TypeOf(T){
    std::string Type="unknown";
    if(std::is_same<T,int>::value) Type="int";
    if(std::is_same<T,std::string>::value) Type="String";
    if(std::is_same<T,MyClass>::value) Type="MyClass";
    return Type;}


int main(){;
    int a=0;
    std::string s="";
    MyClass my;
    std::cout<<TypeOf(a)<<std::endl;
    std::cout<<TypeOf(s)<<std::endl;
    std::cout<<TypeOf(my)<<std::endl;

    return 0;}

Salida:

int
String
MyClass
Jahid
fuente
5

Me gusta el método de Nick. Un formulario completo podría ser este (para todos los tipos de datos básicos):

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(short&) { return "short"; }
template<> const char* typeof(long&) { return "long"; }
template<> const char* typeof(unsigned&) { return "unsigned"; }
template<> const char* typeof(unsigned short&) { return "unsigned short"; }
template<> const char* typeof(unsigned long&) { return "unsigned long"; }
template<> const char* typeof(float&) { return "float"; }
template<> const char* typeof(double&) { return "double"; }
template<> const char* typeof(long double&) { return "long double"; }
template<> const char* typeof(std::string&) { return "String"; }
template<> const char* typeof(char&) { return "char"; }
template<> const char* typeof(signed char&) { return "signed char"; }
template<> const char* typeof(unsigned char&) { return "unsigned char"; }
template<> const char* typeof(char*&) { return "char*"; }
template<> const char* typeof(signed char*&) { return "signed char*"; }
template<> const char* typeof(unsigned char*&) { return "unsigned char*"; }
Jahid
fuente
2
(i) no funcionará para otros tipos (es decir, no es genérico en absoluto); (ii) hinchazón de código inútil; (iii) lo mismo puede hacerse (correctamente) con typeido decltype.
edmz
2
Tienes razón, pero cubre todos los tipos básicos ... y eso es lo que necesito ahora ...
Jahid
2
¿Puedes decirme cómo lo harías con decltype?
Jahid
1
Si es una prueba en tiempo de compilación, puede usar std :: is_same <T, S> y decltype para obtener T y S.
edmz
4

Mientras desafiaba decidí probar hasta dónde se podía llegar con un truco de plantilla independiente de la plataforma (con suerte).

Los nombres se ensamblan completamente en el momento de la compilación. (Lo que significa typeid(T).name()que no se puede utilizar, por lo que debe proporcionar explícitamente nombres para tipos no compuestos. De lo contrario, se mostrarán marcadores de posición).

Ejemplo de uso:

TYPE_NAME(int)
TYPE_NAME(void)
// You probably should list all primitive types here.

TYPE_NAME(std::string)

int main()
{
    // A simple case
    std::cout << type_name<void(*)(int)> << '\n';
    // -> `void (*)(int)`

    // Ugly mess case
    // Note that compiler removes cv-qualifiers from parameters and replaces arrays with pointers.
    std::cout << type_name<void (std::string::*(int[3],const int, void (*)(std::string)))(volatile int*const*)> << '\n';
    // -> `void (std::string::*(int *,int,void (*)(std::string)))(volatile int *const*)`

    // A case with undefined types
    //  If a type wasn't TYPE_NAME'd, it's replaced by a placeholder, one of `class?`, `union?`, `enum?` or `??`.
    std::cout << type_name<std::ostream (*)(int, short)> << '\n';
    // -> `class? (*)(int,??)`
    // With appropriate TYPE_NAME's, the output would be `std::string (*)(int,short)`.
}

Código:

#include <type_traits>
#include <utility>

static constexpr std::size_t max_str_lit_len = 256;

template <std::size_t I, std::size_t N> constexpr char sl_at(const char (&str)[N])
{
    if constexpr(I < N)
        return str[I];
    else
        return '\0';
}

constexpr std::size_t sl_len(const char *str)
{
    for (std::size_t i = 0; i < max_str_lit_len; i++)
        if (str[i] == '\0')
            return i;
    return 0;
}

template <char ...C> struct str_lit
{
    static constexpr char value[] {C..., '\0'};
    static constexpr int size = sl_len(value);

    template <typename F, typename ...P> struct concat_impl {using type = typename concat_impl<F>::type::template concat_impl<P...>::type;};
    template <char ...CC> struct concat_impl<str_lit<CC...>> {using type = str_lit<C..., CC...>;};
    template <typename ...P> using concat = typename concat_impl<P...>::type;
};

template <typename, const char *> struct trim_str_lit_impl;
template <std::size_t ...I, const char *S> struct trim_str_lit_impl<std::index_sequence<I...>, S>
{
    using type = str_lit<S[I]...>;
};
template <std::size_t N, const char *S> using trim_str_lit = typename trim_str_lit_impl<std::make_index_sequence<N>, S>::type;

#define STR_LIT(str) ::trim_str_lit<::sl_len(str), ::str_lit<STR_TO_VA(str)>::value>
#define STR_TO_VA(str) STR_TO_VA_16(str,0),STR_TO_VA_16(str,16),STR_TO_VA_16(str,32),STR_TO_VA_16(str,48)
#define STR_TO_VA_16(str,off) STR_TO_VA_4(str,0+off),STR_TO_VA_4(str,4+off),STR_TO_VA_4(str,8+off),STR_TO_VA_4(str,12+off)
#define STR_TO_VA_4(str,off) ::sl_at<off+0>(str),::sl_at<off+1>(str),::sl_at<off+2>(str),::sl_at<off+3>(str)

template <char ...C> constexpr str_lit<C...> make_str_lit(str_lit<C...>) {return {};}
template <std::size_t N> constexpr auto make_str_lit(const char (&str)[N])
{
    return trim_str_lit<sl_len((const char (&)[N])str), str>{};
}

template <std::size_t A, std::size_t B> struct cexpr_pow {static constexpr std::size_t value = A * cexpr_pow<A,B-1>::value;};
template <std::size_t A> struct cexpr_pow<A,0> {static constexpr std::size_t value = 1;};
template <std::size_t N, std::size_t X, typename = std::make_index_sequence<X>> struct num_to_str_lit_impl;
template <std::size_t N, std::size_t X, std::size_t ...Seq> struct num_to_str_lit_impl<N, X, std::index_sequence<Seq...>>
{
    static constexpr auto func()
    {
        if constexpr (N >= cexpr_pow<10,X>::value)
            return num_to_str_lit_impl<N, X+1>::func();
        else
            return str_lit<(N / cexpr_pow<10,X-1-Seq>::value % 10 + '0')...>{};
    }
};
template <std::size_t N> using num_to_str_lit = decltype(num_to_str_lit_impl<N,1>::func());


using spa = str_lit<' '>;
using lpa = str_lit<'('>;
using rpa = str_lit<')'>;
using lbr = str_lit<'['>;
using rbr = str_lit<']'>;
using ast = str_lit<'*'>;
using amp = str_lit<'&'>;
using con = str_lit<'c','o','n','s','t'>;
using vol = str_lit<'v','o','l','a','t','i','l','e'>;
using con_vol = con::concat<spa, vol>;
using nsp = str_lit<':',':'>;
using com = str_lit<','>;
using unk = str_lit<'?','?'>;

using c_cla = str_lit<'c','l','a','s','s','?'>;
using c_uni = str_lit<'u','n','i','o','n','?'>;
using c_enu = str_lit<'e','n','u','m','?'>;

template <typename T> inline constexpr bool ptr_or_ref = std::is_pointer_v<T> || std::is_reference_v<T> || std::is_member_pointer_v<T>;
template <typename T> inline constexpr bool func_or_arr = std::is_function_v<T> || std::is_array_v<T>;

template <typename T> struct primitive_type_name {using value = unk;};

template <typename T, typename = std::enable_if_t<std::is_class_v<T>>> using enable_if_class = T;
template <typename T, typename = std::enable_if_t<std::is_union_v<T>>> using enable_if_union = T;
template <typename T, typename = std::enable_if_t<std::is_enum_v <T>>> using enable_if_enum  = T;
template <typename T> struct primitive_type_name<enable_if_class<T>> {using value = c_cla;};
template <typename T> struct primitive_type_name<enable_if_union<T>> {using value = c_uni;};
template <typename T> struct primitive_type_name<enable_if_enum <T>> {using value = c_enu;};

template <typename T> struct type_name_impl;

template <typename T> using type_name_lit = std::conditional_t<std::is_same_v<typename primitive_type_name<T>::value::template concat<spa>,
                                                                               typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>,
                                            typename primitive_type_name<T>::value,
                                            typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>;
template <typename T> inline constexpr const char *type_name = type_name_lit<T>::value;

template <typename T, typename = std::enable_if_t<!std::is_const_v<T> && !std::is_volatile_v<T>>> using enable_if_no_cv = T;

template <typename T> struct type_name_impl
{
    using l = typename primitive_type_name<T>::value::template concat<spa>;
    using r = str_lit<>;
};
template <typename T> struct type_name_impl<const T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con>,
                                 con::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<vol>,
                                 vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<const volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con_vol>,
                                 con_vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<T *>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, ast>,
                                 typename type_name_impl<T>::l::template concat<     ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &&>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp, amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T, typename C> struct type_name_impl<T C::*>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, type_name_lit<C>, nsp, ast>,
                                 typename type_name_impl<T>::l::template concat<     type_name_lit<C>, nsp, ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<enable_if_no_cv<T[]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<rbr, typename type_name_impl<T>::r>;
};
template <typename T, std::size_t N> struct type_name_impl<enable_if_no_cv<T[N]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<num_to_str_lit<N>, rbr, typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T()>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<rpa, typename type_name_impl<T>::r>;
};
template <typename T, typename P1, typename ...P> struct type_name_impl<T(P1, P...)>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<type_name_lit<P1>,
                          com::concat<type_name_lit<P>>..., rpa, typename type_name_impl<T>::r>;
};

#define TYPE_NAME(t) template <> struct primitive_type_name<t> {using value = STR_LIT(#t);};
HolyBlackCat
fuente
2
#include <iostream>
#include <typeinfo>
using namespace std;
#define show_type_name(_t) \
    system(("echo " + string(typeid(_t).name()) + " | c++filt -t").c_str())

int main() {
    auto a = {"one", "two", "three"};
    cout << "Type of a: " << typeid(a).name() << endl;
    cout << "Real type of a:\n";
    show_type_name(a);
    for (auto s : a) {
        if (string(s) == "one") {
            cout << "Type of s: " << typeid(s).name() << endl;
            cout << "Real type of s:\n";
            show_type_name(s);
        }
        cout << s << endl;
    }

    int i = 5;
    cout << "Type of i: " << typeid(i).name() << endl;
    cout << "Real type of i:\n";
    show_type_name(i);
    return 0;
}

Salida:

Type of a: St16initializer_listIPKcE
Real type of a:
std::initializer_list<char const*>
Type of s: PKc
Real type of s:
char const*
one
two
three
Type of i: i
Real type of i:
int
Lobo gris
fuente
2

Como explicó Scott Meyers en Effective Modern C ++,

Las llamadas a std::type_info::nameno están garantizados para volver anythong sensata.

La mejor solución es dejar que el compilador genere un mensaje de error durante la deducción de tipo, por ejemplo,

template<typename T>
class TD;

int main(){
    const int theAnswer = 32;
    auto x = theAnswer;
    auto y = &theAnswer;
    TD<decltype(x)> xType;
    TD<decltype(y)> yType;
    return 0;
}

El resultado será algo como esto, dependiendo de los compiladores,

test4.cpp:10:21: error: aggregate TD<int> xType has incomplete type and cannot be defined TD<decltype(x)> xType;

test4.cpp:11:21: error: aggregate TD<const int *> yType has incomplete type and cannot be defined TD<decltype(y)> yType;

Por lo tanto, llegamos a saber que xel tipo es int, yel tipo esconst int*

Milo Lu
fuente
0

Para cualquiera que todavía visite, recientemente tuve el mismo problema y decidí escribir una pequeña biblioteca basada en las respuestas de esta publicación. Proporciona nombres de tipo constexpr e índices de tipo y se prueba en Mac, Windows y Ubuntu.

El código de la biblioteca está aquí: https://github.com/TheLartians/StaticTypeInfo

Lars Melchior
fuente