Identifiant de la constante de compilation

Compte tenu de ce qui suit:

template class A { public: static const unsigned int ID = ?; }; 

Je veux que l’ID génère un identifiant de compilation unique pour chaque T. J’ai considéré __COUNTER__ et la bibliothèque boost PP, mais j’ai échoué jusqu’à présent. Comment puis-je atteindre cet objective?

Edit: l’ID doit être utilisable comme le cas dans une déclaration de commutateur

Edit2: Toutes les réponses basées sur l’adresse d’une méthode statique ou d’un membre sont incorrectes. Bien qu’ils créent un identifiant unique, ils ne sont pas résolus au moment de la compilation et ne peuvent donc pas être utilisés comme cas d’une instruction switch.

Ceci est suffisant en supposant un compilateur conforme aux normes (en ce qui concerne la règle de définition unique):

 template class A { public: static char ID_storage; static const void * const ID; }; template char A::ID_storage; template const void * const A::ID= &A::ID_storage; 

A partir de la norme C ++ 3.2.5 Une règle de définition [basic.def.odr] (gras souligné):

… Si D est un modèle et est défini dans plusieurs unités de traduction, les quatre dernières exigences de la liste ci-dessus doivent s’appliquer aux noms de la scope englobante du modèle utilisée dans la définition du modèle (14.6.3), et noms dépendants au moment de l’instanciation (14.6.2). Si les définitions de D satisfont à toutes ces exigences, le programme doit se comporter comme s’il y avait une seule définition de D. Si les définitions de D ne satisfont pas à ces exigences, le comportement n’est pas défini.

Ce que j’utilise habituellement c’est ceci:

 template void type_id(){} using type_id_t = void(*)(); 

Comme chaque instanciation de la fonction a sa propre adresse, vous pouvez utiliser cette adresse pour identifier les types:

 // Work at comstack time constexpr type_id_t int_id = type_id; // Work at runtime too std::map types; types[type_id] = 4; types[type_id] = "values"s // Find values auto it = types.find(type_id); if (it != types.end()) { // Found it! } 

Cela semble fonctionner correctement pour moi:

 template class Counted { public: static int id() { static int v; return (int)&v; } }; #include  int main() { std::cout<<"Counted::id()="<::id()<::id()="<::id()< 

Il est possible de générer un temps de compilation HASH à partir d’une chaîne en utilisant le code de cette réponse .

Si vous pouvez modifier le modèle pour inclure un entier supplémentaire et utiliser une macro pour déclarer la variable:

 template struct A { static const int id = ID; }; #define DECLARE_A(x) A 

En utilisant cette macro pour la déclaration de type, le membre id contient un hachage du nom du type. Par exemple:

 int main() { DECLARE_A(int) a; DECLARE_A(double) b; DECLARE_A(float) c; switch(a.id) { case DECLARE_A(int)::id: cout << "int" << endl; break; case DECLARE_A(double)::id: cout << "double" << endl; break; case DECLARE_A(float)::id: cout << "float" << endl; break; }; return 0; } 

Comme le nom du type est converti en chaîne, toute modification du texte du nom de type se traduit par un identifiant différent. Par exemple:

 static_assert(DECLARE_A(size_t)::id != DECLARE_A(std::size_t)::id, ""); 

Un autre inconvénient est la possibilité d'une collision de hachage.

Utilisez l’adresse mémoire d’une fonction statique.

 template class A { public: static void ID() {} }; 

(&(A::ID)) sera différent de (&(A::ID)) et ainsi de suite.

J’ai rencontré ce problème exact récemment. Ma solution:

counter.hpp

 class counter { static int i; static nexti() { return i++; } }; 

Counter.cpp:

 int counter::i = 0; 

templateclass.hpp

 #include "counter.hpp" template  tclass { static const int id; }; template  int tclass::id = counter::nexti(); 

Cela semble fonctionner correctement dans MSVC et GCC, à la seule exception que vous ne pouvez pas l’utiliser dans une instruction switch.

Pour diverses raisons, je suis allé plus loin et j’ai défini une macro de préprocesseur qui crée une nouvelle classe à partir d’un paramètre de nom donné avec un identifiant statique (comme ci-dessus) qui dérive d’une base commune.

Cela ne peut pas être fait. Une adresse à un object statique est la plus proche possible d’un identifiant unique. Cependant, pour pouvoir prendre l’adresse de ces objects (même les intégrales const statiques), ils doivent être définis quelque part. Selon la règle de définition unique, elles doivent être définies dans un fichier CPP, ce qui ne peut être fait car ce sont des modèles. Si vous définissez la statique dans un fichier d’en-tête, chaque unité de compilation aura sa propre version implémentée à des adresses différentes.

Voici une solution possible basée principalement sur des modèles:

 #include #include #include template struct wrapper { using type = T; constexpr wrapper(std::size_t N): N{N} {} const std::size_t N; }; template struct identifier: wrapper... { template constexpr identifier(std::index_sequence): wrapper{I}... {} template constexpr std::size_t get() const { return wrapper::N; } }; template constexpr identifier ID = identifier{std::make_index_sequence{}}; // --- struct A {}; struct B {}; constexpr auto id = ID; int main() { switch(id.get()) { case id.get(): std::cout << "A" << std::endl; break; case id.get(): std::cout << "B" << std::endl; break; } } 

Notez que cela nécessite C ++ 14.

Tout ce que vous avez à faire pour associer des identifiants séquentiels à une liste de types est de fournir cette liste à une variable de modèle, comme dans l'exemple ci-dessus:

 constexpr auto id = ID; 

A partir de là, vous pouvez obtenir l'identifiant donné pour le type donné à l'aide de la méthode get :

 id.get() 

Et c'est tout. Vous pouvez l'utiliser dans une instruction switch comme demandé et comme indiqué dans l'exemple de code.

Notez que, tant que les types sont ajoutés à la liste des classes auxquelles un identifiant numérique est associé, les identificateurs sont les mêmes après chaque compilation et lors de chaque exécution.
Si vous souhaitez supprimer un type de la liste, vous pouvez toujours utiliser les faux types comme espaces réservés, par exemple:

 template struct noLonger { }; constexpr auto id = ID, B>; 

Cela garantira que A n'a plus d'ID associé et que celui qui est donné à B ne changera pas.
Si vous ne voulez pas supprimer définitivement A , vous pouvez utiliser quelque chose comme:

 constexpr auto id = ID, B>; 

Ou peu importe.

Ok ….. donc c’est un hack que j’ai trouvé sur ce site. Ça devrait marcher. La seule chose à faire est d’append un autre paramètre de modèle à votre struct qui prend un compteur “meta-object”. Notez que A avec int , bool et char ont tous des identifiants uniques, mais il n’est pas garanti que int sera 1 et bool sera 2 , etc., car l’ordre dans lequel les templates sont lancés n’est pas nécessairement connu.

Une autre note:

Cela ne fonctionnera pas avec Microsoft Visual C ++

 #include  #include "meta_counter.hpp" template struct A { static const size_t ID = counter::next(); }; int main () { typedef atch::meta_counter counter; typedef A AInt; typedef A AChar; typedef A ABool; switch (ABool::ID) { case AInt::ID: std::cout << "Int\n"; break; case ABool::ID: std::cout << "Bool\n"; break; case AChar::ID: std::cout << "Char\n"; break; } std::cout << AInt::ID << std::endl; std::cout << AChar::ID << std::endl; std::cout << ABool::ID << std::endl; std::cout << AInt::ID << std::endl; while (1) {} } 

Voici meta_counter.hpp :

 // author: Filip Roséen  // source: http://b.atch.se/posts/constexpr-meta-container #ifndef ATCH_META_COUNTER_HPP #define ATCH_META_COUNTER_HPP #include  namespace atch { namespace { template struct meta_counter { using size_type = std::size_t; template struct ident { friend constexpr size_type adl_lookup (ident); static constexpr size_type value = N; }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - template struct writer { friend constexpr size_type adl_lookup (Ident) { return Ident::value; } static constexpr size_type value = Ident::value; }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - template {})> static constexpr size_type value_reader (int, ident) { return N; } template static constexpr size_type value_reader (float, ident, size_type R = value_reader (0, ident ())) { return R; } static constexpr size_type value_reader (float, ident<0>) { return 0; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - template static constexpr size_type value (size_type R = value_reader (0, ident {})) { return R; } template static constexpr size_type next (size_type R = writer>::value) { return R; } }; }} #endif /* include guard */ 

En utilisant ce compteur d’expression constante:

 template  class A { public: static constexpr int ID() { return next(); } }; class DUMMY { }; int main() { std::cout << A::ID() << std::endl; std::cout << A::ID() << std::endl; std::cout << A::ID() << std::endl; std::cout << A::ID() << std::endl; return 0; } 

sortie: (GCC, C ++ 14)

 1 2 3 3 

L'inconvénient est que vous devrez deviner une limite supérieure sur le nombre de classes dérivées pour que le compteur d'expression constante fonctionne.

Si des valeurs non monotones et un intptr_t sont acceptables:

 template struct TypeID { private: static char id_ref; public: static const intptr_t ID; }; template char TypeID::id_ref; template const intptr_t TypeID::ID = (intptr_t)&TypeID::id_ref; 

Si vous devez avoir ints, ou devez avoir des valeurs incrémentées de façon monotone, je pense que l’utilisation de constructeurs statiques est la seule solution:

 // put this in a namespace extern int counter; template class Counter { private: Counter() { ID_val = counter++; } static Counter init; static int ID_val; public: static const int &ID; }; template Counter Counter::init; template int Counter::ID_val; template const int &Counter::ID = Counter::ID_val; // in a non-header file somewhere int counter; 

Notez qu’aucune de ces techniques n’est sûre si vous les partagez entre des bibliothèques partagées et votre application!

Une autre alternative consiste à considérer les Data classe suivantes avec le type champ membre unique et statique:

 template  class Data { public: static const std::type_index type; }; // do [static data member initialization](http://stackoverflow.com/q/11300652/3041008) // by [generating unique type id](http://stackoverflow.com/q/26794944/3041008) template  std::type_index const Data::type = std::type_index(typeid(T)); 

produit la sortie ( MinGWx64-gcc4.8.4 -std=c++11 -O2 )

 printf("%s %s\n", Data::type.name(), Data::type.name()) //prints "if" 

Ce n’est pas exactement un identifiant entier ou une chaîne joliment imprimable, ni un constexpr , mais peut être utilisé comme index dans des conteneurs associatifs ordonnés .
Il semble également fonctionner si l’en Data.h tête Data.h est inclus dans plusieurs fichiers (mêmes valeurs hashCode() ).

J’ai eu un problème similaire il y a quelques mois. Je cherchais une technique pour définir des identifiants identiques pour chaque exécution.
Si cela est une exigence, voici une autre question qui explore plus ou moins le même problème (bien sûr, cela vient avec sa belle réponse).
Quoi qu’il en soit, je n’ai pas utilisé la solution proposée. Il suit une description de ce que j’ai fait à cette époque.


Vous pouvez définir une fonction constexpr comme la suivante:

 static constexpr uint32_t offset = 2166136261u; static constexpr uint32_t prime = 16777619u; constexpr uint32_t fnv(uint32_t partial, const char *str) { return str[0] == 0 ? partial : fnv((partial^str[0])*prime, str+1); } inline uint32_t fnv(const char *str) { return fnv(offset, str); } 

Ensuite, une classe comme celle-ci à partir de laquelle hériter:

 template struct B { static const uint32_t id() { static uint32_t val = fnv(T::identifier); return val; } }; 

Le langage CRTP fait le rest.
Par exemple, vous pouvez définir une classe dérivée comme suit:

 struct C: B { static const char * identifier; }; const char * C::identifier = "ID(C)"; 

Tant que vous fournissez des identificateurs différents pour différentes classes, vous aurez des valeurs numériques uniques pouvant être utilisées pour distinguer les types.

Les identificateurs ne doivent pas nécessairement faire partie des classes dérivées. A titre d’exemple, vous pouvez les fournir au moyen d’un trait:

 template struct trait; template<> struct trait { static const char * identifier; }; // so on with all the identifiers template struct B { static const uint32_t id() { static uint32_t val = fnv(trait::identifier); return val; } }; 

Avantages:

  • Facile à mettre en œuvre.
  • Pas de dépendances
  • Les valeurs numériques sont les mêmes pendant chaque exécution.
  • Les classes peuvent partager le même identifiant numérique si nécessaire.

Désavantages:

  • Sujettes aux erreurs: copier-coller peut rapidement devenir votre pire ennemi.

Il suit un exemple de travail minimal de ce qui a été décrit ci-dessus.
J’ai adapté le code pour pouvoir utiliser la méthode du membre ID dans une instruction switch :

 #include #include #include static constexpr uint32_t offset = 2166136261u; static constexpr uint32_t prime = 16777619u; template constexpr std::enable_if_t<(I == N), uint32_t> fnv(uint32_t partial, const char (&)[N]) { return partial; } template constexpr std::enable_if_t<(I < N), uint32_t> fnv(uint32_t partial, const char (&str)[N]) { return fnv((partial^str[I])*prime, str); } template constexpr inline uint32_t fnv(const char (&str)[N]) { return fnv<0>(offset, str); } template struct A { static constexpr uint32_t ID() { return fnv(T::identifier); } }; struct C: A { static constexpr char identifier[] = "foo"; }; struct D: A { static constexpr char identifier[] = "bar"; }; int main() { constexpr auto val = C::ID(); switch(val) { case C::ID(): break; case D::ID(): break; default: break; } } 

Veuillez noter que si vous voulez utiliser l’ ID dans une expression non constante, vous devez définir quelque part l’ identifier s comme suit:

 constexpr char C::identifier[]; constexpr char D::identifier[]; 

Une fois que vous l’avez fait, vous pouvez faire quelque chose comme ceci:

 int main() { constexpr auto val = C::ID(); // Now, it is well-formed auto ident = C::ID(); // ... } 

Voici un code C ++ qui utilise la macro __DATE__ et __TIME__ pour obtenir des identificateurs uniques pour les types

Format:

 // __DATE__ "??? ?? ????" // __TIME__ "??:??:??" 

C’est une fonction de hachage de mauvaise qualité:

 #define HASH_A 8416451 #define HASH_B 11368711 #define HASH_SEED 9796691 \ + __DATE__[0x0] * 389 \ + __DATE__[0x1] * 82421 \ + __DATE__[0x2] * 1003141 \ + __DATE__[0x4] * 1463339 \ + __DATE__[0x5] * 2883371 \ + __DATE__[0x7] * 4708387 \ + __DATE__[0x8] * 4709213 \ + __DATE__[0x9] * 6500209 \ + __DATE__[0xA] * 6500231 \ + __TIME__[0x0] * 7071997 \ + __TIME__[0x1] * 10221293 \ + __TIME__[0x3] * 10716197 \ + __TIME__[0x4] * 10913537 \ + __TIME__[0x6] * 14346811 \ + __TIME__[0x7] * 15485863 unsigned HASH_STATE = HASH_SEED; unsigned HASH() { return HASH_STATE = HASH_STATE * HASH_A % HASH_B; } 

En utilisant la fonction de hachage:

 template  class A { public: static const unsigned int ID; }; template <> const unsigned int A::ID = HASH(); template <> const unsigned int A::ID = HASH(); template <> const unsigned int A::ID = HASH(); template <> const unsigned int A::ID = HASH(); #include  int main() { std::cout << A::ID << std::endl; std::cout << A::ID << std::endl; std::cout << A::ID << std::endl; std::cout << A::ID << std::endl; } 

Voici une solution pragmatique, si vous êtes d’accord pour écrire une seule ligne supplémentaire DECLARE_ID(type) pour chaque type vous souhaitez utiliser:

  #include  template struct my_id_helper; #define DECLARE_ID(C) template<> struct my_id_helper { enum {value = __COUNTER__ }; } // actually declare ids: DECLARE_ID(int); DECLARE_ID(double); // this would result in a comstack error: redefinition of struct my_id_helper' // DECLARE_ID(int); template class A { public: static const unsigned int ID = my_id_helper::value; }; int main() { switch(A::ID) { case A::ID: std::cout << "it's an int!\n"; break; case A::ID: std::cout << "it's a double!\n"; break; // case A::ID: // error: incomplete type 'my_id_helper' default: std::cout << "it's something else\n"; break; } }