Ist es möglich, den Variablentyp in Standard-C ++ zu drucken?

393

Zum Beispiel:

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

Erwartete Ausgabe:

int
Jorge Ferreira
quelle
2
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, #endifein Makros für andere Plattformen wie MSVC zu bieten.
Trevor Boyd Smith
Mit expliziterer, von Menschen lesbarer Anforderung: stackoverflow.com/questions/12877521/…
Ciro Santilli 法轮功 冠状 病 六四 事件 22
3
Wenn Sie dies nur zum Debuggen verwenden, sollten Sie dies berücksichtigen template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }. Wenn Sie dann eg verwenden, print_T<const int * const **>();wird void print_T() [T = const int *const **]zur Laufzeit gedruckt und alle Qualifikationsmerkmale werden beibehalten (funktioniert in GCC und Clang).
Henri Menke
@ Henri, __PRETTY_FUNCTION__ist nicht Standard C ++ (Voraussetzung ist im Fragentitel ).
Toby Speight

Antworten:

505

C ++ 11 Update auf eine sehr alte Frage: Druckvariablentyp in C ++.

Die akzeptierte (und gut) Antwort ist die Verwendung typeid(a).name(), wo aein 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 constist 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_demanglefü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 decltypelWert-Ausdrücke mit lWert-Referenzen dar).

Somit ist dieses Tool ein hervorragendes Mittel, um etwas zu lernen decltypeund 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:

  1. Es ist zur Kompilierungszeit!
  2. 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, constexprwenn 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_viewersetzen 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.

Howard Hinnant
quelle
4
VS 14 CTP druckte die richtigen Typen aus, ich musste nur eine #include <iostream>Zeile hinzufügen .
Max Galkin
3
Warum Vorlage <Typname T> std :: string Typname ()? Warum übergeben Sie keinen Typ als Argument?
Moonman239
2
Ich glaube, meine Begründung war, dass ich manchmal nur einen Typ hatte (wie einen abgeleiteten Vorlagenparameter), und ich wollte keinen davon künstlich konstruieren müssen, um den Typ zu erhalten (obwohl diese Tage declvalden Job machen würden).
Howard Hinnant
5
@AngelusMortis: Da Englisch im Vergleich zu C ++ - Code vage / mehrdeutig ist, empfehle ich Ihnen, dies mit dem spezifischen Typ, an dem Sie interessiert sind, und mit dem spezifischen Compiler, an dem Sie interessiert sind, zu kopieren / in Ihren Testfall einzufügen und mit mehr zurückzuschreiben Details, wenn das Ergebnis überraschend und / oder unbefriedigend ist.
Howard Hinnant
3
@ HowardHinnant können Sie std::string_viewanstelle von verwenden static_string?
Lyberta
231

Versuchen:

#include <typeinfo>

// …
std::cout << typeid(a).name() << '\n';

Möglicherweise müssen Sie RTTI in Ihren Compileroptionen aktivieren, damit dies funktioniert. Darüber hinaus hängt die Ausgabe davon vom Compiler ab. Es kann sich um einen rohen Typnamen oder ein Namensmangel-Symbol oder etwas dazwischen handeln.

Konrad Rudolph
quelle
4
Warum ist die von der Funktion name () zurückgegebene Zeichenfolge für die Implementierung definiert?
Destruktor
4
@PravasiMeet Soweit ich weiß, kein guter Grund. Das Komitee wollte Compiler-Implementierer einfach nicht in bestimmte technische Richtungen zwingen - im Nachhinein wahrscheinlich ein Fehler.
Konrad Rudolph
2
Gibt es ein Flag, mit dem ich RTTI aktivieren könnte? Vielleicht könnten Sie Ihre Antwort inklusive machen.
Jim
4
@Destructor Die Bereitstellung eines standardisierten Namensveränderungsformats kann den Eindruck erwecken, dass die Interoperabilität zwischen Binärdateien, die von zwei verschiedenen Compilern erstellt wurden, möglich und / oder sicher ist, wenn dies nicht der Fall ist. Da C ++ keinen Standard-ABI hat, wäre ein Standard-Namensmangelschema sinnlos und möglicherweise irreführend und gefährlich.
Elkvis
1
@ Jim Der Abschnitt über Compiler-Flags wäre eine Größenordnung länger als die Antwort selbst. GCC kompiliert standardmäßig mit dieser Option, daher "-fno-rtti". Andere Compiler entscheiden sich möglicherweise dagegen, aber es gibt keinen Standard für Compiler-Flags.
KFSONE
82

Sehr hässlich, macht aber den Trick, wenn Sie nur Informationen zur Kompilierungszeit benötigen (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*>'
NickV
quelle
2
Nur C ++ könnte dies so schwierig machen (Drucken eines automatischen Variablentyps zur Kompilierungszeit). NUR C ++.
Karl Pickett
3
@KarlP Nun, um fair zu sein, es ist ein wenig verworren, das funktioniert auch :) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
NickV
Unter VC ++ 17 reduziert dies eine rWert-Referenz auf eine einfache Referenz, selbst in einer Vorlagenfunktion mit Weiterleitungsreferenzparameter, und den in std :: forward eingeschlossenen Objektnamen.
Jive Dadson
Sie konnten zum Typ gelangen, ohne neue Räder zu erstellen!
Steven Eckhoff
1
Diese Technik wird auch in "Punkt 4: Wissen, wie abgeleitete Typen
angezeigt
54

Vergessen Sie nicht einzuschließen <typeinfo>

Ich glaube, Sie beziehen sich auf die Identifizierung des Laufzeit-Typs. Sie können das oben genannte erreichen, indem Sie dies tun.

#include <iostream>
#include <typeinfo>

using namespace std;

int main() {
  int i;
  cout << typeid(i).name();
  return 0;
}
mdec
quelle
36

Laut Howards Lösung ist dies eine gute Art der Darstellung und sieht intuitiv aus, wenn Sie die magische Zahl nicht wollen:

#include <string_view>

template <typename T>
constexpr std::string_view 
type_name()
{
    std::string_view name, prefix, suffix;
#ifdef __clang__
    name = __PRETTY_FUNCTION__;
    prefix = "std::string_view type_name() [T = ";
    suffix = "]";
#elif defined(__GNUC__)
    name = __PRETTY_FUNCTION__;
    prefix = "constexpr std::string_view type_name() [with T = ";
    suffix = "; std::string_view = std::basic_string_view<char>]";
#elif defined(_MSC_VER)
    name = __FUNCSIG__;
    prefix = "class std::basic_string_view<char,struct std::char_traits<char> > __cdecl type_name<";
    suffix = ">(void)";
#endif
    name.remove_prefix(prefix.size());
    name.remove_suffix(suffix.size());
    return name;
}
康 桓 瑋
quelle
4
Dies ist eine großartige Zusammenfassung der Bemühungen der letzten C ++ - Versionen zu etwas Kurzem und Süßem. +1.
Einpoklum
1
Das ist auch mein Favorit!
Howard Hinnant
1
Hier eine ähnliche Funktion, die ich verwende und die das Suffix / Präfix automatisch erkennt: stackoverflow.com/questions/1055452/…
HolyBlackCat
22

Beachten Sie, dass die von der RTTI-Funktion von C ++ generierten Namen nicht portierbar sind. Zum Beispiel die Klasse

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

wird 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. Die Eigenschaft typeid (a) .name () kann jedoch weiterhin für Protokoll- / Debugzwecke verwendet werden

paercebal
quelle
19

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 der Typ im obigen Beispiel nicht übereinstimmt, wird "unbekannt" ausgegeben.

Nick
quelle
3
Wird nicht "int" für Shorts und Zeichen gedruckt? Und "Float" für Doppel?
gartenriese
1
@gartenriese Spezialisierung hat diesen Nachteil nicht. Denn doublees würde die nicht spezialisierte Version der Vorlagenfunktion kompilieren, anstatt eine implizite Typkonvertierung
durchzuführen
1
@chappjc: Ich weiß ehrlich gesagt nicht, warum ich das damals gefragt habe, es ist mir jetzt ziemlich klar. Aber danke, dass du trotzdem eine einjährige Frage beantwortet hast!
gartenriese
2
@gartenriese Ich dachte mir das auch, aber "das Internet" könnte irgendwann die gleiche Frage haben.
Chappjc
18

Wie bereits erwähnt, typeid().name()kann ein verstümmelter Name zurückgegeben werden. 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;

}}

ipapadop
quelle
10

Sie können hierfür eine Merkmalsklasse verwenden. Etwas wie:

#include <iostream>
using namespace std;

template <typename T> class type_name {
public:
    static const char *name;
};

#define DECLARE_TYPE_NAME(x) template<> const char *type_name<x>::name = #x;
#define GET_TYPE_NAME(x) (type_name<typeof(x)>::name)

DECLARE_TYPE_NAME(int);

int main()
{
    int a = 12;
    cout << GET_TYPE_NAME(a) << endl;
}

Das DECLARE_TYPE_NAME Definition dient dazu, Ihnen das Deklarieren dieser Merkmalsklasse für alle Typen zu erleichtern, die Sie voraussichtlich benötigen.

Dies ist möglicherweise nützlicher als die Lösungen, typeidda Sie die Ausgabe steuern können. Wenn Sie beispielsweise typeidfür long longmeinen Compiler verwenden, erhalten Sie "x".

Greg Hewgill
quelle
10

In C ++ 11 haben wir decltype. In Standard-C ++ gibt es keine Möglichkeit, den genauen Variablentyp anzuzeigen, der mit decltype deklariert wurde. Wir können den Boost-Typeindex verwenden type_id_with_cvr(cvr steht für const, volatile, reference), um den Typ wie unten zu drucken.

#include <iostream>
#include <boost/type_index.hpp>

using namespace std;
using boost::typeindex::type_id_with_cvr;

int main() {
  int i = 0;
  const int ci = 0;
  cout << "decltype(i) is " << type_id_with_cvr<decltype(i)>().pretty_name() << '\n';
  cout << "decltype((i)) is " << type_id_with_cvr<decltype((i))>().pretty_name() << '\n';
  cout << "decltype(ci) is " << type_id_with_cvr<decltype(ci)>().pretty_name() << '\n';
  cout << "decltype((ci)) is " << type_id_with_cvr<decltype((ci))>().pretty_name() << '\n';
  cout << "decltype(std::move(i)) is " << type_id_with_cvr<decltype(std::move(i))>().pretty_name() << '\n';
  cout << "decltype(std::static_cast<int&&>(i)) is " << type_id_with_cvr<decltype(static_cast<int&&>(i))>().pretty_name() << '\n';
  return 0;
}
abodeofcode
quelle
1
template<typename T> void print_type(T){cout << "type T is: "<< type_id_with_cvr<T>().pretty_name()<< '\n';}
Wäre
6

Sie können auch c ++ filt mit der Option -t (Typ) verwenden, um den Typnamen zu entwirren:

#include <iostream>
#include <typeinfo>
#include <string>

using namespace std;

int main() {
  auto x = 1;
  string my_type = typeid(x).name();
  system(("echo " + my_type + " | c++filt -t").c_str());
  return 0;
}

Nur unter Linux getestet.

Alan
quelle
1
Hölle hässlich, aber wird für das tun, was ich brauche. Und viel kleiner als die anderen Lösungen. Funktioniert übrigens auf dem Mac.
Marco Luglio
6

Howard Hinnant verwendete magische Zahlen, um den Typnamen zu extrahieren. 康 桓 瑋 schlug ein Zeichenfolgenpräfix und -suffix vor. 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 <iostream>
#include <string_view>

using namespace std;

class probe_type;

template <typename T>
constexpr string_view type_name() {
  string_view probe_type_name("class probe_type");
  const string_view class_specifier("class");

  string_view name;
#ifdef __clang__
  name = __PRETTY_FUNCTION__;
  probe_type_name.remove_prefix(class_specifier.length());
#elif defined(__GNUC__)
  name = __PRETTY_FUNCTION__;
  probe_type_name.remove_prefix(class_specifier.length());
#elif defined(_MSC_VER)
  name = __FUNCSIG__;
#endif

  if (name.find(probe_type_name) != string_view::npos)
    return name;

  const string_view probe_type_raw_name = type_name<probe_type>();

  const size_t prefix_size = probe_type_raw_name.find(probe_type_name);

  name.remove_prefix(prefix_size);
  name.remove_suffix(probe_type_raw_name.length() - prefix_size - probe_type_name.length());

  return name;
}

class test;

int main() {
  cout << type_name<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.0.0 20190919 Wandbox:

 test
 const int *&
 unsigned int
 const int
 const int *
 const int *&
 constexpr std::string_view type_name() [with T = probe_type; std::string_view = std::basic_string_view<char>]

clang 10.0.0 Wandbox:

 test
 const int *&
 unsigned int
 const int
 const int *
 const int *&
 std::__1::string_view type_name() [T = probe_type]

VS 2019 Version 16.3.3:

class test
const int*&
unsigned int
const int
const int*
const int*&
class std::basic_string_view<char,struct std::char_traits<char> > __cdecl type_name<class probe_type>(void)
Val
quelle
5

Die anderen Antworten mit RTTI (typeid) sind wahrscheinlich genau das, was Sie wollen, solange:

  • Sie können sich den Speicheraufwand leisten (der bei einigen Compilern beträchtlich sein kann).
  • Die Klassennamen, die Ihr Compiler zurückgibt, sind nützlich

Die Alternative (ähnlich wie bei Greg Hewgills Antwort) besteht darin, eine Tabelle mit Merkmalen zur Kompilierungszeit zu erstellen.

template <typename T> struct type_as_string;

// declare your Wibble type (probably with definition of Wibble)
template <>
struct type_as_string<Wibble>
{
    static const char* const value = "Wibble";
};

Beachten Sie, dass Sie beim Umschließen der Deklarationen in ein Makro aufgrund des Kommas Probleme haben, Namen für Vorlagentypen zu deklarieren, die mehr als einen Parameter (z. B. std :: map) verwenden.

Um auf den Namen des Variablentyps zuzugreifen, benötigen Sie lediglich

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}
James Hopkin
quelle
1
Guter Punkt zum Komma, ich wusste, dass es einen Grund gibt, warum Makros eine schlechte Idee sind, aber ich habe damals nicht daran gedacht!
Greg Hewgill
2
statische const char * value = "Wibble"; du kannst diesen Kumpel nicht machen :)
Johannes Schaub - litb
5

Eine allgemeinere Lösung ohne Funktionsüberlastung als meine vorherige:

template<typename T>
std::string TypeOf(T){
    std::string Type="unknown";
    if(std::is_same<T,int>::value) Type="int";
    if(std::is_same<T,std::string>::value) Type="String";
    if(std::is_same<T,MyClass>::value) Type="MyClass";

    return Type;}

Hier ist MyClass eine benutzerdefinierte Klasse. Weitere Bedingungen können auch hier hinzugefügt werden.

Beispiel:

#include <iostream>



class MyClass{};


template<typename T>
std::string TypeOf(T){
    std::string Type="unknown";
    if(std::is_same<T,int>::value) Type="int";
    if(std::is_same<T,std::string>::value) Type="String";
    if(std::is_same<T,MyClass>::value) Type="MyClass";
    return Type;}


int main(){;
    int a=0;
    std::string s="";
    MyClass my;
    std::cout<<TypeOf(a)<<std::endl;
    std::cout<<TypeOf(s)<<std::endl;
    std::cout<<TypeOf(my)<<std::endl;

    return 0;}

Ausgabe:

int
String
MyClass
Jahid
quelle
5

Ich mag Nicks Methode. Ein vollständiges Formular könnte dies sein (für alle grundlegenden Datentypen):

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(short&) { return "short"; }
template<> const char* typeof(long&) { return "long"; }
template<> const char* typeof(unsigned&) { return "unsigned"; }
template<> const char* typeof(unsigned short&) { return "unsigned short"; }
template<> const char* typeof(unsigned long&) { return "unsigned long"; }
template<> const char* typeof(float&) { return "float"; }
template<> const char* typeof(double&) { return "double"; }
template<> const char* typeof(long double&) { return "long double"; }
template<> const char* typeof(std::string&) { return "String"; }
template<> const char* typeof(char&) { return "char"; }
template<> const char* typeof(signed char&) { return "signed char"; }
template<> const char* typeof(unsigned char&) { return "unsigned char"; }
template<> const char* typeof(char*&) { return "char*"; }
template<> const char* typeof(signed char*&) { return "signed char*"; }
template<> const char* typeof(unsigned char*&) { return "unsigned char*"; }
Jahid
quelle
2
(i) es funktioniert nicht für andere Typen (dh überhaupt nicht generisch); (ii) nutzloses Aufblähen von Code; (iii) das gleiche kann (richtig) mit typeidoder gemacht werden decltype.
Edmz
2
Sie haben Recht, aber es deckt alle Grundtypen ab ... und das ist es, was ich gerade brauche ...
Jahid
2
Können Sie mir sagen, wie würden Sie es mit decltype tun,
Jahid
1
Wenn es sich um einen Test zur Kompilierungszeit handelt, können Sie std :: is_same <T, S> und decltype verwenden, um T und S zu erhalten.
edmz
4

Als ich mich herausforderte, beschloss ich zu testen, wie weit man mit plattformunabhängigen (hoffentlich) Template-Tricks gehen kann.

Die Namen werden zur Kompilierungszeit vollständig zusammengestellt. (Was bedeutet, typeid(T).name()dass nicht verwendet werden konnte, daher müssen Sie explizit Namen für nicht zusammengesetzte Typen angeben. Andernfalls werden stattdessen Platzhalter angezeigt.)

Anwendungsbeispiel:

TYPE_NAME(int)
TYPE_NAME(void)
// You probably should list all primitive types here.

TYPE_NAME(std::string)

int main()
{
    // A simple case
    std::cout << type_name<void(*)(int)> << '\n';
    // -> `void (*)(int)`

    // Ugly mess case
    // Note that compiler removes cv-qualifiers from parameters and replaces arrays with pointers.
    std::cout << type_name<void (std::string::*(int[3],const int, void (*)(std::string)))(volatile int*const*)> << '\n';
    // -> `void (std::string::*(int *,int,void (*)(std::string)))(volatile int *const*)`

    // A case with undefined types
    //  If a type wasn't TYPE_NAME'd, it's replaced by a placeholder, one of `class?`, `union?`, `enum?` or `??`.
    std::cout << type_name<std::ostream (*)(int, short)> << '\n';
    // -> `class? (*)(int,??)`
    // With appropriate TYPE_NAME's, the output would be `std::string (*)(int,short)`.
}

Code:

#include <type_traits>
#include <utility>

static constexpr std::size_t max_str_lit_len = 256;

template <std::size_t I, std::size_t N> constexpr char sl_at(const char (&str)[N])
{
    if constexpr(I < N)
        return str[I];
    else
        return '\0';
}

constexpr std::size_t sl_len(const char *str)
{
    for (std::size_t i = 0; i < max_str_lit_len; i++)
        if (str[i] == '\0')
            return i;
    return 0;
}

template <char ...C> struct str_lit
{
    static constexpr char value[] {C..., '\0'};
    static constexpr int size = sl_len(value);

    template <typename F, typename ...P> struct concat_impl {using type = typename concat_impl<F>::type::template concat_impl<P...>::type;};
    template <char ...CC> struct concat_impl<str_lit<CC...>> {using type = str_lit<C..., CC...>;};
    template <typename ...P> using concat = typename concat_impl<P...>::type;
};

template <typename, const char *> struct trim_str_lit_impl;
template <std::size_t ...I, const char *S> struct trim_str_lit_impl<std::index_sequence<I...>, S>
{
    using type = str_lit<S[I]...>;
};
template <std::size_t N, const char *S> using trim_str_lit = typename trim_str_lit_impl<std::make_index_sequence<N>, S>::type;

#define STR_LIT(str) ::trim_str_lit<::sl_len(str), ::str_lit<STR_TO_VA(str)>::value>
#define STR_TO_VA(str) STR_TO_VA_16(str,0),STR_TO_VA_16(str,16),STR_TO_VA_16(str,32),STR_TO_VA_16(str,48)
#define STR_TO_VA_16(str,off) STR_TO_VA_4(str,0+off),STR_TO_VA_4(str,4+off),STR_TO_VA_4(str,8+off),STR_TO_VA_4(str,12+off)
#define STR_TO_VA_4(str,off) ::sl_at<off+0>(str),::sl_at<off+1>(str),::sl_at<off+2>(str),::sl_at<off+3>(str)

template <char ...C> constexpr str_lit<C...> make_str_lit(str_lit<C...>) {return {};}
template <std::size_t N> constexpr auto make_str_lit(const char (&str)[N])
{
    return trim_str_lit<sl_len((const char (&)[N])str), str>{};
}

template <std::size_t A, std::size_t B> struct cexpr_pow {static constexpr std::size_t value = A * cexpr_pow<A,B-1>::value;};
template <std::size_t A> struct cexpr_pow<A,0> {static constexpr std::size_t value = 1;};
template <std::size_t N, std::size_t X, typename = std::make_index_sequence<X>> struct num_to_str_lit_impl;
template <std::size_t N, std::size_t X, std::size_t ...Seq> struct num_to_str_lit_impl<N, X, std::index_sequence<Seq...>>
{
    static constexpr auto func()
    {
        if constexpr (N >= cexpr_pow<10,X>::value)
            return num_to_str_lit_impl<N, X+1>::func();
        else
            return str_lit<(N / cexpr_pow<10,X-1-Seq>::value % 10 + '0')...>{};
    }
};
template <std::size_t N> using num_to_str_lit = decltype(num_to_str_lit_impl<N,1>::func());


using spa = str_lit<' '>;
using lpa = str_lit<'('>;
using rpa = str_lit<')'>;
using lbr = str_lit<'['>;
using rbr = str_lit<']'>;
using ast = str_lit<'*'>;
using amp = str_lit<'&'>;
using con = str_lit<'c','o','n','s','t'>;
using vol = str_lit<'v','o','l','a','t','i','l','e'>;
using con_vol = con::concat<spa, vol>;
using nsp = str_lit<':',':'>;
using com = str_lit<','>;
using unk = str_lit<'?','?'>;

using c_cla = str_lit<'c','l','a','s','s','?'>;
using c_uni = str_lit<'u','n','i','o','n','?'>;
using c_enu = str_lit<'e','n','u','m','?'>;

template <typename T> inline constexpr bool ptr_or_ref = std::is_pointer_v<T> || std::is_reference_v<T> || std::is_member_pointer_v<T>;
template <typename T> inline constexpr bool func_or_arr = std::is_function_v<T> || std::is_array_v<T>;

template <typename T> struct primitive_type_name {using value = unk;};

template <typename T, typename = std::enable_if_t<std::is_class_v<T>>> using enable_if_class = T;
template <typename T, typename = std::enable_if_t<std::is_union_v<T>>> using enable_if_union = T;
template <typename T, typename = std::enable_if_t<std::is_enum_v <T>>> using enable_if_enum  = T;
template <typename T> struct primitive_type_name<enable_if_class<T>> {using value = c_cla;};
template <typename T> struct primitive_type_name<enable_if_union<T>> {using value = c_uni;};
template <typename T> struct primitive_type_name<enable_if_enum <T>> {using value = c_enu;};

template <typename T> struct type_name_impl;

template <typename T> using type_name_lit = std::conditional_t<std::is_same_v<typename primitive_type_name<T>::value::template concat<spa>,
                                                                               typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>,
                                            typename primitive_type_name<T>::value,
                                            typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>;
template <typename T> inline constexpr const char *type_name = type_name_lit<T>::value;

template <typename T, typename = std::enable_if_t<!std::is_const_v<T> && !std::is_volatile_v<T>>> using enable_if_no_cv = T;

template <typename T> struct type_name_impl
{
    using l = typename primitive_type_name<T>::value::template concat<spa>;
    using r = str_lit<>;
};
template <typename T> struct type_name_impl<const T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con>,
                                 con::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<vol>,
                                 vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<const volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con_vol>,
                                 con_vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<T *>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, ast>,
                                 typename type_name_impl<T>::l::template concat<     ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &&>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp, amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T, typename C> struct type_name_impl<T C::*>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, type_name_lit<C>, nsp, ast>,
                                 typename type_name_impl<T>::l::template concat<     type_name_lit<C>, nsp, ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<enable_if_no_cv<T[]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<rbr, typename type_name_impl<T>::r>;
};
template <typename T, std::size_t N> struct type_name_impl<enable_if_no_cv<T[N]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<num_to_str_lit<N>, rbr, typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T()>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<rpa, typename type_name_impl<T>::r>;
};
template <typename T, typename P1, typename ...P> struct type_name_impl<T(P1, P...)>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<type_name_lit<P1>,
                          com::concat<type_name_lit<P>>..., rpa, typename type_name_impl<T>::r>;
};

#define TYPE_NAME(t) template <> struct primitive_type_name<t> {using value = STR_LIT(#t);};
HolyBlackCat
quelle
2
#include <iostream>
#include <typeinfo>
using namespace std;
#define show_type_name(_t) \
    system(("echo " + string(typeid(_t).name()) + " | c++filt -t").c_str())

int main() {
    auto a = {"one", "two", "three"};
    cout << "Type of a: " << typeid(a).name() << endl;
    cout << "Real type of a:\n";
    show_type_name(a);
    for (auto s : a) {
        if (string(s) == "one") {
            cout << "Type of s: " << typeid(s).name() << endl;
            cout << "Real type of s:\n";
            show_type_name(s);
        }
        cout << s << endl;
    }

    int i = 5;
    cout << "Type of i: " << typeid(i).name() << endl;
    cout << "Real type of i:\n";
    show_type_name(i);
    return 0;
}

Ausgabe:

Type of a: St16initializer_listIPKcE
Real type of a:
std::initializer_list<char const*>
Type of s: PKc
Real type of s:
char const*
one
two
three
Type of i: i
Real type of i:
int
Grauer Wolf
quelle
2

Wie von Scott Meyers in Effective Modern C ++ erklärt,

Anrufe an std::type_info::namewerden nicht garantiert vernünftig zurückgegeben.

Die beste Lösung besteht darin, den Compiler während des Typabzugs eine Fehlermeldung generieren zu lassen, z.

template<typename T>
class TD;

int main(){
    const int theAnswer = 32;
    auto x = theAnswer;
    auto y = &theAnswer;
    TD<decltype(x)> xType;
    TD<decltype(y)> yType;
    return 0;
}

Das Ergebnis wird ungefähr so ​​aussehen, abhängig von den Compilern.

test4.cpp:10:21: error: aggregate TD<int> xType has incomplete type and cannot be defined TD<decltype(x)> xType;

test4.cpp:11:21: error: aggregate TD<const int *> yType has incomplete type and cannot be defined TD<decltype(y)> yType;

Daher erfahren wir, dass xder Typ ist int, yder Typ istconst int*

Milo Lu
quelle
0

Für alle, die noch zu Besuch sind, hatte ich kürzlich das gleiche Problem und habe beschlossen, eine kleine Bibliothek zu schreiben, die auf den Antworten aus diesem Beitrag basiert. Es enthält constexpr-Typnamen und Typindizes und wird unter Mac, Windows und Ubuntu getestet.

Der Bibliothekscode ist hier: https://github.com/TheLartians/StaticTypeInfo

Lars Melchior
quelle