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


393

Per esempio:

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

Uscita prevista:

int

2
Ecco una sintesi della soluzione di forma lunga di Howard, ma implementato con un eretica una riga macro: #define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL). Se avete bisogno di supporto cross-platform: Usa #ifdef, #else, #endifper fornire una macro per altre piattaforme come MSVC.
Trevor Boyd Smith,


3
Se lo usi solo per il debug, potresti prendere in considerazione template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }. Quindi usando ad esempio print_T<const int * const **>();si stamperà void print_T() [T = const int *const **]in fase di esecuzione e preserverà tutti i qualificatori (funziona in GCC e Clang).
Henri Menke,

@Henri, __PRETTY_FUNCTION__non è Standard C ++ (il requisito è nel titolo della domanda).
Toby Speight,

Risposte:


505

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

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

Ora in C ++ 11 abbiamo decltype(x), che può trasformare un'espressione in un tipo. E decltype()viene fornito con un proprio set di regole molto interessanti. Ad esempio, decltype(a)e decltype((a))saranno generalmente tipi diversi (e per ragioni buone e comprensibili una volta che tali ragioni sono state scoperte).

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

No.

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

La questione fondamentale:

typeid(a).name()

butta via qualificatori 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è se n'è constandato. Questo non è un problema di QOI (Quality Of Implementation). Lo standard impone questo comportamento.

Quello che sto raccomandando di seguito è:

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

che sarebbe usato in questo modo:

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

e per me uscite:

int const

<disclaimer>Non l'ho provato su MSVC. </disclaimer> Ma sono lieto di ricevere feedback da coloro che lo fanno.

La soluzione C ++ 11

Sto usando __cxa_demangleper piattaforme non MSVC come raccomandato da ipapadop nella sua risposta ai tipi di demangle. Ma su MSVC mi fido typeiddi confondere i nomi (non testato). E questo nucleo racchiude alcuni semplici test che rilevano, ripristinano e riportano qualificatori di cv e riferimenti al tipo di input.

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

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

e l'output è:

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

Nota (ad 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 convenzione decltyperappresentano espressioni di valore con riferimenti di valore).

Quindi questo strumento è un veicolo eccellente solo per conoscere decltype, oltre a esplorare e eseguire il debug del proprio codice.

Al contrario, se dovessi costruire questo subito typeid(a).name(), senza aggiungere riferimenti o qualifiche CV persi, l'output sarebbe:

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

Vale a dire ogni riferimento e qualificatore CV viene rimosso.

Aggiornamento C ++ 14

Proprio quando pensi di aver risolto un problema, qualcuno esce sempre dal nulla e ti mostra un modo molto migliore. :-)

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

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

La risposta di Jamboree non delinea tutto per VS, e sto modificando un po 'il suo codice. Ma dal momento che questa risposta ottiene molte visualizzazioni, prenditi del tempo per andare laggiù e votare la sua risposta, senza la quale, questo aggiornamento non sarebbe mai avvenuto.

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

Questo codice eseguirà il backoff automatico constexprse sei ancora bloccato nell'antico C ++ 11. E se stai dipingendo sul muro della caverna con C ++ 98/03, anche il noexceptsacrificio.

Aggiornamento C ++ 17

Nei commenti sottostanti Lyberta sottolinea che il nuovo std::string_viewpuò sostituire 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
}

Ho aggiornato le costanti per VS grazie al bellissimo lavoro investigativo di Jive Dadson nei commenti qui sotto.

Aggiornare:

Assicurati di dare un'occhiata a questa riscrittura di seguito che elimina i numeri magici illeggibili nella mia ultima formulazione.


4
VS 14 CTP stampato tipi corretti, ho dovuto solo aggiungere una #include <iostream>riga.
Max Galkin,

3
Perché template <typename T> std :: string type_name ()? Perché non passi un tipo come argomento?
moonman239

2
Credo che la mia logica fosse che a volte avevo solo un tipo (come un parametro template dedotto), e non volevo costruirne artificialmente uno per ottenere il tipo (anche se in questi giorni declvalfarebbe il lavoro).
Howard Hinnant,

5
@AngelusMortis: poiché l'inglese è vago / ambiguo rispetto al codice C ++, ti incoraggio a copiarlo / incollarlo nel tuo test case con il tipo specifico che ti interessa, e con il compilatore specifico che ti interessa, e riscrivere con altro dettagli se il risultato è sorprendente e / o insoddisfacente.
Howard Hinnant,

3
@HowardHinnant puoi usare std::string_viewinvece di static_string?
Lyberta,

231

Provare:

#include <typeinfo>

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

Potrebbe essere necessario attivare RTTI nelle opzioni del compilatore per farlo funzionare. Inoltre, l'output di questo dipende dal compilatore. Potrebbe essere un nome di tipo non elaborato o un simbolo che altera il nome o qualsiasi altra via di mezzo.


4
Perché la stringa restituita dalla funzione name () è definita dall'implementazione?
Distruttore

4
@PravasiMeet Nessuna buona ragione, per quanto ne so. Il comitato semplicemente non voleva forzare gli attuatori del compilatore in particolari direzioni tecniche - probabilmente un errore, a ben vedere.
Konrad Rudolph,

2
C'è un flag che potrei usare per abilitare RTTI? Forse potresti dare una risposta inclusiva.
Jim,

4
@Destructor Fornire un formato standardizzato per la manipolazione dei nomi potrebbe dare l'impressione che l'interoperabilità tra i binari creati da due diversi compilatori sia possibile e / o sicura, quando non lo è. Poiché C ++ non ha un ABI standard, uno schema standard di manipolazione dei nomi sarebbe inutile, potenzialmente fuorviante e pericoloso.
Elkvis,

1
@Jim La sezione sui flag del compilatore sarebbe un ordine di grandezza più lungo della risposta stessa. GCC lo compila per impostazione predefinita, quindi "-fno-rtti", altri compilatori potrebbero scegliere di non farlo, ma non esiste uno standard per i flag del compilatore.
kfsone,

82

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

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

Ritorna:

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*>'

2
solo c ++ potrebbe renderlo così difficile (stampare un tipo di variabili automatiche al momento della compilazione). SOLO C ++.
Karl Pickett,

3
@KarlP beh per essere onesti è un po 'contorto, funziona anche questo :) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
NickV

Su VC ++ 17, questo riduce un riferimento rvalue a un riferimento semplice, anche in una funzione template con parametro forwarding-reference e il nome dell'oggetto racchiuso in std :: forward.
Jive Dadson,

Sei stato in grado di arrivare al tipo senza creare nuove ruote!
Steven Eckhoff,

1
Questa tecnica è descritta anche in "Articolo 4: Sapere come visualizzare i tipi dedotti" in Effective Modern C ++
lenkite,

54

Non dimenticare di includere <typeinfo>

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

#include <iostream>
#include <typeinfo>

using namespace std;

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

36

Secondo la soluzione di Howard , se non vuoi il numero magico, penso che questo sia un buon modo per rappresentare e sembra intuitivo:

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

4
Questa è una grande distillazione di sforzi nel passato diverse versioni di C ++ in qualcosa di breve e dolce. +1.
einpoklum,

1
Anche questo è il mio preferito!
Howard Hinnant,

1
Ecco una funzione simile che uso, che rileva il suffisso / prefisso automaticamente: stackoverflow.com/questions/1055452/...
HolyBlackCat

22

Si noti che i nomi generati dalla funzionalità RTTI di C ++ non sono portatili. Ad esempio, la classe

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

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 di log / debug


19

Puoi usare i template.

template <typename T> 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".


3
Non stampa "int" per pantaloncini e personaggi? E "float" per i doppi?
Gartenriese,

1
La specializzazione @gartenriese non ha questo inconveniente. Perché doublecompilerebbe la versione non specializzata della funzione template anziché fare una conversione di tipo implicita per usare la specializzazione: cpp.sh/2wzc
chappjc

1
@chappjc: Onestamente non so perché l'ho chiesto allora, è abbastanza chiaro per me ora. Ma grazie per aver risposto comunque a una domanda di un anno!
Gartenriese,

2
@gartenriese Ho pensato tanto, ma "Internet" potrebbe avere la stessa domanda ad un certo punto.
Chappjc,

18

Come accennato, typeid().name()può restituire un nome alterato. In GCC (e in alcuni altri compilatori) puoi aggirare il problema con il seguente codice:

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

}


10

Per questo puoi usare una classe di tratti. Qualcosa di simile a:

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

Il DECLARE_TYPE_NAME definizione esiste per semplificarti la vita nel dichiarare questa classe di tratti per tutti i tipi che ti aspetti di aver bisogno.

Questo potrebbe essere più utile delle soluzioni che coinvolgono typeidperché puoi controllare l'output. Ad esempio, usando typeidfor long longsul mio compilatore si ottiene "x".


10

In C ++ 11, abbiamo decltype. In c ++ standard non è possibile visualizzare il tipo esatto di variabile dichiarata utilizzando decltype. Possiamo usare boost typeindex cioè type_id_with_cvr(cvr sta per const, volatile, riferimento) per stampare il tipo come sotto.

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

1
sarebbe più semplice usare una funzione di aiuto:template<typename T> void print_type(T){cout << "type T is: "<< type_id_with_cvr<T>().pretty_name()<< '\n';}
r0ng

6

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

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

Testato solo su Linux.


1
Brutto inferno ma farà per quello che mi serve. E molto più piccolo delle altre soluzioni. Funziona su Mac tra l'altro.
Marco Luglio,

6

Howard Hinnant ha usato numeri magici per estrarre il nome del tipo. 瑋 桓 瑋 ha suggerito il prefisso e il suffisso della stringa. Ma prefisso / suffisso continuano a cambiare. Con "probe_type" type_name calcola automaticamente le dimensioni del prefisso e del suffisso per "probe_type" per estrarre il nome del tipo:

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

Produzione

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 versione 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)

5

Le altre risposte che coinvolgono RTTI (typeid) sono probabilmente ciò che vuoi, purché:

  • puoi permetterti il ​​sovraccarico di memoria (che può essere considerevole con alcuni compilatori)
  • i nomi delle classi restituiti dal compilatore sono utili

L'alternativa, (simile alla risposta di Greg Hewgill), è quella di costruire una tabella dei tratti in fase di compilazione.

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

Tieni presente che se racchiudi le dichiarazioni in una macro, avrai difficoltà a dichiarare i nomi per i tipi di modello che accettano 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 <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}

1
Un buon punto sulla virgola, sapevo che c'era un motivo per cui le macro erano una cattiva idea, ma non ci ho pensato in quel momento!
Greg Hewgill,

2
static const char * value = "Wibble"; non puoi farlo
amico

5

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

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

Qui MyClass è una classe definita dall'utente. Altre condizioni possono essere aggiunte anche qui.

Esempio:

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

Produzione:

int
String
MyClass

5

Mi piace il metodo di Nick, un modulo completo potrebbe essere questo (per tutti i tipi di dati di base):

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

2
(i) non funzionerà per altri tipi (cioè non del tutto generico); (ii) inutile eccesso di codice; (iii) lo stesso può essere (correttamente) fatto con typeido decltype.
Edmz,

2
Hai ragione, ma copre tutti i tipi di base ... ed è quello di cui ho bisogno in questo momento ..
Jahid

2
Puoi dirmi, come lo faresti con decltype,
Jahid

1
Se è un test in fase di compilazione, puoi usare std :: is_same <T, S> e decltype per ottenere T e S.
edmz

4

Mentre sfido, ho deciso di testare fino a che punto si può andare con l'inganno del modello indipendente dalla piattaforma (si spera).

I nomi sono assemblati completamente al momento della compilazione. (Il che significa typeid(T).name()che non è possibile utilizzarlo, quindi è necessario fornire esplicitamente nomi per tipi non composti. Altrimenti verranno visualizzati i segnaposto.)

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

Codice:

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

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

Produzione:

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

2

Come spiegato da Scott Meyers in Effective Modern C ++,

Chiamate a std::type_info::namenon sono garantiti per tornare anythong sensibile.

La soluzione migliore è consentire al compilatore di generare un messaggio di errore durante la detrazione del tipo, ad esempio,

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

Il risultato sarà qualcosa del genere, a seconda dei compilatori,

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;

Quindi, arriviamo a sapere che xil tipo è int, yil tipo èconst int*


0

Per chiunque sia ancora in visita, di recente ho avuto lo stesso problema e ho deciso di scrivere una piccola biblioteca basata sulle risposte di questo post. Fornisce nomi di tipo constexpr e indici di tipo e viene testato su Mac, Windows e Ubuntu.

Il codice della libreria è qui: https://github.com/TheLartians/StaticTypeInfo

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.