Solución:
Actualización de C ++ 11 a una pregunta muy antigua: Imprimir tipo de variable en C ++.
La respuesta aceptada (y buena) es usar typeid(a).name()
, dónde a
es 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 buenas y comprensibles razones una vez que se expongan esas razones).
¿Será nuestro fiel typeid(a).name()
ayúdanos a explorar este nuevo mundo feliz?
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 con typeid(a).name()
. Y esta nueva herramienta está construida sobre typeid(a).name()
.
El problema fundamental:
typeid(a).name()
descarta calificadores de 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, el const
se 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 mi 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_demangle
para plataformas que no son MSVC como lo recomienda ipapadop en su respuesta a los tipos de demanda. Pero en MSVC confío typeid
para exigir nombres (no probado). Y este núcleo está envuelto en algunas pruebas simples que detectan, restauran e informan calificadores de 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 expresión i
. (las expresiones nunca tienen un tipo de referencia, sino como una convención decltype
representa expresiones lvalue con referencias lvalue).
Por lo tanto, esta herramienta es un vehículo excelente para aprender decltype
, además de explorar y depurar su propio código.
Por el contrario, si tuviera que construir esto solo en typeid(a).name()
, sin volver a agregar calificadores o referencias de 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, siempre aparece alguien de la nada y te muestra un camino 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:
- ¡Está en tiempo de compilación!
- Obtienes el propio compilador para hacer el trabajo en lugar de una biblioteca (incluso una std :: lib). Esto significa resultados más precisos para las últimas funciones del lenguaje (como lambdas).
La respuesta de Jamboree no presenta todo para VS, y estoy modificando un poco su código. Pero dado que esta respuesta recibe muchas visitas, tómese un tiempo para ir allí y votar su respuesta, sin la 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 retrocederá automáticamente en el constexpr
si todavía está atascado en el antiguo C ++ 11. Y si está pintando en la pared de la cueva con C ++ 98/03, el noexcept
también se sacrifica.
Actualización de C ++ 17
En los comentarios a continuación, Lyberta señala que el nuevo std::string_view
puede sustituir 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
}
Actualicé 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.
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 alteración de nombres o cualquier cosa intermedia.
Muy feo, pero funciona si solo desea información de tiempo de compilación (por ejemplo, para depuración):
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*>'