URL: https://linuxfr.org/users/serge_ss_paille/journaux/switch-chaine-constante-et-c Title: Switch, chaîne constante et c++ Authors: serge_sans_paille Date: 2016年08月31日T10:48:35+02:00 License: CC By-SA Tags: c++14 et c++ Score: 16 Salut 'Nal (ah non, ça marche pas) Lors d'une discussion avec un collègue, ça chambrait [^1] gentillement sur l'absence de switch sur des chaînes constantes en C++. Ça a déclenché quelques recherches de mon côté, et voilà ce que j'ai pu trouver (les codes qui suivent ne sont que le reflet de ce que j'ai pu lire en ligne, rien de nouveau sous le soleil brestois) Approche LLVM : StringSwitch ============================ Source : http://llvm.org/docs/doxygen/html/StringSwitch_8h_source.html L'idée est de simuler une cascade de ```c++ if(str == "str0") return value0; else if(str == "str1") return value1; else return value2; ``` La solution est plutôt amusante, je vous mets une version simplifiée ci-dessous ```c++ template class StringSwitch { Holder const * holder_; std::string const value_; public: StringSwitch(std::string value) : holder_(nullptr), value_(value) {} template StringSwitch& Case(char const (&v)[N], Holder const& h) { if(!holder_ && value_.size() == N- 1 && value_ == v) holder_ = &h; return *this; } Holder const& Default(Holder const& h) { if(holder_) return *holder_; return h; } }; int main(int argc, char **argv) { auto && msg = StringSwitch(argc == 1 ? "--help" : argv[1]) .Case("hello", "world") .Case("--help", "no help can be found in this world") .Default(""); if(msg.empty()) return 1; else { std::cout << msg << std::endl; return 0; } } ``` Ça coûte à peine plus que la cascade de if (quelques comparaison à un pointeur nul en plus) et c'est plutôt élégant. Bien sûr ce n'est qu'un petit exemple, il faut se référer au code complet pour saisir les subtilités ! Pour réduire un peu le coût de comparaison (on en $\mathcal{O}(n \times m)$ dans le pire des cas, où n est le nombre de case et m la taille moyenne d'une chaîne, on pourrait utiliser une `std::map` que l'on marquerait `static const`. Malheureusement, le constructeur de `std::map` n'est pas `constexpr`, donc on aurait une initialisation à l'exécution. On pourrait, pour un compromis différent, utiliser une `std::unordered_map` aussi. Et pour les chagrins qui veulent du code dans leur `case:` on peut utiliser une `std::function` (mais il y a un coût caché). Un truc du genre : ```c++ #include
    #include int main(int argc, char **argv) { static const std::map> Switch = { {"hello", []() { std::cout << "world" << std::endl; return 1; } }, {"--help", []() { std::cout << "no help can be found in this world" << std::endl; return 1; } } }; auto where = Switch.find(argc == 1 ? "--help" : argv[1]); return where == Switch.end() ? 1 : where->second(); } ``` Approche C++14 ============== Cette approche est fortement inspirée de ce post https://dev.krzaq.cc/post/switch-on-strings-with-c11/ L'idée est de reposer sur les `constexpr` pour calculer un hash à compile time, et donc traduire une chaîne en un entier, valide pour un switch. Un peu de sucre syntaxique avec les *user defined literals* et on obtient ça : ```c++ constexpr unsigned long hash(char const* str) { unsigned long hash = 5381; int c = 0; while (c = *str++) hash = hash * 33 + c; return hash; } constexpr auto operator ""_h(char const str[], size_t) { return hash(str); } int main(int argc, char **argv) { switch(hash(argc == 1 ? "--help" : argv[1])) { case "hello"_h: std::cout << "world" << std::endl; return 0; case "--help"_h: std::cout << "no help can be found in this world" << std::endl; return 0; default: return 1; }; } ``` Malheureusement l'implémentation de `std::hash` n'est pas constexpr, sinon on aurait pu éviter de fournir une implem miteuse[^2] mais respectant la constexpritude. On est pas loin du `switch` sur des litéraux, et ça coûte un calcul de hash + le saut. Conclusion ========== Comme souvent, on a le choix en C++, et c'est ce que j'aime dans ce langage (et ce qui en fait un langage difficile à utiliser en production). Mais la monotonie n'est pas prête de venir toquer à la porte ! [^1]: petit conseil : si ça chambre, aère ! [^2]: http://www.pokepedia.fr/Mimitoss

    AltStyle によって変換されたページ (->オリジナル) /