Est-il possible d'imprimer le type d'une variable en C ++ standard?

393

Par exemple:

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

Production attendue:

int
Jorge Ferreira
la source
2
Voici un résumé de la solution de forme longue de Howard , mais mis en œuvre avec une ligne macro hérétique: #define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL). Si vous avez besoin d'un support multiplateforme: Utiliser #ifdef, #else, #endifpour fournir une macros pour d' autres plates - formes comme MSVC.
Trevor Boyd Smith
Avec une exigence de lecture humaine plus explicite: stackoverflow.com/questions/12877521/…
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功
3
Si vous ne l'utilisez que pour le débogage, vous voudrez peut-être envisager template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }. Ensuite, l'utilisation de eg print_T<const int * const **>();affichera void print_T() [T = const int *const **]au moment de l'exécution et préserve tous les qualificatifs (fonctionne dans GCC et Clang).
Henri Menke
@Henri, __PRETTY_FUNCTION__n'est pas C ++ standard (l'exigence est dans le titre de la question).
Toby Speight

Réponses:

505

Mise à jour C ++ 11 vers une question très ancienne: imprimer le type de variable en C ++.

La réponse acceptée (et bonne) est d'utiliser typeid(a).name(), où aest un nom de variable.

Maintenant, en C ++ 11, nous avons decltype(x), ce qui peut transformer une expression en un type. Et decltype()vient avec son propre ensemble de règles très intéressantes. Par exemple decltype(a)etdecltype((a)) seront généralement de types différents (et pour de bonnes raisons compréhensibles une fois que ces raisons sont exposées).

Est-ce que notre fidèle typeid(a).name() nous aideront-ils à explorer ce nouveau monde courageux?

Non.

Mais l'outil qui le fera n'est pas si compliqué. Et c'est cet outil que j'utilise comme réponse à cette question. Je comparerai et opposerai ce nouvel outil à typeid(a).name(). Et ce nouvel outil est en fait construit par-dessus typeid(a).name().

La question fondamentale:

typeid(a).name()

jette les qualificatifs cv, les références et lvalue / rvalue-ness. Par exemple:

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

Pour moi, les sorties:

i

et je suppose sur les sorties MSVC:

int

C'est à dire le const c'est parti. Ce n'est pas un problème de QOI (Quality Of Implementation). La norme impose ce comportement.

Ce que je recommande ci-dessous est:

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

qui serait utilisé comme ceci:

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

et pour moi sorties:

int const

<disclaimer>Je n'ai pas testé cela sur MSVC. </disclaimer> Mais j'accueille favorablement les commentaires de ceux qui le font.

La solution C ++ 11

J'utilise __cxa_demanglepour les plates-formes non MSVC comme le recommande ipapadop dans sa réponse aux types de démêlage. Mais sur MSVC, j'ai confiance typeidpour démêler les noms (non testés). Et ce noyau est enroulé autour de quelques tests simples qui détectent, restaurent et signalent les qualificatifs cv et les références au type d'entrée.

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

Les resultats

Avec cette solution, je peux le faire:

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

et la sortie est:

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

Notez (par exemple) la différence entre decltype(i)et decltype((i)). Le premier est le type de la déclaration de i. Ce dernier est le "type" de l' expression i . (les expressions n'ont jamais de type référence, mais comme une conventiondecltype représentent des expressions lvalue avec des références lvalue).

Ainsi, cet outil est un excellent véhicule pour en savoir plus decltype, en plus d'explorer et de déboguer votre propre code.

En revanche, si je construisais cela juste typeid(a).name()sans ajouter de qualificatifs ou de références cv perdus, la sortie serait:

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

C'est-à-dire que chaque référence et qualificatif de cv est supprimé.

Mise à jour C ++ 14

Juste au moment où vous pensez avoir trouvé une solution à un problème, quelqu'un sort toujours de nulle part et vous montre une bien meilleure façon. :-)

Cette réponse du Jamboree montre comment obtenir le nom du type en C ++ 14 au moment de la compilation. C'est une solution brillante pour deux raisons:

  1. C'est au moment de la compilation!
  2. Vous obtenez le compilateur lui-même pour faire le travail au lieu d'une bibliothèque (même un std :: lib). Cela signifie des résultats plus précis pour les dernières fonctionnalités linguistiques (comme les lambdas).

La réponse de Jamboree ne décrit pas tout pour VS, et je peaufine un peu son code. Mais puisque cette réponse obtient beaucoup de vues, prenez le temps d'aller là-bas et de voter pour sa réponse, sans laquelle, cette mise à jour n'aurait jamais eu lieu.

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

Ce code sera automatiquement désactivé constexprsi vous êtes toujours bloqué dans l'ancien C ++ 11. Et si vous peignez sur le mur de la grotte avec C ++ 98/03, le noexceptest également sacrifié.

Mise à jour C ++ 17

Dans les commentaires ci-dessous, Lyberta souligne que le nouveau std::string_viewpeut remplacer 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
}

J'ai mis à jour les constantes de VS grâce au très beau travail de détective de Jive Dadson dans les commentaires ci-dessous.

Mise à jour:

Assurez-vous de vérifier cette réécriture ci-dessous qui élimine les nombres magiques illisibles dans ma dernière formulation.

Howard Hinnant
la source
4
VS 14 CTP a imprimé les bons types, je n'ai eu qu'à ajouter une #include <iostream>ligne.
Max Galkin
3
Pourquoi le template <typename T> std :: string type_name ()? Pourquoi ne passez-vous pas un type comme argument?
moonman239
2
Je crois que mon raisonnement était que parfois je n'avais un type (comme un paramètre de modèle déduit), et je ne voulais pas avoir à construire artificiellement un de ceux pour obtenir le type (bien que ces jours -ci feraient le travail). declval
Howard Hinnant
5
@AngelusMortis: Parce que l'anglais est vague / ambigu par rapport au code C ++, je vous encourage à copier / coller ceci dans votre cas de test avec le type spécifique qui vous intéresse, et avec le compilateur spécifique qui vous intéresse, et à réécrire avec plus détails si le résultat est surprenant et / ou insatisfaisant.
Howard Hinnant
3
@HowardHinnant pouvez-vous utiliser à la std::string_viewplace de static_string?
Lyberta
231

Essayer:

#include <typeinfo>

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

Vous devrez peut-être activer RTTI dans vos options de compilation pour que cela fonctionne. De plus, la sortie de ceci dépend du compilateur. Il peut s'agir d'un nom de type brut ou d'un symbole de changement de nom ou de quelque chose entre les deux.

Konrad Rudolph
la source
4
Pourquoi la chaîne retournée par la fonction name () est-elle définie par l'implémentation?
Destructor
4
@PravasiMeet Aucune bonne raison, pour autant que je sache. Le comité ne voulait tout simplement pas forcer les implémenteurs du compilateur à des directions techniques particulières - probablement une erreur, avec le recul.
Konrad Rudolph
2
Existe-t-il un indicateur que je pourrais utiliser pour activer RTTI? Vous pourriez peut-être rendre votre réponse inclusive.
Jim
4
@Destructor Fournir un format de gestion de nom normalisé peut donner l'impression que l'interopérabilité entre des fichiers binaires construits par deux compilateurs différents est possible et / ou sûre, quand elle ne l'est pas. Parce que C ++ n'a pas d'ABI standard, un schéma de gestion de nom standard serait inutile, et potentiellement trompeur et dangereux.
Elkvis
1
@Jim La section sur les drapeaux du compilateur serait d'un ordre de grandeur plus longue que la réponse elle-même. GCC compile avec lui par défaut, d'où "-fno-rtti", d'autres compilateurs peuvent choisir de ne pas le faire, mais il n'y a pas de standard pour les drapeaux du compilateur.
kfsone
82

Très moche mais fait l'affaire si vous voulez seulement compiler les informations de temps (par exemple pour le débogage):

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

Retour:

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
la source
2
seul c ++ pourrait rendre cela si difficile (impression d'un type de variables automatiques au moment de la compilation). SEULEMENT C ++.
Karl Pickett
3
@KarlP bien pour être juste c'est un peu compliqué, ça marche aussi :) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
NickV
Sur VC ++ 17, cela réduit une rvalue-reference à une référence simple, même dans une fonction de modèle avec un paramètre de référence de transfert, et le nom d'objet encapsulé dans std :: forward.
Jive Dadson
Vous avez pu arriver au type sans créer de nouvelles roues!
Steven Eckhoff
1
Cette technique est également décrite dans «Point 4: savoir comment afficher les types déduits» dans Effective Modern C ++
lenkite
54

N'oubliez pas d'inclure <typeinfo>

Je pense que vous faites référence à l'identification du type d'exécution. Vous pouvez réaliser ce qui précède en faisant.

#include <iostream>
#include <typeinfo>

using namespace std;

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

Selon la solution de Howard , si vous ne voulez pas le nombre magique, je pense que c'est un bon moyen de représenter et semble intuitif:

#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;
}
康 桓 瑋
la source
4
C'est une grande distillation des efforts des dernières versions C ++ en quelque chose de court et de doux. +1.
einpoklum
1
C'est aussi mon préféré!
Howard Hinnant
1
Voici une fonction similaire que j'utilise, qui détecte automatiquement le suffixe / préfixe: stackoverflow.com/questions/1055452/…
HolyBlackCat
22

Notez que les noms générés par la fonctionnalité RTTI de C ++ ne sont pas portables. Par exemple, la classe

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

aura les noms suivants:

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

Vous ne pouvez donc pas utiliser ces informations pour la sérialisation. Mais toujours, la propriété typeid (a) .name () peut toujours être utilisée à des fins de journal / débogage

paercebal
la source
19

Vous pouvez utiliser des modèles.

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

Dans l'exemple ci-dessus, lorsque le type ne correspond pas, il affichera "inconnu".

pseudo
la source
3
N'imprimera-t-il pas "int" pour les shorts et les caractères? Et "flotter" pour les doubles?
gartenriese
1
La spécialisation @gartenriese n'a pas cet inconvénient. Car doubleil compilerait la version non spécialisée de la fonction modèle plutôt que de faire une conversion de type implicite pour utiliser la spécialisation: cpp.sh/2wzc
chappjc
1
@chappjc: Honnêtement, je ne sais pas pourquoi j'ai demandé cela à l'époque, c'est assez clair pour moi maintenant. Mais merci d'avoir répondu à une question d'un an quand même!
gartenriese
2
@gartenriese Je pensais autant, mais "Internet" pourrait avoir la même question à un moment donné.
chappjc
18

Comme mentionné, typeid().name()peut renvoyer un nom mutilé. Dans GCC (et certains autres compilateurs), vous pouvez contourner ce problème avec le code suivant:

#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
la source
10

Vous pouvez utiliser une classe de traits pour cela. Quelque chose comme:

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

La DECLARE_TYPE_NAMEdéfinition existe pour vous faciliter la vie en déclarant cette classe de traits pour tous les types dont vous vous attendez.

Cela peut être plus utile que les solutions impliquant, typeidcar vous pouvez contrôler la sortie. Par exemple, utiliser typeidfor long longsur mon compilateur donne "x".

Greg Hewgill
la source
10

En C ++ 11, nous avons decltype. Il n'y a aucun moyen dans c ++ standard d'afficher le type exact de variable déclaré en utilisant decltype. Nous pouvons utiliser boost typeindex ie type_id_with_cvr(cvr signifie const, volatile, reference) pour imprimer le type comme ci-dessous.

#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
la source
1
serait-il plus simple d'utiliser une fonction d'aide:template<typename T> void print_type(T){cout << "type T is: "<< type_id_with_cvr<T>().pretty_name()<< '\n';}
r0ng
6

Vous pouvez également utiliser c ++ filt avec l'option -t (type) pour démêler le nom du type:

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

Testé sur Linux uniquement.

Alan
la source
1
Enfer moche mais fera ce dont j'ai besoin. Et bien plus petit que les autres solutions. Fonctionne sur Mac btw.
Marco Luglio
6

Howard Hinnant a utilisé des nombres magiques pour extraire le nom du type. 康 桓 瑋 préfixe et suffixe de chaîne suggérés. Mais le préfixe / suffixe continue de changer. Avec "probe_type" type_name calcule automatiquement les tailles de préfixe et de suffixe pour "probe_type" pour extraire le nom du type:

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

Production

gcc 10.0.0 20190919 Wandbox:

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

clang 10.0.0 Wandbox:

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

VS 2019 version 16.3.3:

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

Les autres réponses impliquant RTTI (typeid) sont probablement ce que vous voulez, tant que:

  • vous pouvez vous permettre la surcharge de mémoire (qui peut être considérable avec certains compilateurs)
  • les noms de classe renvoyés par votre compilateur sont utiles

L'alternative, (similaire à la réponse de Greg Hewgill), est de construire un tableau de traits au moment de la compilation.

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

Sachez que si vous encapsulez les déclarations dans une macro, vous aurez du mal à déclarer les noms des types de modèles prenant plus d'un paramètre (par exemple std :: map), en raison de la virgule.

Pour accéder au nom du type d'une variable, il vous suffit de

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}
James Hopkin
la source
1
Bon point sur la virgule, je savais qu'il y avait une raison pour laquelle les macros étaient une mauvaise idée mais je n'y avais pas pensé à l'époque!
Greg Hewgill
2
static const char * value = "Wibble"; vous ne pouvez pas faire ce compagnon :)
Johannes Schaub - litb
5

Une solution plus générique sans surcharge de fonction que la précédente:

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

Ici, MyClass est une classe définie par l'utilisateur. D'autres conditions peuvent également être ajoutées ici.

Exemple:

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

Production:

int
String
MyClass
Jahid
la source
5

J'aime la méthode de Nick, un formulaire complet pourrait être celui-ci (pour tous les types de données de 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*"; }
Jahid
la source
2
(i) cela ne fonctionnera pas pour d'autres types (c'est-à-dire pas générique du tout); (ii) un gonflement inutile du code; (iii) la même chose peut être (correctement) effectuée avec typeidou decltype.
edmz
2
Vous avez raison, mais cela couvre tous les types de base ... et c'est ce dont j'ai besoin en ce moment ..
Jahid
2
Pouvez-vous me dire comment vous feriez avec decltype,
Jahid
1
S'il s'agit d'un test à la compilation, vous pouvez utiliser std :: is_same <T, S> et decltype pour obtenir T et S.
edmz
4

Comme je mets au défi, j'ai décidé de tester jusqu'où peut-on aller avec la supercherie de modèle indépendante de la plate-forme (espérons-le).

Les noms sont assemblés complètement au moment de la compilation. (Ce qui typeid(T).name()ne peut pas être utilisé, vous devez donc fournir explicitement des noms pour les types non composés. Sinon, les espaces réservés seront affichés à la place.)

Exemple d'utilisation:

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

TYPE_NAME(std::string)

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

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

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

Code:

#include <type_traits>
#include <utility>

static constexpr std::size_t max_str_lit_len = 256;

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

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

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

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

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

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

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

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


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

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

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

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

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

template <typename T> struct type_name_impl;

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

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

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

#define TYPE_NAME(t) template <> struct primitive_type_name<t> {using value = STR_LIT(#t);};
HolyBlackCat
la source
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;
}

Production:

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
Loup gris
la source
2

Comme expliqué par Scott Meyers dans Effective Modern C ++,

Les appels vers std::type_info::namene sont pas garantis de retourner n'importe quoi sensible.

La meilleure solution consiste à laisser le compilateur générer un message d'erreur lors de la déduction de type, par exemple,

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

Le résultat sera quelque chose comme ça, selon les compilateurs,

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;

Par conséquent, nous apprenons que xle type est int, yle type estconst int*

Milo Lu
la source
0

Pour tous ceux qui visitent encore, j'ai récemment eu le même problème et j'ai décidé d'écrire une petite bibliothèque basée sur les réponses de ce post. Il fournit des noms de type constexpr et des indices de type et est testé sur Mac, Windows et Ubuntu.

Le code de la bibliothèque est ici: https://github.com/TheLartians/StaticTypeInfo

Lars Melchior
la source