7 votes

Est-il possible de vérifier à la compilation la correspondance entre les chaînes de caractères et les nombres entiers ?

J'essaie probablement de réaliser l'impossible, mais StackExchange me surprend toujours, alors n'hésitez pas à tenter votre chance :

J'ai besoin de faire correspondre un nom à un nombre entier. Les noms (environ 2k) sont uniques. Il n'y aura pas d'ajouts ni de suppressions à cette liste et les valeurs ne changeront pas pendant l'exécution.

Les mettre en œuvre en tant que const int me permet de vérifier à la compilation l'existence et le type des variables. Le code est également très clair et verbeux. Les erreurs sont facilement repérées.

Les mettre en œuvre en tant que std::map<std::string, int> me donne une grande flexibilité pour construire les noms à rechercher avec la manipulation de chaînes de caractères. Je peux l'utiliser pour donner des chaînes comme paramètres à des fonctions qui peuvent alors interroger la liste pour des valeurs multiples en ajoutant des préfixes/suffixes à cette chaîne. Je peux également effectuer une boucle sur plusieurs valeurs en créant une partie numérique du nom de la clé à partir de la variable de boucle.

Ma question est la suivante : existe-t-il une méthode permettant de combiner ces deux avantages ? L'absence de vérification à la compilation (en particulier pour l'existence de la clé) tue presque la deuxième méthode pour moi. (D'autant plus que std::map renvoie silencieusement 0 si la clé n'existe pas, ce qui crée des bogues difficiles à trouver). Mais les capacités de bouclage et d'ajout de pré/suffixe sont tellement utiles.

Je préférerais une solution qui n'utilise pas de bibliothèques supplémentaires comme boost, mais n'hésitez pas à les suggérer néanmoins car je pourrais les réimplémenter de toute façon.

Un exemple de ce que je fais avec la carte :

void init(std::map<std::string, int> &labels)
{        
  labels.insert(std::make_pair("Bob1" , 45 ));
  labels.insert(std::make_pair("Bob2" , 8758 ));
  labels.insert(std::make_pair("Bob3" , 436 ));
  labels.insert(std::make_pair("Alice_first" , 9224 ));
  labels.insert(std::make_pair("Alice_last" , 3510 ));
}

int main() 
{      
  std::map<std::string, int> labels;
  init(labels);

  for (int i=1; i<=3; i++)
  {
    std::stringstream key;
    key << "Bob" << i; 
    doSomething(labels[key.str()]);
  }

  checkName("Alice");
}

void checkName(std::string name)
{
  std::stringstream key1,key2;
  key1 << name << "_first";
  key2 << name << "_last";
  doFirstToLast(labels[key1.str()], labels[key2.str()]);
}

Un autre objectif est que le code présenté dans le main() reste aussi simple et verbeuse que possible. (Elle doit être comprise par les non-programmeurs.) La fonction init() sera générée par certains outils. La fonction doSomething(int) sont fixes, mais je peux écrire des fonctions enveloppantes autour d'elles. Des aides comme checkName() peuvent être plus complexes, mais doivent être facilement déboguables.

1voto

nickie Points 3226

Je ne suis pas sûr de comprendre toutes vos exigences, mais que diriez-vous de quelque chose comme ceci, sans utiliser std::map . Je suppose que vous avez trois chaînes, "FIRST", "SECOND" et "THIRD", que vous voulez faire correspondre à 42, 17 et 37. que vous voulez faire correspondre à 42, 17 et 37, respectivement.

#include <stdio.h>

const int m_FIRST = 0;
const int m_SECOND = 1;
const int m_THIRD = 2;

const int map[] = {42, 17, 37};

#define LOOKUP(s) (map[m_ ## s])

int main ()
{
  printf("%d\n", LOOKUP(FIRST));
  printf("%d\n", LOOKUP(SECOND));
  return 0;
}

L'inconvénient est qu'il n'est pas possible d'utiliser des chaînes de caractères variables avec LOOKUP . Mais maintenant, vous pouvez itérer sur les valeurs.

1voto

Steve Jessop Points 166970

Peut-être quelque chose comme ceci (non testé) ?

struct Bob {
    static constexpr int values[3] = { 45, 8758, 436 };
};

struct Alice {
    struct first {
        static const int value = 9224;
    };
    struct last {
        static const int value = 3510;
    };
};

template <typename NAME>
void checkName()
{
    doFirstToLast(NAME::first::value, NAME::last::value);
}

...

constexpr int Bob::values[3]; // need a definition in exactly one TU

int main() 
{
    for (int i=1; i<=3; i++)
    {
       doSomething(Bob::values[i]);
    }

    checkName<Alice>();
}

1voto

brian beuning Points 1676

Une façon de mettre en œuvre votre exemple est d'utiliser une énumération et de coller un jeton, comme ceci

enum {
  Bob1 = 45,
  Bob2 = 8758,
  Bob3 = 436,
  Alice_first = 9224,
  Alice_last = 3510
};

#define LABEL( a, b ) ( a ## b )

int main() 
{      

  doSomething( LABEL(Bob,1) );
  doSomething( LABEL(Bob,2) );
  doSomething( LABEL(Bob,3) );
}

void checkName()
{
  doFirstToLast( LABEL(Alice,_first), LABEL(Alice,_last) );
}

La question de savoir si c'est la meilleure solution dépend de l'origine des noms.

Si vous avez besoin de prendre en charge le cas d'utilisation de la boucle for, envisagez alors de

int bob[] = { 0, Bob1, Bob2, Bob3 }; // Values from the enum

int main() 
{      
  for( int i = 1; i <= 3; i++ ) {
    doSomething( bob[i] );
  }
}

0voto

cpp Points 2201

L'utilisation d'une énumération permet de vérifier le temps de compilation et de faire une boucle sur l'énumération :

Comment itérer sur une énumération ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X