web-dev-qa-db-fra.com

Chaînage des arguments de modèles

Est-il possible en C++ d'enchaîner les arguments du template? J'ai essayé ceci:

#define STRINGIFY(x) #x

template <typename T>
struct Stringify
{
     Stringify()
     {
          cout<<STRINGIFY(T)<<endl;
     }
};

int main() 
{
     Stringify<int> s;
}

Mais ce que je reçois est un «T» et non un «int». Il semble que les préprocesseurs entrent en jeu avant la résolution du modèle.

Y a-t-un autre moyen de faire ça?

Le prétraitement peut-il avoir lieu après la résolution du modèle? (Le compilateur est VC++).

33
sold

Tu pourrais essayer

 typeid(T).name()

Edit : Corrigé en fonction des commentaires.

32
eduffy

Vous pouvez utiliser un modèle de magie.

#include <iostream>

template <typename T>
struct TypeName { static const char *name; };

template <typename T>
const char *TypeName<T>::name = "unknown";

template <>
const char *TypeName<int>::name = "int";

template <typename T>
struct Stringify
{
     Stringify()
     {
          std::cout << TypeName<T>::name << std::endl;
     }
};

int main() 
{
     Stringify<int> s;
}

Ceci a un avantage sur RTTI (c'est-à-dire typeinfo) - il est résolu lors de la compilation; et désavantage - vous devez fournir vous-même les informations de type (à moins qu'une bibliothèque ne le fasse déjà à mon insu; peut-être même quelque chose dans Boost).

Ou, comme Matrin York suggéré dans les commentaires, utilisez plutôt des modèles de fonction inline:

template <typename T>
inline const char* typeName(void) { return "unknown"; }

template <>
inline const char* typeName<int>(void) { return "int"; }

// ...
std::cout << typeName<T>() << std::endl;

Toutefois, si vous avez besoin de stocker davantage d'informations sur ce type particulier, les modèles de classe seront probablement meilleurs.

23
Cat Plus Plus

Votre code ne fonctionne pas car le préprocesseur, chargé de rechercher et de développer les macros que vous utilisez dans votre code, n'est pas conscient du langage lui-même. C'est juste un analyseur de texte. Il trouve que STRINGIFY (T) dans le modèle de fonction même et le développe, bien avant que vous ne donniez un type à ce modèle. En fin de compte, vous obtiendrez toujours "T" au lieu du nom de fichier attendu, malheureusement.

Comme suggéré par litb , j'ai (mal) implémenté ce modèle de fonction `getTypeName 'qui renvoie le nom de type que vous lui transmettez:

#include <iostream>

template <typename _Get_TypeName>
const std::string &getTypeName()
{
    static std::string name;

    if (name.empty())
    {
        const char *beginStr = "_Get_TypeName =";
        const size_t beginStrLen = 15; // Yes, I know...
                                       // But isn't it better than strlen()?

        size_t begin,length;
        name = __PRETTY_FUNCTION__;

        begin = name.find(beginStr) + beginStrLen + 1;
        length = name.find("]",begin) - begin;
        name = name.substr(begin,length);
    }

    return name;
}

int main()
{
    typedef void (*T)(int,int);

    // Using getTypeName()
    std::cout << getTypeName<float>() << '\n';
    std::cout << getTypeName<T>() << '\n'; // You don't actually need the
                                           // typedef in this case, but
                                           // for it to work with the
                                           // typeid below, you'll need it

    // Using typeid().name()
    std::cout << typeid(float).name() << '\n';
    std::cout << typeid(T).name() << '\n';

    return 0;
}

Le code ci-dessus a pour résultat la sortie suivante avec GCC flag -s ("effacer tous les symboles de binaire") activé:

float
void (*)(int, int)
f
PFviiE

Donc, voyez-vous, getTypename () fait un travail plutôt meilleur, au prix de cette analyse de fugly string (je sais, c'est sacrément moche).

Quelques points à prendre en compte:

  • Le code est uniquement GCC. Je ne sais pas comment le porter sur un autre compilateur. Il est probable que seuls quelques-uns d'entre eux disposent d'une telle capacité à produire de si jolis noms de fonctions, et d'après ce que j'ai cherché, MSVC++ n'en a pas, si vous vous posez la question.
  • Si, dans une nouvelle version, GCC formate différemment le __PRETTY_FUNCTION__, la correspondance de chaîne peut se rompre et vous devrez la réparer. Pour cette même raison, je vous avertis également que getTypeName () pourrait serait bon pour le débogage (et encore, peut-être même pas bon pour cela), mais c'est sûrement mauvais, mauvais et mauvais pour d'autres raisons, telles que la comparaison de deux types dans un modèle ou quelque chose du genre (je ne sais pas, je ne sais pas ce que quelqu'un pourrait penser de…). Utilisez-le uniquement pour le débogage, et de préférence, n'appelez-le pas dans les versions (utilisez des macros pour désactiver), évitez donc d'utiliser __PRETTY_FUNCTION__ et le compilateur ne produira pas la chaîne correspondante.
  • Je ne suis certainement pas un expert et je ne suis pas sûr qu'un type étrange puisse entraîner l'échec de la correspondance de chaîne. J'aimerais demander aux personnes qui lisent ce post de commenter si elles sont au courant d'un tel cas.
  • Le code utilise un std :: string statique. Cela signifie que, si une exception est levée de son constructeur ou de son destructeur, il est impossible qu'elle atteigne un bloc catch et vous obtiendrez une exception non gérée. Je ne sais pas si std :: strings peut le faire, mais méfiez-vous, s'ils le font, vous êtes potentiellement en difficulté. Je l'ai utilisé car il a besoin d'un destructeur pour libérer la mémoire. Vous pouvez implémenter votre propre classe pour cela, cependant, en veillant à ce qu'aucune exception ne soit émise en dehors d'un échec d'allocation (c'est assez fatal, n'est-ce pas? So ...), et renvoyer une simple chaîne de caractères C.
  • Avec typedefs, vous pouvez obtenir des résultats étranges, comme celui-ci (pour une raison quelconque, le site interrompt la mise en forme de cet extrait, et j'utilise donc ce lien coller): http://Pastebin.com/f51b888ad

Malgré ces inconvénients, je voudrais dire que c'est rapide. Pour la deuxième fois, vous rechercherez le même nom de type. Cela coûtera de choisir une référence à un std :: string global contenant le nom. Et, comparé aux méthodes de spécialisation des gabarits suggérées précédemment, il n’ya rien d’autre à déclarer que le gabarit lui-même, il est donc beaucoup plus facile à utiliser.

15
Guilherme Vieira

Non, vous ne pouvez pas travailler sur des types comme s'il s'agissait de variables. Vous pouvez écrire du code qui extrait le typeid () d’un élément et en affiche le nom, mais la valeur obtenue ne sera probablement pas celle que vous attendez (les noms de types ne sont pas normalisés).

Vous pouvez également utiliser des spécialisations de modèles (et certaines magies de macro) pour obtenir une version plus intéressante si le nombre de types que vous souhaitez utiliser est limité:

template <typename T> const char* printtype(); // not implemented

// implement specializations for given types
#define DEFINE_PRINT_TYPE( type ) \
template<>\
const char* printtype<type>() {\
   return #type;\
}
DEFINE_PRINT_TYPE( int );
DEFINE_PRINT_TYPE( double );
// ... and so on
#undef DEFINE_PRINT_TYPE
template <typename T> void test()
{
   std::cout << printtype<T>() << std::endl;
}
int main() {
   test<int>();
   test<double>();
   test<float>(); // compilation error, printtype undefined for float
}

Vous pouvez également combiner les deux versions: implémentez le modèle générique printtype à l'aide de typeinfo, puis indiquez des spécialisations pour les types que vous souhaitez attribuer à des noms plus sophistiqués.

template <typename T>
const char* printtype()
{
   return typeid(T).name();
}

Cela rompt l’un de mes principes fondamentaux d’écriture de code C++: évitez d’utiliser des astuces à la fois dans les fonctionnalités du modèle et dans le préprocesseur.

Une partie de la raison pour laquelle les modèles et la méchanceté qu'ils introduisent dans le langage était une tentative de dissuader les développeurs d’utiliser le pré-processeur. Si vous utilisez les deux, alors les terroristes gagnent.

4
T.E.D.

Si vous utilisez boost/core/demangle.hpp, vous pouvez obtenir une chaîne fiable, lisible par l'homme.

char const * name = typeid(T).name();
boost::core::scoped_demangled_name demangled( name );

std::cout << (demangled.get() ? demangled.get() : "Failed to demangle") << std::endl;
2
Hayden

Voici ce que je fais: j’ai une fonction demangle() (implémentée au-dessus de abi::__cxa_demangle(), que j’appelle avec deux surcharges de fonction de modèle pratique, nameof(), avec le type que je veux stringified ou une instance de celui-ci.

C’est assez compact, je vais donc le reproduire ici dans toute sa splendeur. Dans demangle.hh nous avons:

#pragma once
#include <typeinfo>

namespace terminator {

    /// actual function to demangle an allegedly mangled thing
    char const* demangle(char const* const symbol) noexcept;

    /// convenience function template to stringify a name of a type,
    /// either per an explicit specialization:
    ///     char const* mytypename = terminator::nameof<SomeType>();
    template <typename NameType>
    char const* nameof() {
        try {
            return demangle(typeid(NameType).name());
        } catch (std::bad_typeid const&) {
            return "<unknown>";
        }
    }

    ///  … or as implied by an instance argument:
    ///     char const* myinstancetypename = terminator::nameof(someinstance);
    template <typename ArgType>
    char const* nameof(ArgType argument) {
        try {
            return demangle(typeid(argument).name());
        } catch (std::bad_typeid const&) {
            return "<unknown>";
        }
    }

} /* namespace terminator */

… Et ensuite dans demangle.cpp:

#include "demangle.hh"

#include <cstdlib>
#include <cxxabi.h>
#include <mutex>
#include <memory>

namespace terminator {

    namespace {

        /// define one singular, private, static std::mutex,
        /// to keep the demangler from reentering itself
        static std::mutex mangle_barrier;

        /// define a corresponding private and static std::unique_ptr,
        /// using a delete-expression to reclaim the memory malloc()'ed by
        /// abi::__cxa_demangle() upon its return.
        /// … we use clang pragmas to add flags locally for this to work:
        #pragma clang diagnostic Push
        #pragma clang diagnostic ignored "-Wglobal-constructors"
        #pragma clang diagnostic ignored "-Wexit-time-destructors"
        std::unique_ptr<char, decltype(std::free)&> demangled_name{ nullptr, std::free };
        #pragma clang diagnostic pop

    }

    char const* demangle(char const* const symbol) noexcept {
        if (!symbol) { return "<null>"; }
        std::lock_guard<std::mutex> lock(mangle_barrier);
        int status = -4;
        demangled_name.reset(
            abi::__cxa_demangle(symbol,
                                demangled_name.get(),
                                nullptr, &status));
        return ((status == 0) ? demangled_name.release() : symbol);
    }

} /* namespace terminator */

Pour utiliser cela, je pense que vous devrez créer un lien vers libc++ (ou votre équivalent local) pour utiliser abi::__cxa_demangle(). Ce qui peut être sous-optimal pour l'OP est le fait qu'il s'agisse du démêlage et de la hiérarchisation au moment de l'exécution. Personnellement, j’aimerais bien quelque chose de constexpr- amical à cela, mais comme je souffre d’une grave allergie aux macro-abus, je trouve que c’est la solution la moins déraisonnable à ce problème.

(l'espace de nom terminator est sans importance - j'utilise ce code dans un stacktracer basé sur libunwind appelé à partir du gestionnaire de terminaison - n'hésitez pas à s///g ce jeton)

0
fish2000

dans mon code, j'utilise la double déclaration "terrible" du "nom de classe"

MqFactoryC<MyServer>::Add("MyServer").Default();

parce que c ++ n'est PAS capable d'extraire la chaîne "MyServer" du modèle ... le seul "moyen" de se "débarrasser" de cela ... en utilisant un "wrapper" cpp

#define MQ_CPPSTR(s) #s
#define MqFactoryCAdd(T) MqFactoryC<T>::Add(MQ_CPPSTR(T)).Default()
0
Andreas Otto