Ist es möglich, den Typ einer Variablen in Standard-C++ zu drucken?

Lesezeit: 14 Minuten

Ist es moglich den Typ einer Variablen in Standard C zu
Jörg Ferreira

Zum Beispiel:

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

Erwartete Ausgabe:

int

  • 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 zB print_T<const int * const **>(); wird drucken void 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

Ist es moglich den Typ einer Variablen in Standard C zu
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 decltypezusä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:

  1. Es ist zur Kompilierzeit!
  2. Sie erhalten den Compiler selbst, um die Arbeit anstelle einer Bibliothek (sogar einer std::lib) zu erledigen. Dies bedeutet genauere Ergebnisse für die neuesten Sprachfeatures (wie Lambdas).

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.

Aktualisieren:

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 templatestd::string type_name()? Warum übergibst du keinen Typ als Argument?

    – 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

1647181216 690 Ist es moglich den Typ einer Variablen in Standard C zu
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

1647181217 270 Ist es moglich den Typ einer Variablen in Standard C zu
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

1647181218 998 Ist es moglich den Typ einer Variablen in Standard C zu
康桓瑋

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;
}

Demo.

1647181218 172 Ist es moglich den Typ einer Variablen in Standard C zu
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;
}

1647181219 430 Ist es moglich den Typ einer Variablen in Standard C zu
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

Ist es moglich den Typ einer Variablen in Standard C zu
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

998030cookie-checkIst es möglich, den Typ einer Variablen in Standard-C++ zu drucken?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy