554 votes

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

Par exemple:

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

Résultat attendu:

int

2 votes

Voici un résumé de la solution longue de Howard, mais implémentée avec un macro hérétique d'une seule ligne : #define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL). Si vous avez besoin de support multiplateforme : Utilisez #ifdef, #else, #endif pour fournir des macros pour d'autres plateformes comme MSVC.

0 votes

Avec une exigence plus explicite lisible par l'homme : stackoverflow.com/questions/12877521/…

3 votes

Si vous utilisez cela uniquement pour le débogage, vous voudrez peut-être envisager template void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }. Ensuite, en utilisant par exemple print_T(); affichera void print_T() [T = const int *const **] à l'exécution et conservera tous les qualificateurs (fonctionne dans GCC et Clang).

693voto

Howard Hinnant Points 59526

Mise à jour de C++11 à une très vieille question : Imprimer le type de variable en C++.

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

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

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

Non.

Mais l'outil qui le fera n'est pas si compliqué. Et c'est cet outil que j'utilise pour répondre à cette question. Je vais comparer et contraster ce nouvel outil avec typeid(a).name() . Et ce nouvel outil est en fait construit au dessus de typeid(a).name() .

La question fondamentale :

typeid(a).name()

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

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

Pour moi, des sorties :

i

et je devine sur les sorties MSVC :

int

C'est-à-dire le const est parti. Il ne s'agit pas d'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 suit :

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

et pour moi des sorties :

int const

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

La solution C++11

J'utilise __cxa_demangle pour les plates-formes non-MSVC, comme le recommande la Commission européenne. ipapadop dans sa réponse aux types de démêlés. Mais sur MSVC, je fais confiance à typeid pour démêler les noms (non testé). Et ce noyau est enveloppé autour de quelques tests simples qui détectent, restaurent et rapportent les cv-qualificateurs 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 résultats

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) y decltype((i)) . Le premier est le type de déclaration de i . Ce dernier est le "type" de la expression i . (les expressions n'ont jamais de type de référence, mais par convention decltype représente des expressions de type lvalue avec des références de type lvalue).

Ainsi, cet outil est un excellent moyen d'apprendre à connaître decltype en plus de l'exploration et du débogage de votre propre code.

En revanche, si je devais construire ceci juste sur typeid(a).name() sans ajouter les qualificatifs ou références cv perdus, le résultat serait le suivant :

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 chaque qualificatif de cv est supprimé.

Mise à jour du C++14

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

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

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

Jamboree réponse ne met pas tout à plat pour VS, et je modifie un peu son code. Mais puisque cette réponse reçoit beaucoup de vues, prenez le temps d'y aller et d'upvoter sa réponse, sans quoi, 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 déclenchera le retour automatique sur le constexpr si vous êtes toujours coincé dans l'ancien C++11. Et si vous peignez sur le mur de la caverne avec C++98/03, les noexcept est également sacrifié.

Mise à jour C++17

Dans les commentaires ci-dessous Lyberta souligne que la nouvelle std::string_view peut 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 pour VS grâce au très bon travail de détective de Jive Dadson dans les commentaires ci-dessous.

Mise à jour :

N'oubliez pas de consulter cette réécriture ci-dessous ce qui élimine les chiffres magiques illisibles dans ma dernière formulation.

4 votes

VS 14 CTP a imprimé les types corrects, j'ai juste eu à ajouter une ligne #include .

3 votes

Pourquoi templatestd::string type_name()? Pourquoi ne pas passer un type en argument?

2 votes

Je pense que ma justification était que parfois je n'avais que un type (comme un paramètre de modèle déduit), et je ne voulais pas devoir en construire artificiellement un pour obtenir le type (bien que de nos jours declval ferait l'affaire).

256voto

Konrad Rudolph Points 231505

Essayer :

#include 

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

Vous devrez peut-être activer RTTI dans les options de votre compilateur 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 mangle de nom ou de tout ce qui se situe entre les deux.

6 votes

Pourquoi la chaîne renvoyée par la fonction nom() est-elle définie par l'implémentation ?

6 votes

@PravasiMeet Aucune bonne raison, à ma connaissance. Le comité ne voulait tout simplement pas contraindre les implémenteurs de compilateurs dans des directions techniques particulières - probablement une erreur, rétrospectivement.

2 votes

Y a-t-il un drapeau que je pourrais utiliser pour activer le RTTI? Peut-être pourriez-vous rendre votre réponse inclusive.

117voto

NickV Points 156

Très laid mais fait l'affaire si vous ne voulez que des informations au moment de la compilation (par exemple pour le débogage) :

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

Renvoie :

La compilation s'est terminée avec des erreurs :
source.cpp: Dans la fonction 'int main()':
source.cpp:5:19: erreur : 'foo' n'est pas un membre de 'std::tuple'

11 votes

Seul c++ pourrait rendre cela si difficile (imprimer le type d'une variable automatique au moment de la compilation). SEULEMENT C++.

3 votes

@KarlP eh bien pour être juste c'est un peu compliqué, cela fonctionne aussi :) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;

0 votes

Sur VC++17, cela réduit une référence rvalue à une référence simple, même dans une fonction modèle avec un paramètre de forwarding-reference, et le nom de l'objet est enveloppé dans std::forward.

60voto

mdec Points 2687

N'oubliez pas d'inclure

Je crois que vous faites référence à l'identification du type d'exécution. Vous pouvez y parvenir en faisant .

#include 
#include 

using namespace std;

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

26voto

paercebal Points 38526

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

MyNamespace::CMyContainer

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 cette information pour la sérialisation. Mais néanmoins, la propriété typeid(a).name() peut toujours être utilisée à des fins de journalisation/débogage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X