C ++ 11 Update auf eine sehr alte Frage: Druckvariablentyp in C ++.
Die akzeptierte (und gut) Antwort ist die Verwendung typeid(a).name()
, wo a
ein Variablenname.
Jetzt haben wir in C ++ 11 decltype(x)
, was einen Ausdruck in einen Typ verwandeln kann. Und decltype()
kommt mit seinen eigenen sehr interessanten Regeln. Zum Beispiel decltype(a)
und decltype((a))
wird im Allgemeinen verschiedene Typen sein (und aus guten und verständlichen Gründen, sobald diese Gründe aufgedeckt sind).
Werden unsere vertrauenswürdigen typeid(a).name()
uns helfen, diese schöne neue Welt zu erkunden?
Nein.
Aber das Werkzeug, das wird, ist nicht so kompliziert. Und es ist dieses Werkzeug, das ich als Antwort auf diese Frage benutze. Ich werde dieses neue Tool vergleichen und gegenüberstellen typeid(a).name()
. Und dieses neue Tool baut tatsächlich darauf auftypeid(a).name()
.
Das grundlegende Problem:
typeid(a).name()
wirft cv-qualifiers, referenzen und lvalue / rvalue-ness weg. Zum Beispiel:
const int ci = 0;
std::cout << typeid(ci).name() << '\n';
Für mich Ausgaben:
i
und ich vermute auf MSVC-Ausgängen:
int
Dh das const
ist weg. Dies ist kein QOI-Problem (Quality of Implementation). Der Standard schreibt dieses Verhalten vor.
Was ich unten empfehle, ist:
template <typename T> std::string type_name();
welches so verwendet werden würde:
const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';
und für mich Ausgaben:
int const
<disclaimer>
Ich habe dies nicht auf MSVC getestet. </disclaimer>
Aber ich freue mich über Feedback von denen, die dies tun.
Die C ++ 11-Lösung
Ich verwende __cxa_demangle
für Nicht-MSVC-Plattformen, wie von ipapadop in seiner Antwort auf Demangle-Typen empfohlen. Aber bei MSVC vertraue ich darauf typeid
, Namen zu entwirren (ungetestet). In diesem Kern geht es um einige einfache Tests, bei denen Lebenslaufqualifizierer und Verweise auf den Eingabetyp erkannt, wiederhergestellt und gemeldet werden.
#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;
}
Die Ergebnisse
Mit dieser Lösung kann ich Folgendes tun:
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';
}
und die Ausgabe ist:
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
Beachten Sie (zum Beispiel) den Unterschied zwischen decltype(i)
und decltype((i))
. Ersteres ist die Art der Erklärung von i
. Letzteres ist der "Typ" des Ausdrucks i
. (Ausdrücke haben niemals einen Referenztyp, sondern stellen als Konvention decltype
lWert-Ausdrücke mit lWert-Referenzen dar).
Somit ist dieses Tool ein hervorragendes Mittel, um etwas zu lernen decltype
und Ihren eigenen Code zu erkunden und zu debuggen.
Im Gegensatz dazu wäre typeid(a).name()
die Ausgabe , wenn ich dies nur aufbauen würde , ohne verlorene Lebenslaufqualifizierer oder Referenzen wieder hinzuzufügen:
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
Dh jeder Referenz- und Lebenslaufqualifizierer wird entfernt.
C ++ 14 Update
Gerade wenn Sie glauben, eine Lösung für ein Problem gefunden zu haben, kommt immer jemand aus dem Nichts und zeigt Ihnen einen viel besseren Weg. :-)
Diese Antwort von Jamboree zeigt, wie der Typname in C ++ 14 zur Kompilierungszeit abgerufen wird. Es ist aus mehreren Gründen eine brillante Lösung:
- Es ist zur Kompilierungszeit!
- Sie veranlassen den Compiler selbst, den Job anstelle einer Bibliothek (sogar einer std :: lib) auszuführen. Dies bedeutet genauere Ergebnisse für die neuesten Sprachfunktionen (wie Lambdas).
Jamborees Antwort legt nicht alles für VS fest, und ich optimiere seinen Code ein wenig. Da diese Antwort jedoch viele Aufrufe erhält, nehmen Sie sich etwas Zeit, um ihre Antwort zu verbessern. Ohne diese Antwort wäre dieses Update niemals zustande gekommen.
#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
}
Dieser Code wird automatisch zurückgesetzt, constexpr
wenn Sie noch im alten C ++ 11 stecken. Und wenn Sie mit C ++ 98/03 an die Höhlenwand malen, ist dienoexcept
wird auch das geopfert.
C ++ 17 Update
In den Kommentaren unten weist Lyberta darauf hin, dass das Neue std::string_view
ersetzen kann 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
}
Ich habe die Konstanten für VS dank der sehr schönen Detektivarbeit von Jive Dadson in den Kommentaren unten aktualisiert.
Aktualisieren:
Schauen Sie sich unbedingt diese unten stehende Umschreibung an, die die unlesbaren magischen Zahlen in meiner neuesten Formulierung beseitigt.
#define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL)
. Wenn Sie plattformübergreifende Unterstützung benötigen: Verwenden Sie#ifdef
,#else
,#endif
ein Makros für andere Plattformen wie MSVC zu bieten.