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++).
Tu pourrais essayer
typeid(T).name()
Edit : Corrigé en fonction des commentaires.
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.
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:
__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.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.
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.
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;
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)
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()