É possível imprimir o tipo de uma variável no C ++ padrão?

393

Por exemplo:

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

Saída esperada:

int
Jorge Ferreira
fonte
2
Aqui está um resumo da solução de longo formulário de Howard, mas implementado com um de uma linha macro herética: #define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL). Se você precisar de suporte multi-plataforma: Use #ifdef, #else, #endifpara fornecer um macros para outras plataformas como MSVC.
Trevor Boyd Smith
Com exigência legível mais explícito: stackoverflow.com/questions/12877521/...
Ciro Santilli郝海东冠状病六四事件法轮功
3
Se você usar isso apenas para depuração, poderá considerar template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }. Em seguida, usar eg print_T<const int * const **>();imprimirá void print_T() [T = const int *const **]em tempo de execução e preservará todos os qualificadores (funciona no GCC e no Clang).
Henri Menke
@ Henri, __PRETTY_FUNCTION__não é padrão em C ++ (o requisito está no título da pergunta).
Toby Speight

Respostas:

505

Atualização do C ++ 11 para uma pergunta muito antiga: imprima o tipo de variável em C ++.

A resposta aceita (e boa) é usar typeid(a).name(), onde aé um nome de variável.

Agora, em C ++ 11, temos decltype(x), que pode transformar uma expressão em um tipo. E decltype()vem com seu próprio conjunto de regras muito interessantes. Por exemplo, decltype(a)e decltype((a))geralmente serão tipos diferentes (e por razões boas e compreensíveis quando essas razões forem expostas).

Nossos confiáveis typeid(a).name()nos ajudarão a explorar este admirável mundo novo?

Não.

Mas a ferramenta que a vontade não é tão complicada. E é essa ferramenta que estou usando como resposta a esta pergunta. Vou comparar e contrastar esta nova ferramenta typeid(a).name(). E essa nova ferramenta é realmente construída sobre typeid(a).name().

A questão fundamental:

typeid(a).name()

joga fora qualificadores-cv, referências e lvalue / rvalue-ness. Por exemplo:

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

Para mim saídas:

i

e eu estou supondo em saídas MSVC:

int

Ou seja, o constse foi. Este não é um problema de QOI (Qualidade de implementação). O padrão exige esse comportamento.

O que eu estou recomendando abaixo é:

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

que seria usado assim:

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

e para mim saídas:

int const

<disclaimer>Eu não testei isso no MSVC. </disclaimer> Mas agradeço o feedback daqueles que o fazem.

A solução C ++ 11

Estou usando __cxa_demanglepara plataformas não-MSVC, como recomendado pelo ipapadop em sua resposta para desmembrar tipos. Mas no MSVC, estou confiando typeidem desmantelar nomes (não testados). E esse núcleo envolve alguns testes simples que detectam, restauram e reportam qualificadores de cv e referências ao tipo de entrada.

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

Os resultados

Com esta solução, posso fazer o seguinte:

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 a saída é:

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

Observe (por exemplo) a diferença entre decltype(i)e decltype((i)). O primeiro é o tipo da declaração de i. O último é o "tipo" da expressão i . (as expressões nunca têm tipo de referência, mas, como convenção, decltyperepresentam expressões lvalue com referências lvalue).

Portanto, essa ferramenta é um excelente veículo apenas para aprender decltype, além de explorar e depurar seu próprio código.

Por outro lado, se eu fosse construir isso apenas typeid(a).name(), sem adicionar novamente qualificadores ou referências cv perdidos, a saída seria:

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

Ou seja, todas as referências e qualificadores de cv são retirados.

Atualização C ++ 14

Apenas quando você pensa que tem uma solução para um problema pregado, alguém sempre sai do nada e mostra uma maneira muito melhor. :-)

Esta resposta do Jamboree mostra como obter o nome do tipo em C ++ 14 em tempo de compilação. É uma solução brilhante por alguns motivos:

  1. Está em tempo de compilação!
  2. Você solicita que o compilador faça o trabalho em vez de uma biblioteca (mesmo um std :: lib). Isso significa resultados mais precisos para os recursos mais recentes do idioma (como lambdas).

A resposta de Jamboree não explica tudo para o VS, e estou ajustando um pouco o código dele. Mas como essa resposta recebe muitas visualizações, reserve um tempo para ir até lá e aprovar sua resposta, sem a qual, essa atualização nunca teria acontecido.

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

Esse código será retirado automaticamente constexprse você ainda estiver preso no antigo C ++ 11. E se você estiver pintando na parede da caverna com C ++ 98/03, o noexceptsacrifício também será.

Atualização C ++ 17

Nos comentários abaixo, Lyberta ressalta que o novo std::string_viewpode substituir 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
}

Atualizei as constantes do VS graças ao excelente trabalho de detetive de Jive Dadson nos comentários abaixo.

Atualizar:

Certifique-se de verificar esta reescrita abaixo, que elimina os números mágicos ilegíveis na minha formulação mais recente.

Howard Hinnant
fonte
4
O VS 14 CTP imprimiu os tipos corretos, só tive que adicionar uma #include <iostream>linha.
precisa
3
Por que modelo <typename T> std :: string type_name ()? Por que você não está passando um tipo como argumento?
moonman239
2
Acredito que minha lógica era que, às vezes, eu tinha um tipo (como um parâmetro de modelo deduzido) e não queria ter que construir artificialmente um deles para obter o tipo (embora esses dias declvalfuncionem bem).
Howard Hinnant
5
@AngelusMortis: como o inglês é vago / ambíguo em comparação com o código C ++, encorajo você a copiar / colar isso no seu caso de teste com o tipo específico de seu interesse e com o compilador específico de seu interesse, e escreva novamente com mais detalhes se o resultado for surpreendente e / ou insatisfatório.
Howard Hinnant 02/03
3
@HowardHinnant você pode usar em std::string_viewvez de static_string?
Lyberta
231

Tentar:

#include <typeinfo>

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

Talvez você precise ativar o RTTI nas opções do compilador para que isso funcione. Além disso, a saída disso depende do compilador. Pode ser um nome de tipo bruto ou um símbolo de distorção de nome ou qualquer outra coisa.

Konrad Rudolph
fonte
4
Por que a string retornada pela função name () é definida como implementação?
Destructor
4
@PravasiMeet Não há uma boa razão, tanto quanto eu sei. O comitê simplesmente não queria forçar os implementadores de compiladores a seguir instruções técnicas específicas - provavelmente um erro, em retrospectiva.
21978 Konrad Rudolph
2
Existe um sinalizador que eu possa usar para ativar o RTTI? Talvez você possa tornar sua resposta inclusiva.
Jim Jim
4
@Destructor Fornecer um formato padronizado de manipulação de nomes pode dar a impressão de que a interoperabilidade entre binários criados por dois compiladores diferentes é possível e / ou segura, quando não é. Como o C ++ não possui uma ABI padrão, um esquema de confusão de nomes padrão seria inútil e potencialmente enganoso e perigoso.
Elkvis 5/05
11
@ Jim A seção sobre sinalizadores do compilador seria uma ordem de magnitude maior que a própria resposta. O GCC compila com ele por padrão, portanto, "-fno-rtti", outros compiladores podem optar por não fazê-lo, mas não há padrão para sinalizadores de compilador.
Kfsone #
82

Muito feio, mas faz o truque se você quiser apenas informações de tempo de compilação (por exemplo, para depuração):

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

Devoluções:

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
fonte
2
somente o c ++ poderia tornar isso tão difícil (imprimir um tipo de variáveis ​​automáticas em tempo de compilação). SOMENTE C ++.
Karl Pickett
3
@KarlP bem para ser justo, é um pouco complicado, isso funciona também :) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
NickV
No VC ++ 17, isso reduz uma referência rvalue a uma referência simples, mesmo em uma função de modelo com o parâmetro forwarding-reference, e o nome do objeto envolvido em std :: forward.
precisa
Você foi capaz de chegar ao tipo sem criar novas rodas!
Steven Eckhoff
11
Esta técnica é também descrita no "Item 4: Saiba como visualizar tipos deduzidos" em Effective Modern C ++
lenkite
54

Não esqueça de incluir <typeinfo>

Acredito que você está se referindo à identificação do tipo de tempo de execução. Você pode conseguir o acima, fazendo.

#include <iostream>
#include <typeinfo>

using namespace std;

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

De acordo com a solução de Howard , se você não quiser o número mágico, acho que essa é uma boa maneira de representar e parecer intuitiva:

#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;
}
康 桓 瑋
fonte
4
Essa é uma grande destilação de esforços nas últimas versões do C ++ em algo curto e agradável. +1.
Einpoklum
11
Este é o meu favorito também!
Howard Hinnant
11
Aqui uma função semelhante que eu uso, que detecta o sufixo / prefixo automaticamente: stackoverflow.com/questions/1055452/…
HolyBlackCat
22

Observe que os nomes gerados pelo recurso RTTI do C ++ não são portáveis. Por exemplo, a classe

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

terá os seguintes nomes:

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

Portanto, você não pode usar essas informações para serialização. Mas ainda assim, a propriedade typeid (a) .name () ainda pode ser usada para fins de log / depuração

paercebal
fonte
19

Você pode usar modelos.

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

No exemplo acima, quando o tipo não for correspondido, será impresso "desconhecido".

usuario
fonte
3
Não imprimirá "int" para shorts e chars? E "flutuar" para duplas?
gartenriese 17/02
11
A especialização @gartenriese não tem essa desvantagem. Para doubleisso seria compilar a versão não-especializado da função de modelo em vez de fazer uma conversão implícita de tipo de utilizar a especialização: cpp.sh/2wzc
chappjc
11
@chappjc: Sinceramente, não sei por que perguntei naquela época, agora está bem claro para mim. Mas obrigado por responder a uma pergunta de um ano!
gartenriese
2
@gartenriese eu imaginei isso, mas "a internet" pode ter a mesma pergunta em algum momento.
Chappjc 5/03/2015
18

Como mencionado, typeid().name()pode retornar um nome mutilado. No GCC (e em alguns outros compiladores), você pode contornar o problema com o seguinte código:

#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
fonte
10

Você pode usar uma classe de características para isso. Algo como:

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

o DECLARE_TYPE_NAME definição existe para facilitar sua vida na declaração dessa classe de características para todos os tipos que você espera.

Isso pode ser mais útil do que as soluções que envolvem, typeidporque você controla a saída. Por exemplo, usar typeidfor long longno meu compilador fornece "x".

Greg Hewgill
fonte
10

No C ++ 11, temos decltype. Não há como no c ++ padrão exibir o tipo exato de variável declarada usando decltype. Podemos usar o tipo de impulso index type_id_with_cvr( ie cvr significa const, volatile, reference) para imprimir o tipo como abaixo.

#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
fonte
11
seria mais simples usar uma função auxiliar:template<typename T> void print_type(T){cout << "type T is: "<< type_id_with_cvr<T>().pretty_name()<< '\n';}
r0ng 18/17
6

Você também pode usar o c ++ filt com a opção -t (type) para desmontar o nome do 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;
}

Testado apenas no Linux.

Alan
fonte
11
Inferno feio, mas vai fazer o que eu preciso. E muito menor que as outras soluções. Funciona em Mac btw.
Marco Luglio 20/05
6

Howard Hinnant usou números mágicos para extrair o nome do tipo. Pref 桓 瑋 sugeriu prefixo e sufixo de string. Mas prefixo / sufixo continuam mudando. Com "probe_type" type_name calcula automaticamente os tamanhos de prefixo e sufixo para "probe_type" para extrair o nome do 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;
}

Resultado

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 versão 16.3.3:

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

As outras respostas que envolvem RTTI (typeid) são provavelmente o que você deseja, desde que:

  • você pode pagar a sobrecarga de memória (o que pode ser considerável em alguns compiladores)
  • os nomes de classe que seu compilador retorna são úteis

A alternativa (semelhante à resposta de Greg Hewgill) é criar uma tabela de características em tempo de compilação.

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

Esteja ciente de que, se você agrupar as declarações em uma macro, terá problemas ao declarar nomes para os tipos de modelo usando mais de um parâmetro (por exemplo, std :: map), devido à vírgula.

Para acessar o nome do tipo de uma variável, tudo o que você precisa é

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}
James Hopkin
fonte
11
Bom ponto sobre a vírgula, eu sabia que havia uma razão pela qual as macros eram uma má ideia, mas não pensei nisso na época!
Greg Hewgill 17/09/08
2
static const char * value = "Wibble"; você não pode fazer isso companheiro :)
Johannes Schaub - litb
5

Uma solução mais genérica sem sobrecarga de função que a anterior:

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

Aqui MyClass é uma classe definida pelo usuário. Mais condições podem ser adicionadas aqui também.

Exemplo:

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

Resultado:

int
String
MyClass
Jahid
fonte
5

Eu gosto do método de Nick. Um formulário completo pode ser este (para todos os tipos de dados básicos):

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
fonte
2
(i) não funcionará para outros tipos (ou seja, não é genérico); (ii) inchaço inútil do código; (iii) o mesmo pode ser (corretamente) feito com typeidou decltype.
Edmz 13/03/2015
2
Você está certo, mas abrange todos os tipos básicos ... e isso é o que eu preciso agora ..
Jahid
2
Você pode me dizer, como você faria com decltype,
Jahid
11
Se é um teste de tempo de compilação, você pode usar std :: is_same <T, S> e decltype para obter T e S.
edmz
4

Ao desafiar, decidi testar até que ponto alguém pode ir com truques de modelos independentes de plataforma (espero).

Os nomes são montados completamente no momento da compilação. (O que significa typeid(T).name()que não pôde ser usado, portanto, você deve fornecer explicitamente nomes para tipos não compostos. Caso contrário, os espaços reservados serão exibidos.)

Exemplo de uso:

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

Código:

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

Resultado:

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
Lobo cinza
fonte
2

Conforme explicado por Scott Meyers no Effective Modern C ++,

Chamadas para std::type_info::namenão são garantidos para retornar anythong sensata.

A melhor solução é permitir que o compilador gere uma mensagem de erro durante a dedução de tipo, por exemplo,

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

O resultado será algo assim, dependendo dos compiladores,

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;

Por isso, sabemos que esse xé o tipo int, yé o tipo éconst int*

Milo Lu
fonte
0

Para quem ainda está visitando, recentemente tive o mesmo problema e decidi escrever uma pequena biblioteca com base nas respostas deste post. Ele fornece nomes de tipo constexpr e índices de tipo e é testado no Mac, Windows e Ubuntu.

O código da biblioteca está aqui: https://github.com/TheLartians/StaticTypeInfo

Lars Melchior
fonte