web-dev-qa-db-fra.com

C ++ Récupère le nom du type dans le modèle

J'écris quelques classes de modèles pour analyser certains fichiers de données de texte, et en tant que tel, il est probable que la grande majorité des erreurs d'analyse seront dues à des erreurs dans le fichier de données, qui ne sont pour la plupart pas écrites par les programmeurs, et ont donc besoin un joli message expliquant pourquoi l'application n'a pas pu être chargée, par exemple quelque chose comme:

Erreur lors de l'analyse de example.txt. La valeur ("notaninteger") de la clé [MySectiom] n'est pas un entier valide

Je peux déterminer le nom du fichier, de la section et des clés à partir des arguments passés à la fonction de modèle et aux variables membres de la classe, mais je ne sais pas comment obtenir le nom du type vers lequel la fonction de modèle tente de se convertir.

Mon code actuel ressemble, avec des spécialisations pour les chaînes simples et autres:

template<typename T> T GetValue(const std::wstring &section, const std::wstring &key)
{
    std::map<std::wstring, std::wstring>::iterator it = map[section].find(key);
    if(it == map[section].end())
        throw ItemDoesNotExist(file, section, key)
    else
    {
        try{return boost::lexical_cast<T>(it->second);}
        //needs to get the name from T somehow
        catch(...)throw ParseError(file, section, key, it->second, TypeName(T));
    }
}

Je préfère ne pas avoir à faire de surcharges spécifiques pour chaque type que les fichiers de données pourraient utiliser, car il y en a beaucoup ...

J'ai également besoin d'une solution qui n'entraîne aucune surcharge d'exécution à moins qu'une exception ne se produise, c'est-à-dire qu'une solution de temps de compilation complète est ce que je veux car ce code est appelé des tonnes de temps et les temps de chargement sont déjà un peu longs.

EDIT: Ok c'est la solution que j'ai trouvée:

J'ai un types.h contenant les éléments suivants

#pragma once
template<typename T> const wchar_t *GetTypeName();

#define DEFINE_TYPE_NAME(type, name) \
    template<>const wchar_t *GetTypeName<type>(){return name;}

Ensuite, je peux utiliser la macro DEFINE_TYPE_NAME dans des fichiers cpp pour chaque type que je dois traiter (par exemple dans le fichier cpp qui a défini le type pour commencer).

L'éditeur de liens est alors en mesure de trouver la spécialisation de modèle appropriée tant qu'il a été défini quelque part, ou de lancer une erreur de l'éditeur de liens sinon afin que je puisse ajouter le type.

67
Fire Lancer

La solution de Jesse Beder est probablement la meilleure, mais si vous n'aimez pas les noms que typeid vous donne (je pense que gcc vous donne des noms mutilés par exemple), vous pouvez faire quelque chose comme:

template<typename T>
struct TypeParseTraits;

#define REGISTER_PARSE_TYPE(X) template <> struct TypeParseTraits<X> \
    { static const char* name; } ; const char* TypeParseTraits<X>::name = #X


REGISTER_PARSE_TYPE(int);
REGISTER_PARSE_TYPE(double);
REGISTER_PARSE_TYPE(FooClass);
// etc...

Et puis utilisez-le comme

throw ParseError(TypeParseTraits<T>::name);

MODIFIER:

Vous pouvez également combiner les deux, modifier name pour être une fonction qui appelle par défaut typeid(T).name() et ne se spécialiser que pour les cas où cela n'est pas acceptable.

35
Logan Capaldo

La solution est

typeid(T).name()

qui retourne std :: type_info .

60
Jesse Beder

typeid(T).name() est défini par l'implémentation et ne garantit pas une chaîne lisible par l'homme.

Lecture cppreference.com :

Renvoie une chaîne de caractères terminée par une valeur nulle définie par l'implémentation contenant le nom du type. Aucune garantie n'est donnée, en particulier, la chaîne retournée peut être identique pour plusieurs types et changer entre les invocations d'un même programme.

...

Avec des compilateurs tels que gcc et clang, la chaîne retournée peut être redirigée via c ++ filt -t pour être convertie sous une forme lisible par l'homme.

Mais dans certains cas, gcc ne retourne pas la bonne chaîne. Par exemple, sur ma machine, j'ai gcc avec -std=c++11 Et à l'intérieur de la fonction de modèle typeid(T).name() renvoie "j" Pour "unsigned int". C'est ce qu'on appelle un nom mutilé. Pour obtenir le nom du type réel, utilisez la fonction abi :: __ cxa_demangle () (gcc uniquement):

#include <string>
#include <cstdlib>
#include <cxxabi.h>

template<typename T>
std::string type_name()
{
    int status;
    std::string tname = typeid(T).name();
    char *demangled_name = abi::__cxa_demangle(tname.c_str(), NULL, NULL, &status);
    if(status == 0) {
        tname = demangled_name;
        std::free(demangled_name);
    }   
    return tname;
}
40
Bunkar

Comme mentionné par Bunkar typeid (T) .name est défini par l'implémentation.

Pour éviter ce problème, vous pouvez utiliser la bibliothèque Boost.TypeIndex .

Par exemple:

boost::typeindex::type_id<T>().pretty_name() // human readable
18
Andrey

La réponse de Logan Capaldo est correcte mais peut être marginalement simplifiée car il n'est pas nécessaire de spécialiser la classe à chaque fois. On peut écrire:

// in header
template<typename T>
struct TypeParseTraits
{ static const char* name; };

// in c-file
#define REGISTER_PARSE_TYPE(X) \
    template <> const char* TypeParseTraits<X>::name = #X

REGISTER_PARSE_TYPE(int);
REGISTER_PARSE_TYPE(double);
REGISTER_PARSE_TYPE(FooClass);
// etc...

Cela vous permet également de placer les instructions REGISTER_PARSE_TYPE dans un fichier C++ ...

9
rhomu

Pour reformuler la réponse d'Andrey:

La bibliothèque Boost TypeIndex peut être utilisée pour imprimer les noms des types.

À l'intérieur d'un modèle, cela pourrait se lire comme suit

#include <boost/type_index.hpp>
#include <iostream>

template<typename T>
void printNameOfType() {
    std::cout << "Type of T: " 
              << boost::typeindex::type_id<T>().pretty_name() 
              << std::endl;
}
8
chrisb2244

Je le laisse juste là. Si quelqu'un en a encore besoin, vous pouvez utiliser ceci:

template <class T>
bool isString(T* t) { return false;  } // normal case returns false

template <>
bool isString(char* t) { return true; }  // but for char* or String.c_str() returns true
.
.
.

Cela ne verra que le type et non le GET et seulement pour 1 type ou 2.

1
Xar

Si vous souhaitez un joli_nom, la solution de Logan Capaldo ne peut pas gérer une structure de données complexe: REGISTER_PARSE_TYPE(map<int,int>) et typeid(map<int,int>).name() me donne un résultat de St3mapIiiSt4lessIiESaISt4pairIKiiEEE

Il existe une autre réponse intéressante en utilisant unordered_map ou map provient de https://en.cppreference.com/w/cpp/types/type_index .

#include <iostream>
#include <unordered_map>
#include <map>
#include <typeindex>
using namespace std;
unordered_map<type_index,string> types_map_;

int main(){
    types_map_[typeid(int)]="int";
    types_map_[typeid(float)]="float";
    types_map_[typeid(map<int,int>)]="map<int,int>";

    map<int,int> mp;
    cout<<types_map_[typeid(map<int,int>)]<<endl;
    cout<<types_map_[typeid(mp)]<<endl;
    return 0;
}
1
Voyager