È ansible stampare il tipo di una variabile in C ++ standard?

Per esempio:

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

Uscita prevista:

 int 

Aggiornamento di C ++ 11 a una domanda molto vecchia: stampa il tipo di variabile in C ++.

La risposta accettata (e buona) è quella di usare typeid(a).name() , dove a è un nome di variabile.

Ora in C ++ 11 abbiamo decltype(x) , che può trasformare un’espressione in un tipo. E decltype() una propria serie di regole molto interessanti. Ad esempio decltype(a) e decltype((a)) saranno generalmente di tipo diverso (e per ragioni buone e comprensibili una volta che tali motivi sono stati esposti).

Il nostro fidato typeid(a).name() ci aiuterà a esplorare questo nuovo mondo coraggioso?

No.

Ma lo strumento che non sarà così complicato. Ed è quello strumento che sto usando come risposta a questa domanda. Confronterò e contrapporrò questo nuovo strumento a typeid(a).name() . E questo nuovo strumento è in realtà costruito sopra a typeid(a).name() .

Il problema fondamentale:

 typeid(a).name() 

getta via qualificatori di cv, riferimenti e lvalue / rvalue-ness. Per esempio:

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

Per me uscite:

 i 

e sto indovinando sulle uscite MSVC:

 int 

Cioè il const è sparito. Questo non è un problema QOI (Quality Of Implementation). Lo standard impone questo comportamento.

Quello che sto raccomandando di seguito è:

 template  std::string type_name(); 

che verrebbe usato in questo modo:

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

e per me uscite:

 int const 

Non ho provato questo su MSVC. Ma accolgo con favore il feedback di coloro che lo fanno.

La soluzione C ++ 11

Sto usando __cxa_demangle per piattaforms non MSVC come raccomandato da ipapadop nella sua risposta ai tipi di demangle. Ma su MSVC mi fido di typeid per demaggere i nomi (non testati). E questo nucleo è avvolto attorno ad alcuni semplici test che rilevano, ripristinano e riportano qualifiche e qualifiche cv al tipo di input.

 #include  #include  #ifndef _MSC_VER # include  #endif #include  #include  #include  template  std::string type_name() { typedef typename std::remove_reference::type TR; std::unique_ptr 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::value) r += " const"; if (std::is_volatile::value) r += " volatile"; if (std::is_lvalue_reference::value) r += "&"; else if (std::is_rvalue_reference::value) r += "&&"; return r; } 

I risultati

Con questa soluzione posso fare questo:

 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() << '\n'; std::cout << "decltype((i)) is " << type_name() << '\n'; std::cout << "decltype(ci) is " << type_name() << '\n'; std::cout << "decltype((ci)) is " << type_name() << '\n'; std::cout << "decltype(static_cast(i)) is " << type_name(i))>() << '\n'; std::cout << "decltype(static_cast(i)) is " << type_name(i))>() << '\n'; std::cout << "decltype(static_cast(i)) is " << type_name(i))>() << '\n'; std::cout << "decltype(foo_lref()) is " << type_name() << '\n'; std::cout << "decltype(foo_rref()) is " << type_name() << '\n'; std::cout << "decltype(foo_value()) is " << type_name() << '\n'; } 

e l'output è:

 decltype(i) is int decltype((i)) is int& decltype(ci) is int const decltype((ci)) is int const& decltype(static_cast(i)) is int& decltype(static_cast(i)) is int&& decltype(static_cast(i)) is int decltype(foo_lref()) is int& decltype(foo_rref()) is int&& decltype(foo_value()) is int 

Nota (per esempio) la differenza tra decltype(i) e decltype((i)) . Il primo è il tipo di dichiarazione di i . Quest'ultimo è il "tipo" dell'espressione i . (le espressioni non hanno mai un tipo di riferimento, ma come decltype convenzione rappresentano espressioni lvalue con riferimenti lvalue).

Quindi questo strumento è un veicolo eccellente solo per imparare a decltype , oltre a esplorare e fare il debug del tuo codice.

Al contrario, se dovessi creare questo solo su typeid(a).name() , senza aggiungere back cv-qualifier o riferimenti, l'output sarebbe:

 decltype(i) is int decltype((i)) is int decltype(ci) is int decltype((ci)) is int decltype(static_cast(i)) is int decltype(static_cast(i)) is int decltype(static_cast(i)) is int decltype(foo_lref()) is int decltype(foo_rref()) is int decltype(foo_value()) is int 

Vale a dire ogni riferimento e cv-qualificatore viene rimosso.

Aggiornamento C ++ 14

Proprio quando pensi di avere una soluzione a un problema inchiodato, qualcuno arriva sempre dal nulla e ti mostra un modo molto migliore. 🙂

Questa risposta di Jamboree mostra come ottenere il nome del tipo in C ++ 14 in fase di compilazione. È una soluzione brillante per un paio di motivi:

  1. È in fase di compilazione!
  2. Si ottiene il compilatore stesso per fare il lavoro invece di una libreria (anche una std :: lib). Ciò significa risultati più accurati per le ultime funzionalità linguistiche (come lambdas).

La risposta di Jamboree non spiega tutto per VS, e sto modificando un po 'il suo codice. Ma dal momento che questa risposta ottiene molti punti di vista, prenditi un po 'di tempo per andare là e alzare la sua risposta, senza la quale, questo aggiornamento non sarebbe mai accaduto.

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

Questo codice si spegnerà automaticamente su constexpr se sei ancora bloccato nell'antico C ++ 11. E se stai dipingendo sulla parete della caverna con C ++ noexcept , anche la noexcept - noexcept viene sacrificata.

Aggiornamento C ++ 17

Nei commenti sotto Lyberta sottolinea che il nuovo std::string_view può sostituire static_string :

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

Ho aggiornato le costanti per VS grazie all'ottimo lavoro investigativo di Jive Dadson nei commenti qui sotto.

Provare:

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

Potrebbe essere necessario triggersre RTTI nelle opzioni del compilatore affinché funzioni. Inoltre, l'output di questo dipende dal compilatore. Potrebbe trattarsi di un nome di tipo non elaborato o di un simbolo di manomissione del nome o di qualsiasi altra cosa nel mezzo.

Molto brutto ma fa il trucco se vuoi solo informazioni sulla compilazione del tempo (ad esempio per il debug):

 auto testVar = std::make_tuple(1, 1.0, "abc"); static_assert(decltype(testVar)::dummy_error, "DUMP MY TYPE" ); 

Ritorna:

 Compilation finished with errors: source.cpp: In function 'int main()': source.cpp:5:19: error: 'dummy_error' is not a member of 'std::tuple' 

Non dimenticare di includere

Credo che ciò a cui ti riferisci sia l’identificazione del tipo di runtime. È ansible ottenere quanto sopra facendo.

 #include  #include  using namespace std; int main() { int i; cout << typeid(i).name(); return 0; } 

Si noti che i nomi generati dalla funzione RTTI di C ++ non sono portabili. Ad esempio, la class

 MyNamespace::CMyContainer 

avrà i seguenti nomi:

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

Quindi non puoi usare queste informazioni per la serializzazione. Tuttavia, la proprietà typeid (a) .name () può ancora essere utilizzata per scopi log / debug

Puoi usare i modelli.

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

Nell’esempio sopra, quando il tipo non corrisponde, verrà stampato “sconosciuto”.

Come detto, typeid().name() può restituire un nome storpiato. In GCC (e in alcuni altri compilatori) puoi aggirarlo con il seguente codice:

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

}

Potresti usare una class di tratti per questo. Qualcosa di simile a:

 #include  using namespace std; template  class type_name { public: static const char *name; }; #define DECLARE_TYPE_NAME(x) template<> const char *type_name::name = #x; #define GET_TYPE_NAME(x) (type_name::name) DECLARE_TYPE_NAME(int); int main() { int a = 12; cout << GET_TYPE_NAME(a) << endl; } 

La definizione DECLARE_TYPE_NAME esiste per semplificarti la vita nel dichiarare questa class di tratti per tutti i tipi che ti aspetti di avere bisogno.

Questo potrebbe essere più utile delle soluzioni che coinvolgono typeid perché si arriva a controllare l'output. Ad esempio, l'uso di typeid per long long sul mio compilatore restituisce "x".

Le altre risposte che riguardano RTTI (typeid) sono probabilmente quelle che vuoi, purché:

  • puoi permetterti il ​​sovraccarico della memoria (che può essere considerevole con alcuni compilatori)
  • i nomi delle classi sono utili per i rendimenti del compilatore

L’alternativa, (simile alla risposta di Greg Hewgill), è quella di build una tabella dei tratti compilata.

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

Si tenga presente che se si avvolgono le dichiarazioni in una macro, si avrà difficoltà a dichiarare nomi per i tipi di modelli che utilizzano più di un parametro (ad es. Std :: map), a causa della virgola.

Per accedere al nome del tipo di una variabile, tutto ciò che serve è

 template  const char* get_type_as_string(const T&) { return type_as_string::value; } 

In C ++ 11, abbiamo decltype. Non c’è modo in c ++ standard per visualizzare il tipo esatto di variabile dichiarata usando decltype. Possiamo usare boost typeindex ie type_id_with_cvr (cvr sta per const, volatile, reference) per stampare il tipo come sotto.

 #include  #include  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().pretty_name() << '\n'; cout << "decltype((i)) is " << type_id_with_cvr().pretty_name() << '\n'; cout << "decltype(ci) is " << type_id_with_cvr().pretty_name() << '\n'; cout << "decltype((ci)) is " << type_id_with_cvr().pretty_name() << '\n'; cout << "decltype(std::move(i)) is " << type_id_with_cvr().pretty_name() << '\n'; cout << "decltype(std::static_cast(i)) is " << type_id_with_cvr(i))>().pretty_name() << '\n'; return 0; } 

Una soluzione più generica senza sovraccarico di funzioni rispetto alla mia precedente:

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

Qui MyClass è una class definita dall’utente. Altre condizioni possono essere aggiunte anche qui.

Esempio:

 #include  class MyClass{}; template std::string TypeOf(T){ std::string Type="unknown"; if(std::is_same::value) Type="int"; if(std::is_same::value) Type="String"; if(std::is_same::value) Type="MyClass"; return Type;} int main(){; int a=0; std::string s=""; MyClass my; std::cout< 

Produzione:

 int String MyClass 

Mi piace il metodo di Nick, una forma completa potrebbe essere questa (per tutti i tipi di dati di base):

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

Puoi anche usare c ++ filt con l’opzione -t (tipo) per demangular il nome del tipo:

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

Testato solo su Linux.

Mentre sfido, ho deciso di testare fino a che punto si può andare con l’astuzia modello (indipendentemente) dalla piattaforma indipendente.

I nomi sono assemblati completamente al momento della compilazione. (Il che significa che non è ansible utilizzare typeid(T).name() , pertanto è necessario fornire esplicitamente nomi per tipi non composti, altrimenti verranno invece visualizzati segnaposti).

Esempio di utilizzo:

 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 << '\n'; // -> `void (*)(int)` // Ugly mess case // Note that compiler removes cv-qualifiers from parameters and replaces arrays with pointers. std::cout << type_name << '\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 << '\n'; // -> `class? (*)(int,??)` // With appropriate TYPE_NAME's, the output would be `std::string (*)(int,short)`. } 

Codice:

 #include  #include  static constexpr std::size_t max_str_lit_len = 256; template  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  struct str_lit { static constexpr char value[] {C..., '\0'}; static constexpr int size = sl_len(value); template  struct concat_impl {using type = typename concat_impl::type::template concat_impl::type;}; template  struct concat_impl> {using type = str_lit;}; template  using concat = typename concat_impl::type; }; template  struct trim_str_lit_impl; template  struct trim_str_lit_impl, S> { using type = str_lit; }; template  using trim_str_lit = typename trim_str_lit_impl, S>::type; #define STR_LIT(str) ::trim_str_lit<::sl_len(str), ::str_lit::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(str),::sl_at(str),::sl_at(str),::sl_at(str) template  constexpr str_lit make_str_lit(str_lit) {return {};} template  constexpr auto make_str_lit(const char (&str)[N]) { return trim_str_lit{}; } template  struct cexpr_pow {static constexpr std::size_t value = A * cexpr_pow::value;}; template  struct cexpr_pow {static constexpr std::size_t value = 1;}; template > struct num_to_str_lit_impl; template  struct num_to_str_lit_impl> { static constexpr auto func() { if constexpr (N >= cexpr_pow<10,X>::value) return num_to_str_lit_impl::func(); else return str_lit<(N / cexpr_pow<10,X-1-Seq>::value % 10 + '0')...>{}; } }; template  using num_to_str_lit = decltype(num_to_str_lit_impl::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; 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  inline constexpr bool ptr_or_ref = std::is_pointer_v || std::is_reference_v || std::is_member_pointer_v; template  inline constexpr bool func_or_arr = std::is_function_v || std::is_array_v; template  struct primitive_type_name {using value = unk;}; template >> using enable_if_class = T; template >> using enable_if_union = T; template >> using enable_if_enum = T; template  struct primitive_type_name> {using value = c_cla;}; template  struct primitive_type_name> {using value = c_uni;}; template  struct primitive_type_name> {using value = c_enu;}; template  struct type_name_impl; template  using type_name_lit = std::conditional_t::value::template concat, typename type_name_impl::l::template concat::r>>, typename primitive_type_name::value, typename type_name_impl::l::template concat::r>>; template  inline constexpr const char *type_name = type_name_lit::value; template  && !std::is_volatile_v>> using enable_if_no_cv = T; template  struct type_name_impl { using l = typename primitive_type_name::value::template concat; using r = str_lit<>; }; template  struct type_name_impl { using new_T_l = std::conditional_t::l::size && !ptr_or_ref, spa::concat::l>, typename type_name_impl::l>; using l = std::conditional_t, typename new_T_l::template concat, con::concat>; using r = typename type_name_impl::r; }; template  struct type_name_impl { using new_T_l = std::conditional_t::l::size && !ptr_or_ref, spa::concat::l>, typename type_name_impl::l>; using l = std::conditional_t, typename new_T_l::template concat, vol::concat>; using r = typename type_name_impl::r; }; template  struct type_name_impl { using new_T_l = std::conditional_t::l::size && !ptr_or_ref, spa::concat::l>, typename type_name_impl::l>; using l = std::conditional_t, typename new_T_l::template concat, con_vol::concat>; using r = typename type_name_impl::r; }; template  struct type_name_impl { using l = std::conditional_t, typename type_name_impl::l::template concat, typename type_name_impl::l::template concat< ast>>; using r = std::conditional_t, rpa::concat::r>, typename type_name_impl::r>; }; template  struct type_name_impl { using l = std::conditional_t, typename type_name_impl::l::template concat, typename type_name_impl::l::template concat< amp>>; using r = std::conditional_t, rpa::concat::r>, typename type_name_impl::r>; }; template  struct type_name_impl { using l = std::conditional_t, typename type_name_impl::l::template concat, typename type_name_impl::l::template concat< amp, amp>>; using r = std::conditional_t, rpa::concat::r>, typename type_name_impl::r>; }; template  struct type_name_impl { using l = std::conditional_t, typename type_name_impl::l::template concat, nsp, ast>, typename type_name_impl::l::template concat< type_name_lit, nsp, ast>>; using r = std::conditional_t, rpa::concat::r>, typename type_name_impl::r>; }; template  struct type_name_impl> { using l = typename type_name_impl::l; using r = lbr::concat::r>; }; template  struct type_name_impl> { using l = typename type_name_impl::l; using r = lbr::concat, rbr, typename type_name_impl::r>; }; template  struct type_name_impl { using l = typename type_name_impl::l; using r = lpa::concat::r>; }; template  struct type_name_impl { using l = typename type_name_impl::l; using r = lpa::concat, com::concat>..., rpa, typename type_name_impl::r>; }; #define TYPE_NAME(t) template <> struct primitive_type_name {using value = STR_LIT(#t);};

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

Produzione:

 Type of a: St16initializer_listIPKcE Real type of a: std::initializer_list Type of s: PKc Real type of s: char const* one two three Type of i: i Real type of i: int