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

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

Mise à jour C++11 d'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ù a est un nom de variable.

Maintenant en C++11 nous avons decltype(x) , 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) et decltype((a)) seront généralement de types différents (et pour de bonnes et compréhensibles raisons une fois ces raisons exposées).

Est-ce que notre fidèle typeid(a).name() nous aider à 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 pour répondre à cette question. Je vais comparer et opposer ce nouvel outil à typeid(a).name() . Et ce nouvel outil est en fait construit sur 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, sorties :

i

et je suppose 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.

Voici ce que je recommande :

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'apprécie les commentaires de ceux qui le font.

La solution C++11

J'utilise __cxa_demangle pour les plates-formes non-MSVC, comme recommandé par ipapadop dans sa réponse aux types de démantèlement. Mais sur MSVC, je fais confiance à typeid pour démêler les noms (non testé). Et ce noyau est entouré de 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 résultats

Avec cette solution, je peux faire ceci :

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 le résultat 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 par convention decltype représente des expressions lvalue avec des références lvalue).

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

En revanche, si je devais construire ceci uniquement sur typeid(a).name() , sans rajouter les qualificatifs cv ou les références perdus, le résultat 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. Chaque référence et qualificatif 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 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. 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 du langage (comme les lambdas).

La réponse de Jamboree n'explique 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 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 s'arrêtera automatiquement sur le constexpr si vous êtes toujours bloqué dans l'ancien C++11. Et si vous peignez sur le mur de la grotte avec C++98/03, le noexcept est également sacrifié.

Mise à jour C++17

Dans les commentaires ci-dessous, Lyberta souligne que le nouveau 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 beau travail de détective de Jive Dadson dans les commentaires ci-dessous.

Mise à jour :

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


Essayez :

#include <typeinfo>

// …
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 this dépend du compilateur. Il peut s'agir d'un nom de type brut ou d'un symbole de mutilation de nom ou de n'importe quoi entre les deux.


Très moche mais fait l'affaire si vous ne voulez que des informations sur le temps de compilation (par exemple pour le débogage) :

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

Renvoie :

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