629 Stimmen

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

Zum Beispiel:

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

Erwartetes Ergebnis:

int

3 Stimmen

Hier ist eine Zusammenfassung von Howards Langformlösung, die jedoch mit einem ketzerischen einzeiligen Makro umgesetzt 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.

0 Stimmen

Mit expliziteren, für Menschen lesbaren Anforderungen: stackoverflow.com/questions/12877521/

3 Stimmen

Wenn Sie dies nur zum Debuggen verwenden, sollten Sie Folgendes in Betracht ziehen template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; } . Dann verwenden Sie z.B. print_T<const int * const **>(); wird gedruckt void print_T() [T = const int *const **] zur Laufzeit und bewahrt alle Qualifier (funktioniert in GCC und Clang).

27voto

paercebal Punkte 78198

Beachten Sie, dass die von der RTTI-Funktion von C++ erzeugten Namen no tragbar. Zum Beispiel kann die Klasse

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

werden die folgenden 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() für Protokoll-/Debug-Zwecke verwendet werden

26voto

ipapadop Punkte 1412

Wie bereits erwähnt, typeid().name() kann einen verstümmelten Namen zurückgeben. In GCC (und einigen anderen Compilern) können Sie dies mit dem folgenden Code umgehen:

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

}

24voto

Nick Punkte 26768

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

Im obigen Beispiel wird, wenn der Typ nicht übereinstimmt, "unbekannt" gedruckt.

5 Stimmen

Wird nicht "int" für Shorts und Chars gedruckt? Und "float" für Doubles?

2 Stimmen

@gartenriese Spezialisierung hat diesen Nachteil nicht. Für double würde die nicht spezialisierte Version der Vorlagenfunktion kompiliert, anstatt eine implizite Typkonvertierung durchzuführen, um die Spezialisierung zu verwenden: cpp.sh/2wzc

2 Stimmen

@chappjc: Ich weiß ehrlich gesagt nicht, warum ich das damals gefragt habe, jetzt ist es mir ziemlich klar. Aber trotzdem danke für die Antwort auf eine ein Jahr alte Frage!

22voto

einpoklum Punkte 100527

Eine andere Sichtweise auf <a href="https://stackoverflow.com/a/56766138">@'s Antwort </a>(ursprünglich ), wobei weniger Annahmen über die Besonderheiten der Präfixe und Suffixe gemacht werden, und inspiriert von <a href="https://stackoverflow.com/a/58331141/1593077">@Val's Antwort </a>- aber ohne den globalen Namensraum zu verschmutzen; ohne Bedingungen; und hoffentlich leichter zu lesen.

Die gängigen Compiler stellen ein Makro mit der Signatur der aktuellen Funktion bereit. Nun sind Funktionen schablonenfähig; die Signatur enthält also die Schablonenargumente. Der grundlegende Ansatz ist also: Geben Sie einen Typ an, seien Sie in einer Funktion mit diesem Typ als Vorlagenargument.

Leider ist der Typname in einen Text zur Beschreibung der Funktion verpackt, der von Compiler zu Compiler unterschiedlich ist. Mit GCC zum Beispiel ist die Signatur von template <typename T> int foo() mit Typ double ist: int foo() [T = double] .

Wie wird man also den Wrapper-Text los? Die Lösung von @HowardHinnant ist die kürzeste und "direkteste": Verwenden Sie einfach magische Zahlen pro Compiler, um ein Präfix und ein Suffix zu entfernen. Aber das ist natürlich sehr spröde; und niemand mag mag magische Zahlen in seinem Code. Es stellt sich jedoch heraus, dass man, wenn man den Makro-Wert für einen Typ mit bekanntem Namen hat, feststellen kann, welches Präfix und Suffix die Umhüllung bilden.

#include <string_view>

template <typename T> constexpr std::string_view type_name();

template <>
constexpr std::string_view type_name<void>()
{ return "void"; }

namespace detail {

using type_name_prober = void;

template <typename T>
constexpr std::string_view wrapped_type_name() 
{
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return __FUNCSIG__;
#else
#error "Unsupported compiler"
#endif
}

constexpr std::size_t wrapped_type_name_prefix_length() { 
    return wrapped_type_name<type_name_prober>().find(type_name<type_name_prober>()); 
}

constexpr std::size_t wrapped_type_name_suffix_length() { 
    return wrapped_type_name<type_name_prober>().length() 
        - wrapped_type_name_prefix_length() 
        - type_name<type_name_prober>().length();
}

} // namespace detail

template <typename T>
constexpr std::string_view type_name() {
    constexpr auto wrapped_name = detail::wrapped_type_name<T>();
    constexpr auto prefix_length = detail::wrapped_type_name_prefix_length();
    constexpr auto suffix_length = detail::wrapped_type_name_suffix_length();
    constexpr auto type_name_length = wrapped_name.length() - prefix_length - suffix_length;
    return wrapped_name.substr(prefix_length, type_name_length);
}

Siehe auf GodBolt . Dies sollte auch mit MSVC funktionieren.

0 Stimmen

Eine gute Entscheidung, Code mit Makros und Präfix/Suffix-Operationen zu isolieren.

1 Stimmen

@Val: Ich habe im Grunde nur versucht, dem "Prinzip des geringsten Erstaunens" zu folgen.

20voto

Val Punkte 161

Howard Hinnant magische Zahlen verwendet, um den Typnamen zu extrahieren. vorgeschlagenes Präfix und Suffix der Zeichenfolge. Aber Präfix/Suffix ändern sich ständig. Mit "probe_type" berechnet type_name automatisch die Präfix- und Suffixgrößen für "probe_type", um den Typnamen zu extrahieren:

#include <string_view>
using namespace std;

namespace typeName {
 template <typename T>
  constexpr string_view wrapped_type_name () {
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return  __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return  __FUNCSIG__;
#endif
  }

  class probe_type;
  constexpr string_view probe_type_name ("typeName::probe_type");
  constexpr string_view probe_type_name_elaborated ("class typeName::probe_type");
  constexpr string_view probe_type_name_used (wrapped_type_name<probe_type> ().find (probe_type_name_elaborated) != -1 ? probe_type_name_elaborated : probe_type_name);

  constexpr size_t prefix_size () {
    return wrapped_type_name<probe_type> ().find (probe_type_name_used);
  }

  constexpr size_t suffix_size () {
    return wrapped_type_name<probe_type> ().length () - prefix_size () - probe_type_name_used.length ();
  }

  template <typename T>
  string_view type_name () {
    constexpr auto type_name = wrapped_type_name<T> ();

    return type_name.substr (prefix_size (), type_name.length () - prefix_size () - suffix_size ());
  }
}

#include <iostream>

using typeName::type_name;
using typeName::probe_type;

class test;

int main () {
  cout << type_name<class 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;
}

Ausgabe

gcc 10.2 :

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type

clang 11.0.0 :

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type

VS 2019 Version 16.7.6:

class test
const int*&
unsigned int
const int
const int*
const int*&
class typeName::probe_type

1 Stimmen

Tolle Lösung! Dem class_specifier fehlt jedoch ein Leerzeichen am Ende, so dass die Größe des Präfixes falsch berechnet wird, indem sie ein Zeichen kleiner ist als sie sein sollte. Ich kann den Beitrag nicht selbst bearbeiten, da es sich um eine zu kleine Änderung handelt. Die Compilerausgaben sollten dann entsprechend geändert werden.

1 Stimmen

Eigentlich machen Sie eine Annahme noch qualitativ anders als @ : Die Annahme, dass für msvc gibt es ein extra Präfix von "class ", und für GCC und clang es nicht existiert. Wenn Präfixe und Suffixe sich ändern können, dann können auch que . Es ist also nicht klar, ob dies besser ist, als eine Annahme über die gesamte Vorwahl zu treffen.

0 Stimmen

... wie auch immer, würden Sie sich bitte meinen eigenen Lösungsvorschlag unten ansehen? Und mir sagen, ob ich einige zusätzliche Anpassung für MSVC benötigen?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X