Czy można wydrukować typ zmiennej w standardowym C ++?

393

Na przykład:

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

Oczekiwany wynik:

int
Jorge Ferreira
źródło
2
Oto podsumowanie roztworu długo postać Howarda, ale realizowane z heretyckiego jedna linia makro: #define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL). Jeśli potrzebujesz wsparcia cross-platform: Użyj #ifdef, #else, #endifw celu dostarczenia jednego makra dla innych platform, takich jak MSVC.
Trevor Boyd Smith
Z bardziej wyraźnym wymaganiem czytelnym dla człowieka: stackoverflow.com/questions/12877521/...
Ciro Santilli 22 冠状 病 六四 事件 法轮功
3
Jeśli używasz tego tylko do debugowania, możesz rozważyć template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }. Następnie użycie np. print_T<const int * const **>();Wydrukuje void print_T() [T = const int *const **]w czasie wykonywania i zachowa wszystkie kwalifikatory (działa w GCC i Clang).
Henri Menke
@Henri, __PRETTY_FUNCTION__nie jest Standardowym C ++ (wymaganie jest w tytule pytania).
Toby Speight

Odpowiedzi:

505

Aktualizacja C ++ 11 do bardzo starego pytania: wypisz typ zmiennej w C ++.

Zaakceptowaną (i dobrą) odpowiedzią jest użycie typeid(a).name(), gdzie ajest nazwą zmiennej.

Teraz w C ++ 11 mamy decltype(x), który może zmienić wyrażenie w typ. I decltype()ma własny zestaw bardzo interesujących zasad. Na przykład decltype(a)i na decltype((a))ogół będą to różne typy (oraz z dobrych i zrozumiałych powodów po ujawnieniu tych powodów).

Czy nasze zaufane rzeczy typeid(a).name()pomogą nam odkryć ten nowy wspaniały świat?

Nie.

Ale narzędzie, które nie będzie tak skomplikowane. I to jest to narzędzie, którego używam jako odpowiedź na to pytanie. Porównuję i skontrastuję to nowe narzędzie typeid(a).name(). A to nowe narzędzie jest w rzeczywistości zbudowane na bazie typeid(a).name().

Podstawowa kwestia:

typeid(a).name()

wyrzuca kwalifikatory cv, referencje i lvalue / rvalue-ness. Na przykład:

const int ci = 0;
std::cout << typeid(ci).name() << '\n';

Dla mnie wyniki:

i

i zgaduję na wyjściach MSVC:

int

Tj const. Zniknął. To nie jest kwestia QOI (Quality of Implementation). Norma nakazuje takie zachowanie.

To co polecam poniżej to:

template <typename T> std::string type_name();

który byłby użyty w ten sposób:

const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';

a dla mnie wyniki:

int const

<disclaimer>Nie testowałem tego na MSVC. </disclaimer> Ale cieszę się z opinii tych, którzy to robią.

Rozwiązanie C ++ 11

Korzystam __cxa_demanglez platform innych niż MSVC, jak zalecił ipapadop w swojej odpowiedzi na typy trójkątów . Ale w MSVC ufam, typeidże rozplątam nazwy (niesprawdzone). I ten rdzeń jest otoczony kilkoma prostymi testami, które wykrywają, przywracają i raportują kwalifikatory cv oraz odniesienia do typu danych wejściowych.

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

Wyniki

Dzięki temu rozwiązaniu mogę to zrobić:

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

a wynikiem jest:

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

Uwaga (na przykład) różnica między decltype(i)i decltype((i)). Były to rodzaj deklaracji o i. Ten ostatni jest „typem” wyrażenia i . (wyrażenia nigdy nie mają typu referencji, ale jako konwencja decltypereprezentują wyrażenia lvalue z referencjami lvalue).

Dlatego to narzędzie jest doskonałym narzędziem do nauki decltype, oprócz eksploracji i debugowania własnego kodu.

W przeciwieństwie do tego, gdybym to zbudował typeid(a).name(), bez dodawania utraconych kwalifikatorów cv lub referencji, wynik byłby następujący:

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

Tj. Każda referencja i kwalifikator cv są usuwane.

Aktualizacja C ++ 14

Właśnie wtedy, gdy myślisz, że masz rozwiązanie problemu, który został przybity, ktoś zawsze pojawia się znikąd i pokazuje ci znacznie lepszy sposób. :-)

Ta odpowiedź od Jamboree pokazuje jak uzyskać nazwę typu w C ++ 14 w czasie kompilacji. To genialne rozwiązanie z kilku powodów:

  1. Jest w czasie kompilacji!
  2. Dostajesz sam kompilator do wykonania zadania zamiast biblioteki (nawet std :: lib). Oznacza to dokładniejsze wyniki najnowszych funkcji językowych (takich jak lambdas).

Odpowiedź Jamboree nie wyjaśnia wszystkiego VS, a ja trochę poprawiam jego kod. Ale ponieważ ta odpowiedź zyskuje wiele wyświetleń, poświęć trochę czasu, aby tam głosować i głosować na jego odpowiedź, bez której aktualizacja nigdy by się nie wydarzyła.

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

Ten kod automatycznie się wycofuje, constexprjeśli nadal tkwisz w starożytnym C ++ 11. A jeśli malujesz na ścianie jaskini za pomocą C ++ 98/03, noexceptto również się poświęca.

Aktualizacja C ++ 17

W komentarzach poniżej Lyberta wskazuje, że nowy std::string_viewmoże zastąpić 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
}

Zaktualizowałem stałe dla VS dzięki bardzo miłej pracy detektywistycznej Jive Dadson w komentarzach poniżej.

Aktualizacja:

Pamiętaj, aby sprawdzić to przepisać poniżej który eliminuje nieczytelne numery magiczne w moim najnowszym preparacie.

Howard Hinnant
źródło
4
VS 14 CTP wydrukował prawidłowe typy, musiałem tylko dodać jedną #include <iostream>linię.
Max Galkin,
3
Dlaczego szablon <typename T> std :: string type_name ()? Dlaczego nie podajesz typu jako argumentu?
moonman239,
2
Wydaje mi się, że moim uzasadnieniem było to, że czasami miałem tylko typ (na przykład wydedukowany parametr szablonu) i nie chciałem sztucznie skonstruować jednego z nich, aby uzyskać ten typ (choć te dni declvalbyłyby wystarczające).
Howard Hinnant,
5
@AngelusMortis: Ponieważ angielski jest niejasny / dwuznaczny w porównaniu z kodem C ++, zachęcam do skopiowania / wklejenia go do testowego przypadku z typem, który Cię interesuje, i za pomocą konkretnego kompilatora, którym jesteś zainteresowany, i odpisania z większą ilością szczegóły, jeśli wynik jest zaskakujący i / lub niezadowalający.
Howard Hinnant
3
@HowardHinnant możesz użyć std::string_viewzamiast static_string?
Lyberta,
231

Próbować:

#include <typeinfo>

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

Aby to zadziałało, może być konieczne aktywowanie RTTI w opcjach kompilatora. Dodatkowo wynik tego zależy od kompilatora. Może to być nazwa typu raw, symbol zmieniający nazwę lub cokolwiek pomiędzy.

Konrad Rudolph
źródło
4
Dlaczego ciąg zwracany przez funkcję name () jest zdefiniowany w implementacji?
Destructor
4
@PravasiMeet O ile wiem, nie ma dobrego powodu. Komitet po prostu nie chciał zmusić implementatorów kompilatora do określonych kierunków technicznych - prawdopodobnie z perspektywy czasu to błąd.
Konrad Rudolph,
2
Czy istnieje flaga, której mogłabym użyć, aby włączyć RTTI? Może udzielisz odpowiedzi.
Jim
4
@Destructor Podanie znormalizowanego formatu zniekształcania nazw może sprawiać wrażenie, że interoperacyjność między binariami zbudowanymi przez dwa różne kompilatory jest możliwa i / lub bezpieczna, jeśli nie jest. Ponieważ C ++ nie ma standardowego ABI, standardowy schemat zmiany nazw byłby bezcelowy, potencjalnie wprowadzający w błąd i niebezpieczny.
Elkvis
1
@Jim Sekcja na temat flag kompilatora byłaby o rząd wielkości dłuższa niż sama odpowiedź. GCC kompiluje się z nim domyślnie, stąd „-fno-rtti”, inne kompilatory mogą tego nie robić, ale nie ma standardu dla flag kompilatora.
kfsone
82

Bardzo brzydka, ale robi to, jeśli potrzebujesz tylko informacji o czasie kompilacji (np. Do debugowania):

auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo= 1;

Zwroty:

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
źródło
2
tylko c ++ może to utrudnić (drukowanie typu zmiennych automatycznych w czasie kompilacji). TYLKO C ++.
Karl Pickett,
3
@KarlP dobrze, żeby być uczciwym, to jest trochę skomplikowane, to też działa :) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
NickV
W wersji VC ++ 17 redukuje to odwołanie do wartości do zwykłego odwołania, nawet w funkcji szablonu z parametrem forwarding-referen, a nazwa obiektu jest zawinięta w std :: forward.
Jive Dadson,
Udało ci się dotrzeć do tego typu bez tworzenia nowych kół!
Steven Eckhoff
1
Technikę tę opisano również w „Pozycji 4: Wiedzieć, jak wyświetlać typy dedukcyjne” w Effective Modern C ++
lenkite,
54

Nie zapomnij dołączyć <typeinfo>

Uważam, że masz na myśli identyfikację typu środowiska wykonawczego. Możesz osiągnąć powyższe, robiąc.

#include <iostream>
#include <typeinfo>

using namespace std;

int main() {
  int i;
  cout << typeid(i).name();
  return 0;
}
mdec
źródło
36

Według rozwiązania Howarda , jeśli nie chcesz magicznej liczby, myślę, że to dobry sposób na przedstawienie i wygląda intuicyjnie:

#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;
}
康 桓 瑋
źródło
4
Jest to świetna destylacja starań z kilku ostatnich wersji C ++ w coś, co jest krótkie i słodkie. +1.
einpoklum
1
To też jest moja ulubiona!
Howard Hinnant
1
Tutaj podobna funkcja, której używam, która automatycznie wykrywa przyrostek / prefiks: stackoverflow.com/questions/1055452/…
HolyBlackCat
22

Należy pamiętać, że nazwy generowane przez funkcję RTTI w C ++ nie są przenośne. Na przykład klasa

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

będzie miał następujące nazwy:

// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE

Nie możesz więc użyć tych informacji do serializacji. Ale nadal właściwość typeid (a) .name () może być nadal używana do celów dziennika / debugowania

paercebal
źródło
19

Możesz użyć szablonów.

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(float&) { return "float"; }

W powyższym przykładzie, gdy typ nie jest dopasowany, wypisze „nieznany”.

Nacięcie
źródło
3
Czy to nie będzie drukować „int” dla szortów i znaków? A „float” dla debla?
gartenriese
1
Specjalizacja @gartenriese nie ma tej wady. W doubletym celu skompilowałaby niespecjalistyczną wersję funkcji szablonu zamiast niejawnej konwersji typu w celu użycia specjalizacji: cpp.sh/2wzc
chappjc
1
@chappjc: Szczerze mówiąc, nie wiem, dlaczego o to prosiłem, teraz jest dla mnie całkiem jasne. Ale i tak dziękuję za odpowiedź na roczne pytanie!
gartenriese
2
@gartenriese Doszedłem do wniosku, ale „internet” może mieć to samo pytanie w pewnym momencie.
chappjc
18

Jak wspomniano, typeid().name()może zwrócić zniekształconą nazwę. W GCC (i niektórych innych kompilatorach) możesz obejść ten problem za pomocą następującego kodu:

#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
źródło
10

Możesz do tego użyć klasy cech. Coś jak:

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

The DECLARE_TYPE_NAMEDefine istnieje, aby ułatwić Ci życie, uznając tę klasę cech dla wszystkich typów można oczekiwać od potrzeb.

Może to być bardziej przydatne niż rozwiązania obejmujące, typeidponieważ można kontrolować dane wyjściowe. Na przykład użycie typeidfor long longw moim kompilatorze daje „x”.

Greg Hewgill
źródło
10

W C ++ 11 mamy deklarację. W standardowym c ++ nie ma sposobu, aby wyświetlić dokładny typ zmiennej zadeklarowanej za pomocą decltype. Możemy użyć boost typeindex tj. type_id_with_cvr(Cvr oznacza const, volatile, referencja) do drukowania typu jak poniżej.

#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
źródło
1
czy byłoby prościej użyć funkcji pomocniczej:template<typename T> void print_type(T){cout << "type T is: "<< type_id_with_cvr<T>().pretty_name()<< '\n';}
r0ng 18.10.17
6

Możesz także użyć c ++ filt z opcją -t (typ), aby odformować nazwę typu:

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

Testowane tylko na systemie Linux.

Alan
źródło
1
Piekło brzydkie, ale zrobi to, czego potrzebuję. I znacznie mniejszy niż inne rozwiązania. Działa na komputerze Mac btw.
Marco Luglio,
6

Howard Hinnant użył magicznych liczb do wyodrębnienia nazwy typu. 瑋 桓 瑋 sugerował prefiks i sufiks łańcucha. Ale prefiks / sufiks ciągle się zmieniają. Z „typem sondy” nazwa_typu automatycznie oblicza rozmiary prefiksów i sufiksów dla „typu sondy”, aby wyodrębnić nazwę typu:

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

Wynik

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]

Wersja 16.3.3 VS 2019:

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
źródło
5

Inne odpowiedzi dotyczące RTTI (typeid) są prawdopodobnie tym, czego potrzebujesz, o ile:

  • możesz sobie pozwolić na narzut pamięci (co może być znaczne w przypadku niektórych kompilatorów)
  • nazwy klas zwracane przez kompilator są przydatne

Alternatywą (podobną do odpowiedzi Grega Hewgilla) jest zbudowanie tabeli cech w czasie kompilacji.

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

Pamiętaj, że jeśli zawiniesz deklaracje w makrze, będziesz mieć problem z zadeklarowaniem nazw typów szablonów, które biorą więcej niż jeden parametr (np. Std :: map), z powodu przecinka.

Aby uzyskać dostęp do nazwy typu zmiennej, wystarczy

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}
James Hopkin
źródło
1
Dobra uwaga na temat przecinka, wiedziałam, że istnieje powód, dla którego makra były złym pomysłem, ale wtedy o tym nie myślałam!
Greg Hewgill
2
static const char * value = "Wibble"; nie możesz zrobić tego kumpla :)
Johannes Schaub - litb
5

Bardziej ogólne rozwiązanie bez przeciążania funkcji niż moje poprzednie:

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

Tutaj MyClass jest klasą zdefiniowaną przez użytkownika. Można również dodać więcej warunków.

Przykład:

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

Wynik:

int
String
MyClass
Jahid
źródło
5

Podoba mi się metoda Nicka, może to być kompletny formularz (dla wszystkich podstawowych typów danych):

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
źródło
2
(i) nie będzie działać w przypadku innych typów (tj. nie jest ogólny); (ii) bezużyteczne wzdęcie kodu; (iii) to samo można (poprawnie) zrobić za pomocą typeidlub decltype.
edmz
2
Masz rację, ale obejmuje wszystkie podstawowe typy ... i właśnie tego teraz potrzebuję ..
Jahid
2
Czy możesz mi powiedzieć, jak zrobiłbyś to z dekliną,
Jahid
1
Jeśli jest to test czasu kompilacji, możesz użyć std :: is_same <T, S> i decltype, aby uzyskać T i S.
edmz
4

Kiedy rzucam wyzwanie, postanowiłem przetestować, jak daleko można się posunąć, korzystając z niezależnych od platformy (miejmy nadzieję) podstępów do szablonów.

Nazwy są kompletowane w momencie kompilacji. (Co znaczytypeid(T).name() że nie można użyć, dlatego należy jawnie podać nazwy dla typów nieskomplikowanych. W przeciwnym razie zostaną wyświetlone symbole zastępcze).

Przykładowe użycie:

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)`.
}

Kod:

#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
źródło
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;
}

Wynik:

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
Szary Wilk
źródło
2

Jak wyjaśnił Scott Meyers w Effective Modern C ++,

Zwraca się do std::type_info::namenie gwarantuje powrotu anythong sensowne.

Najlepszym rozwiązaniem jest umożliwienie kompilatorowi wygenerowania komunikatu o błędzie podczas odliczania typu, na przykład

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

Wynik będzie mniej więcej taki, w zależności od kompilatorów,

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;

Dlatego dowiadujemy się, że xtyp jest int, ytyp jestconst int*

Milo Lu
źródło
0

Dla każdego, kto wciąż odwiedza, miałem ostatnio ten sam problem i postanowiłem napisać małą bibliotekę na podstawie odpowiedzi z tego postu. Zapewnia nazwy typów constexpr i indeksy typów oraz jest testowany na komputerach Mac, Windows i Ubuntu.

Kod biblioteki znajduje się tutaj: https://github.com/TheLartians/StaticTypeInfo

Lars Melchior
źródło