Zum Beispiel:
int a = 12;
cout << typeof(a) << endl;
Erwartete Ausgabe:
int
Jörg Ferreira
Zum Beispiel:
int a = 12;
cout << typeof(a) << endl;
Erwartete Ausgabe:
int
Howard Hinnant
C++11-Update zu einer sehr alten Frage: Variablentyp in C++ drucken.
Die akzeptierte (und gute) Antwort ist zu verwenden typeid(a).name()
wo a
ist ein Variablenname.
Jetzt in C++11 haben wir decltype(x)
, die einen Ausdruck in einen Typ umwandeln kann. Und decltype()
kommt mit einem eigenen Satz sehr interessanter Regeln. Zum Beispiel decltype(a)
und decltype((a))
werden im Allgemeinen unterschiedliche Typen sein (und aus guten und verständlichen Gründen, sobald diese Gründe aufgedeckt sind).
Wird unser treuer typeid(a).name()
Helfen Sie uns, 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 verwende. Ich werde dieses neue Tool vergleichen und gegenüberstellen typeid(a).name()
. Und dieses neue Tool ist tatsächlich darauf aufgebaut typeid(a).name()
.
Das Grundproblem:
typeid(a).name()
wirft CV-Qualifizierer, Referenzen und lvalue/rvalue-ness weg. Zum Beispiel:
const int ci = 0;
std::cout << typeid(ci).name() << '\n';
Für mich gibt es aus:
i
und ich vermute auf MSVC-Ausgaben:
int
Dh die 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();
was so verwendet werden würde:
const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';
und für mich gibt aus:
int const
<disclaimer>
Ich habe dies nicht auf MSVC getestet. </disclaimer>
Aber ich freue mich über Feedback von denen, die es tun.
Die C++11-Lösung
ich benutze __cxa_demangle
für Nicht-MSVC-Plattformen, wie von ipapadop in seiner Antwort auf demangle-Typen empfohlen. Aber auf MSVC vertraue ich typeid
Namen zu entwirren (ungetestet). Und dieser Kern ist um einige einfache Tests gewickelt, die CV-Qualifizierer und Verweise auf den Eingabetyp erkennen, wiederherstellen und melden.
#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 das 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” der Ausdruck i
. (Ausdrücke haben nie einen Referenztyp, sondern als Konvention decltype
stellt lvalue-Ausdrücke mit lvalue-Referenzen dar).
Daher ist dieses Tool ein hervorragendes Mittel, um es einfach kennenzulernen decltype
zusätzlich zum Untersuchen und Debuggen Ihres eigenen Codes.
Im Gegensatz dazu würde ich diese einfach weiter aufbauen typeid(a).name()
ohne verlorene CV-Qualifizierer oder Referenzen wieder hinzuzufügen, wäre die Ausgabe:
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 alle Referenzen und CV-Qualifier werden entfernt.
C++14-Update
Gerade wenn Sie denken, dass Sie eine Lösung für ein Problem gefunden haben, kommt immer jemand aus dem Nichts und zeigt Ihnen einen viel besseren Weg. 🙂
Diese Antwort von Jamboree zeigt, wie Sie den Typnamen in C++14 zur Kompilierzeit erhalten. Es ist aus mehreren Gründen eine brillante Lösung:
Die Antwort von Jamboree legt nicht alles für VS dar, und ich passe seinen Code ein wenig an. Da diese Antwort jedoch viele Aufrufe erhält, nehmen Sie sich etwas Zeit, um dorthin zu gehen und seine Antwort positiv zu bewerten, ohne die dieses Update niemals stattgefunden hätte.
#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 auf dem automatisch zurückgesetzt constexpr
wenn Sie immer noch im alten C++11 stecken. Und wenn Sie mit C++98/03 auf die Höhlenwand malen, wird die noexcept
wird auch geopfert.
C++17-Update
In den Kommentaren unten weist Lyberta darauf hin, dass die neue std::string_view
ersetzen können 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 netten Detektivarbeit von Jive Dadson in den Kommentaren unten aktualisiert.
Schauen Sie sich unbedingt diese Neufassung unten an, die die unlesbaren magischen Zahlen in meiner neuesten Formulierung eliminiert.
VS 14 CTP druckte korrekte Typen, ich musste nur einen hinzufügen #include <iostream>
Linie.
– Max Galkin
21. Dezember 2014 um 7:02 Uhr
Warum template
– Montana Burr
31. Dezember 2015 um 0:05 Uhr
Ich glaube, meine Begründung war, dass ich manchmal nur hatte einen Typ (z. B. einen abgeleiteten Vorlagenparameter), und ich wollte nicht künstlich einen davon konstruieren müssen, um den Typ zu erhalten (obwohl heutzutage declval
würde den Job machen).
– Howard Hinnant
31. Dezember 2015 um 2:29 Uhr
@AngelusMortis: Da Englisch im Vergleich zu C ++ – Code vage / mehrdeutig ist, empfehle ich Ihnen, dies mit dem spezifischen Typ, an dem Sie interessiert sind, zu kopieren / in Ihren Testfall einzufügen, und mit dem spezifischen Compiler, an dem Sie interessiert sind, und mit mehr zurück zu schreiben Details, wenn das Ergebnis überraschend und/oder unbefriedigend ist.
– Howard Hinnant
2. März 2016 um 22:20 Uhr
@HowardHinnant kannst du verwenden std::string_view
anstatt static_string
?
– Benutzer3624760
9. August 2017 um 20:16 Uhr
Konrad Rudolf
Versuchen:
#include <typeinfo>
// …
std::cout << typeid(a).name() << '\n';
Möglicherweise müssen Sie RTTI in Ihren Compileroptionen aktivieren, damit dies funktioniert. Außerdem hängt die Ausgabe davon vom Compiler ab. Es kann sich um einen Rohtypnamen oder ein Namensverstümmelungssymbol oder irgendetwas dazwischen handeln.
Warum ist die von der Funktion name() zurückgegebene Zeichenfolge implementierungsdefiniert?
– Destruktor
2. September 2015 um 12:41 Uhr
@PravasiMeet Kein guter Grund, soweit ich weiß. Das Komitee wollte Compiler-Implementierer einfach nicht in bestimmte technische Richtungen zwingen – im Nachhinein wahrscheinlich ein Fehler.
– Konrad Rudolf
2. September 2015 um 14:52 Uhr
Gibt es ein Flag, mit dem ich RTTI aktivieren könnte? Vielleicht könnten Sie Ihre Antwort inklusive machen.
– Jim
13. Januar 2016 um 16:22 Uhr
@Destructor Das Bereitstellen eines standardisierten Namensverstümmelungsformats könnte den Eindruck erwecken, dass die Interoperabilität zwischen Binärdateien, die von zwei verschiedenen Compilern erstellt wurden, möglich und/oder sicher ist, obwohl dies nicht der Fall ist. Da C++ keine Standard-ABI hat, wäre ein Standard-Namensverstümmelungsschema sinnlos und möglicherweise irreführend und gefährlich.
– Elkvis
5. Mai 2016 um 15:52 Uhr
@Jim Der Abschnitt über Compiler-Flags wäre eine Größenordnung länger als die Antwort selbst. GCC wird standardmäßig damit kompiliert, daher “-fno-rtti”, andere Compiler können sich dagegen entscheiden, aber es gibt keinen Standard für Compiler-Flags.
– kfsone
3. Oktober 2016 um 22:42 Uhr
NickV
Sehr hässlich, aber funktioniert, wenn Sie nur Informationen zur Kompilierzeit wünschen (z. B. zum Debuggen):
auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo= 1;
Kehrt zurück:
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*>'
nur C++ könnte dies so schwierig machen (Drucken eines Auto-Variablentyps zur Kompilierzeit). NUR C++.
– Karl Picket
18. Januar 2017 um 20:04 Uhr
@KarlP na ja um fair zu sein es ist ein bisschen kompliziert, das funktioniert auch 🙂 auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
– NickV
1. März 2017 um 13:59 Uhr
Unter VC++17 reduziert dies eine rvalue-Referenz auf eine einfache Referenz, selbst in einer Template-Funktion mit forwarding-reference-Parameter und dem Objektnamen, der in std::forward eingeschlossen ist.
– Jive Dadson
4. November 2017 um 17:45 Uhr
Sie konnten zu dem Typ gelangen, ohne neue Räder zu erstellen!
– Steven Eckhoff
29. August 2018 um 15:08 Uhr
Diese Technik wird auch in „Thema 4: Wissen, wie man abgeleitete Typen anzeigt“ in Effective Modern C++ beschrieben
– Lenkit
7. Juni 2019 um 16:57 Uhr
康桓瑋
Wenn Ihnen die magische Zahl gemäß Howards Lösung nicht gefällt, ist dies meiner Meinung nach eine gute Darstellungsart und sieht intuitiv aus:
#include <string_view>
template <typename T>
constexpr auto type_name() {
std::string_view name, prefix, suffix;
#ifdef __clang__
name = __PRETTY_FUNCTION__;
prefix = "auto type_name() [T = ";
suffix = "]";
#elif defined(__GNUC__)
name = __PRETTY_FUNCTION__;
prefix = "constexpr auto type_name() [with T = ";
suffix = "]";
#elif defined(_MSC_VER)
name = __FUNCSIG__;
prefix = "auto __cdecl type_name<";
suffix = ">(void)";
#endif
name.remove_prefix(prefix.size());
name.remove_suffix(suffix.size());
return name;
}
mdez
Einschließen nicht vergessen <typeinfo>
Ich glaube, Sie beziehen sich auf die Identifizierung des Laufzeittyps. Sie können das oben Gesagte erreichen, indem Sie Folgendes tun.
#include <iostream>
#include <typeinfo>
using namespace std;
int main() {
int i;
cout << typeid(i).name();
return 0;
}
paercebal
Beachten Sie, dass die Namen von der RTTI-Funktion von C++ generiert werden nicht tragbar. Zum Beispiel die Klasse
MyNamespace::CMyContainer<int, test_MyNamespace::CMyObject>
wird folgende Namen haben:
// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE
Sie können diese Informationen also nicht für die Serialisierung verwenden. Dennoch kann die Eigenschaft typeid(a).name() weiterhin für Protokoll-/Debug-Zwecke verwendet werden
Nick
Sie können Vorlagen verwenden.
template <typename T> const char* typeof(T&) { return "unknown"; } // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(float&) { return "float"; }
Wenn im obigen Beispiel der Typ nicht übereinstimmt, wird “unbekannt” ausgegeben.
Wird es nicht “int” für Shorts und Chars drucken? Und “Float” für Doppel?
– Gartenriese
17. Februar 2014 um 8:00 Uhr
@gartenriese Spezialisierung hat diesen Nachteil nicht. Zum double
Es würde die nicht spezialisierte Version der Vorlagenfunktion kompilieren, anstatt eine implizite Typkonvertierung durchzuführen, um die Spezialisierung zu verwenden: cpp.sh/2wzc
– chappjc
4. März 2015 um 18:25 Uhr
@chappjc: Ich weiß ehrlich gesagt nicht, warum ich das damals gefragt habe, jetzt ist es mir ziemlich klar. Aber danke, dass du trotzdem eine ein Jahr alte Frage beantwortet hast!
– Gartenriese
5. März 2015 um 8:25 Uhr
@gartenriese Das habe ich mir schon gedacht, aber “das Internet” könnte irgendwann die gleiche Frage haben.
– chappjc
5. März 2015 um 8:28 Uhr
Hier ist eine Zusammenfassung von Howards Langformlösung, die jedoch mit einem ketzerischen einzeiligen Makro implementiert wurde:
#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
um ein Makro für andere Plattformen wie MSVC bereitzustellen.– Trevor Boyd Smith
5. Juli 2016 um 15:01 Uhr
Mit expliziterer menschenlesbarer Anforderung: stackoverflow.com/questions/12877521/…
– Ciro Santilli Путлер Капут 六四事
22. Juli 2016 um 22:05 Uhr
Wenn Sie dies nur zum Debuggen verwenden, sollten Sie dies in Betracht ziehen
template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }
. Dann mit zBprint_T<const int * const **>();
wird druckenvoid print_T() [T = const int *const **]
zur Laufzeit und behält alle Qualifizierer bei (funktioniert in GCC und Clang).– Henri Menke
29. Mai 2017 um 6:01 Uhr
@Henri,
__PRETTY_FUNCTION__
ist kein Standard-C++ (Anforderung steht im Titel der Frage).– Toby Speight
13. Februar 2020 um 9:44 Uhr