web-dev-qa-db-fra.com

Pourquoi l'instruction switch ne peut pas être appliquée sur des chaînes?

Compiler le code suivant et avoir l'erreur de type illegal.

int main()
{
    // Compilation error - switch expression of type illegal
    switch(std::string("raj"))
    {
    case"sda":
    }
}

Vous ne pouvez pas utiliser de chaîne dans switch ou case. Pourquoi? Existe-t-il une solution qui fonctionne correctement pour prendre en charge une logique similaire à celle consistant à activer des chaînes?

180
yesraaj

La raison en est liée au système de types. C/C++ ne supporte pas vraiment les chaînes en tant que type. Cela prend en charge l'idée d'un tableau de caractères constant mais ne comprend pas vraiment complètement la notion de chaîne. 

Pour générer le code d'une instruction switch, le compilateur doit comprendre ce que cela signifie pour deux valeurs égales. Pour des éléments tels que ints et enums, il s'agit d'une comparaison de bits triviale. Mais comment le compilateur doit-il comparer 2 valeurs de chaîne? Sensible à la casse, insensible, conscient de la culture, etc. 

En outre, les instructions de commutateur C/C++ sont généralement générées sous la forme/ tables de branche . Il n’est pas aussi facile de générer une table de branchement pour un commutateur de style chaîne. 

160
JaredPar

Comme mentionné précédemment, les compilateurs aiment créer des tables de consultation qui optimisent les instructions switch à un temps proche de O(1), dans la mesure du possible. Combinez cela avec le fait que le langage C++ n'a pas de type de chaîne - std::string fait partie de la bibliothèque standard et ne fait pas partie du langage en soi.

Je vais proposer une alternative que vous voudrez peut-être envisager, je l'ai déjà utilisée à bon escient. Au lieu de basculer sur la chaîne elle-même, basculez le résultat d'une fonction de hachage qui utilise la chaîne en tant qu'entrée. Votre code sera presque aussi clair que le changement de chaîne si vous utilisez un ensemble prédéterminé de chaînes:

enum string_code {
    eFred,
    eBarney,
    eWilma,
    eBetty,
    ...
};

string_code hashit (std::string const& inString) {
    if (inString == "Fred") return eFred;
    if (inString == "Barney") return eBarney;
    ...
}

void foo() {
    switch (hashit(stringValue)) {
    case eFred:
        ...
    case eBarney:
        ...
    }
}

Il y a un tas d'optimisations évidentes qui suivent à peu près ce que le compilateur C ferait avec une instruction switch ... drôle comment cela se produit.

53
D.Shawley

Vous ne pouvez utiliser que des primitives telles que int, char et enum. La solution la plus simple pour le faire, comme vous voulez, est d'utiliser un enum.

#include <map>
#include <string>
#include <iostream.h>

// Value-Defintions of the different String values
static enum StringValue { evNotDefined,
                          evStringValue1,
                          evStringValue2,
                          evStringValue3,
                          evEnd };

// Map to associate the strings with the enum values
static std::map<std::string, StringValue> s_mapStringValues;

// User input
static char szInput[_MAX_PATH];

// Intialization
static void Initialize();

int main(int argc, char* argv[])
{
  // Init the string map
  Initialize();

  // Loop until the user stops the program
  while(1)
  {
    // Get the user's input
    cout << "Please enter a string (end to terminate): ";
    cout.flush();
    cin.getline(szInput, _MAX_PATH);
    // Switch on the value
    switch(s_mapStringValues[szInput])
    {
      case evStringValue1:
        cout << "Detected the first valid string." << endl;
        break;
      case evStringValue2:
        cout << "Detected the second valid string." << endl;
        break;
      case evStringValue3:
        cout << "Detected the third valid string." << endl;
        break;
      case evEnd:
        cout << "Detected program end command. "
             << "Programm will be stopped." << endl;
        return(0);
      default:
        cout << "'" << szInput
             << "' is an invalid string. s_mapStringValues now contains "
             << s_mapStringValues.size()
             << " entries." << endl;
        break;
    }
  }

  return 0;
}

void Initialize()
{
  s_mapStringValues["First Value"] = evStringValue1;
  s_mapStringValues["Second Value"] = evStringValue2;
  s_mapStringValues["Third Value"] = evStringValue3;
  s_mapStringValues["end"] = evEnd;

  cout << "s_mapStringValues contains "
       << s_mapStringValues.size()
       << " entries." << endl;
}

Code écrit de Stefan Ruck le 25 juillet 2001.

29
MarmouCorp

Mise à jour C++ 11 apparemment pas de @MarmouCorp ci-dessus mais http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-Son-Strings-in-C.htm

Utilise deux mappes pour convertir entre les chaînes et l'énumération de classe (mieux qu'une énumération car ses valeurs sont étendues à l'intérieur de celle-ci et inverser la recherche des messages d'erreur Nice).

L'utilisation de static dans le code codeguru est possible avec la prise en charge par le compilateur des listes d'initialisateurs, qui signifie VS 2013 plus. gcc 4.8.1 était acceptable, pas sûr de la compatibilité avec le précédent.

/// <summary>
/// Enum for String values we want to switch on
/// </summary>
enum class TestType
{
    SetType,
    GetType
};

/// <summary>
/// Map from strings to enum values
/// </summary>
std::map<std::string, TestType> MnCTest::s_mapStringToTestType =
{
    { "setType", TestType::SetType },
    { "getType", TestType::GetType }
};

/// <summary>
/// Map from enum values to strings
/// </summary>
std::map<TestType, std::string> MnCTest::s_mapTestTypeToString
{
    {TestType::SetType, "setType"}, 
    {TestType::GetType, "getType"}, 
};

...

std::string someString = "setType";
TestType testType = s_mapStringToTestType[someString];
switch (testType)
{
    case TestType::SetType:
        break;

    case TestType::GetType:
        break;

    default:
        LogError("Unknown TestType ", s_mapTestTypeToString[testType]);
}
12
Dirk Bester

Le problème est que, pour des raisons d'optimisation, l'instruction switch en C++ ne fonctionne que sur les types primitifs et que vous ne pouvez les comparer qu'avec des constantes de temps de compilation.

On peut supposer que la raison de la restriction est que le compilateur est capable d'appliquer une forme d'optimisation en compilant le code en une instruction cmp et un goto où l'adresse est calculée en fonction de la valeur de l'argument au moment de l'exécution. Comme les branches et les boucles ne fonctionnent pas bien avec les processeurs modernes, cela peut constituer une optimisation importante.

Pour éviter cela, je crains que vous n’ayez à recourir à des déclarations.

11
tomjen

std::map + C++ 11 modèle lambdas sans enums

unordered_map pour la O(1)____fonctionnement__: Quelle est la meilleure façon d'utiliser un HashMap en C++?

#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

int main() {
    int result;
    const std::unordered_map<std::string,std::function<void()>> m{
        {"one",   [&](){ result = 1; }},
        {"two",   [&](){ result = 2; }},
        {"three", [&](){ result = 3; }},
    };
    const auto end = m.end();
    std::vector<std::string> strings{"one", "two", "three", "foobar"};
    for (const auto& s : strings) {
        auto it = m.find(s);
        if (it != end) {
            it->second();
        } else {
            result = -1;
        }
        std::cout << s << " " << result << std::endl;
    }
}

Sortie:

one 1
two 2
three 3
foobar -1

Utilisation dans les méthodes avec static

Pour utiliser efficacement ce modèle à l’intérieur des classes, initialisez la mappe lambda statiquement, sinon vous payerez O(n) à chaque fois pour le construire à partir de zéro.

Ici, nous pouvons nous échapper avec l'initialisation {} d'une variable de méthode static: Variables statiques dans les méthodes de classe , mais nous pourrions également utiliser les méthodes décrites dans: constructeurs statiques en C++? J'ai besoin d'initialiser des objets statiques privés

Il était nécessaire de transformer la capture de contexte lambda [&] en un argument, ou cela aurait été indéfini: const statique lambda automatique utilisé avec capture par référence

Exemple produisant le même résultat que ci-dessus:

#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

class RangeSwitch {
public:
    void method(std::string key, int &result) {
        static const std::unordered_map<std::string,std::function<void(int&)>> m{
            {"one",   [](int& result){ result = 1; }},
            {"two",   [](int& result){ result = 2; }},
            {"three", [](int& result){ result = 3; }},
        };
        static const auto end = m.end();
        auto it = m.find(key);
        if (it != end) {
            it->second(result);
        } else {
            result = -1;
        }
    }
};

int main() {
    RangeSwitch rangeSwitch;
    int result;
    std::vector<std::string> strings{"one", "two", "three", "foobar"};
    for (const auto& s : strings) {
        rangeSwitch.method(s, result);
        std::cout << s << " " << result << std::endl;
    }
}

C++

fonction de hachage constexpr:

constexpr unsigned int hash(const char *s, int off = 0) {                        
    return !s[off] ? 5381 : (hash(s, off+1)*33) ^ s[off];                           
}                                                                                

switch( hash(str) ){
case hash("one") : // do something
case hash("two") : // do something
}
6
Nick

En C++ et C, les commutateurs ne fonctionnent que sur les types entiers. Utilisez plutôt une échelle if else. C++ aurait évidemment pu implémenter une sorte d’instruction swich pour les chaînes - je suppose que personne n’a pensé que cela en valait la peine, et je suis d’accord avec elles.

6
anon

Pourquoi pas? Vous pouvez utiliser switch implémentation avec une syntaxe équivalente et la même sémantique . Le langage C ne comporte aucun objet ni objet de chaîne, mais Les chaînes dans C sont des chaînes terminées par null référencées par le pointeur . Le langage C++ offre la possibilité de créer des fonctions de surcharge pour la comparaison des objets .__ ou la vérification de l’égalité des objets . As C en tant que C++ est suffisamment flexible pour permettre un tel basculement des chaînes pour le langage C Et pour les objets de tout type prenant en charge comparaison ou vérification. égalité pour C++ langue. Et les C++11 modernes permettent d’avoir cette implémentation de commutateur Suffisamment efficace.

Votre code sera comme ça:

std::string name = "Alice";

std::string gender = "boy";
std::string role;

SWITCH(name)
  CASE("Alice")   FALL
  CASE("Carol")   gender = "girl"; FALL
  CASE("Bob")     FALL
  CASE("Dave")    role   = "participant"; BREAK
  CASE("Mallory") FALL
  CASE("Trudy")   role   = "attacker";    BREAK
  CASE("Peggy")   gender = "girl"; FALL
  CASE("Victor")  role   = "verifier";    BREAK
  DEFAULT         role   = "other";
END

// the role will be: "participant"
// the gender will be: "girl"

Il est possible d'utiliser des types plus compliqués, par exemple std::pairs, ou des structures ou des classes prenant en charge les opérations d'égalité (ou des commandes pour le mode quick).

Caractéristiques

  • tout type de données prenant en charge les comparaisons ou la vérification de l'égalité
  • possibilité de construire des statemens de commutateur imbriqués en cascade.
  • possibilité de casser ou de passer à travers les déclarations de cas
  • possibilité d'utiliser des expressions de casse non constatnt
  • possible d'activer le mode statique/dynamique rapide avec la recherche dans les arbres (pour C++ 11)

Différences Sintax avec changement de langue est

  • mots-clés majuscules
  • besoin de parenthèses pour l'instruction CASE
  • point-virgule ';' à la fin des déclarations n'est pas autorisé
  • les deux points ':' à l'instruction CASE ne sont pas autorisés
  • besoin du mot clé BREAK ou FALL à la fin de l'instruction CASE

Pour C++97 langue utilisée recherche linéaire . Pour C++11 et plus moderne possible d'utiliser le mode quick recherche d'arborescence en mode où l'instruction return dans CASE devenant non autorisée . L'implémentation du langage C existe où le type char* et zéro des comparaisons de chaînes terminées sont utilisées.

Lisez plus sur cette implémentation de commutateur.

5
oklas

Je pense que la raison en est que, dans C, les chaînes ne sont pas des types primitifs, comme dit tomjen, pense que dans une chaîne de caractères, vous ne pouvez pas faire les choses suivantes:

switch (char[]) { // ...
switch (int[]) { // ...
4
grilix

Pour ajouter une variante en utilisant le conteneur le plus simple possible (pas besoin de carte ordonnée) ... Je ne voudrais pas m'embêter avec une énumération - il suffit de mettre la définition du conteneur juste avant le commutateur pour pouvoir voir facilement quel nombre représente quel cas. 

Cela effectue une recherche hachée dans le unordered_map et utilise la variable int associée pour piloter l'instruction switch. Devrait être assez rapide. Notez que at est utilisé à la place de [], comme je l'ai fait avec const. Utiliser [] peut être dangereux - si la chaîne ne figure pas dans la carte, vous créez une nouvelle correspondance et vous pouvez vous retrouver avec des résultats non définis ou une carte en croissance constante.

Notez que la fonction at() lève une exception si la chaîne n'est pas dans la carte. Donc, vous voudrez peut-être d'abord tester avec count().

const static std::unordered_map<std::string,int> string_to_case{
   {"raj",1},
   {"ben",2}
};
switch(string_to_case.at("raj")) {
  case 1: // this is the "raj" case
       break;
  case 2: // this is the "ben" case
       break;


}

La version avec un test pour une chaîne non définie est la suivante:

const static std::unordered_map<std::string,int> string_to_case{
   {"raj",1},
   {"ben",2}
};
switch(string_to_case.count("raj") ? string_to_case.at("raj") : 0) {
  case 1: // this is the "raj" case
       break;
  case 2: // this is the "ben" case
       break;
  case 0: //this is for the undefined case

}
3
rsjaffe

En c ++, les chaînes ne sont pas des citoyens de première classe. Les opérations sur les chaînes se font via la bibliothèque standard. Je pense que c'est la raison. En outre, C++ utilise l'optimisation de table de branche pour optimiser les instructions de casse de commutateur. Regardez le lien.

http://en.wikipedia.org/wiki/Switch_statement

3
chappar

En C++, vous ne pouvez utiliser qu'une instruction switch sur int et char

3
CodeMonkey1313
    cout << "\nEnter Word to select your choice\n"; 
    cout << "ex to exit program (0)\n";     
    cout << "m     to set month(1)\n";
    cout << "y     to set year(2)\n";
    cout << "rm     to return the month(4)\n";
    cout << "ry     to return year(5)\n";
    cout << "pc     to print the calendar for a month(6)\n";
    cout << "fdc      to print the first day of the month(1)\n";
    cin >> c;
    cout << endl;
    a = c.compare("ex") ?c.compare("m") ?c.compare("y") ? c.compare("rm")?c.compare("ry") ? c.compare("pc") ? c.compare("fdc") ? 7 : 6 :  5  : 4 : 3 : 2 : 1 : 0;
    switch (a)
    {
        case 0:
            return 1;

        case 1:                   ///m
        {
            cout << "enter month\n";
            cin >> c;
            cout << endl;
            myCalendar.setMonth(c);
            break;
        }
        case 2:
            cout << "Enter year(yyyy)\n";
            cin >> y;
            cout << endl;
            myCalendar.setYear(y);
            break;
        case 3:
             myCalendar.getMonth();
            break;
        case 4:
            myCalendar.getYear();
        case 5:
            cout << "Enter month and year\n";
            cin >> c >> y;
            cout << endl;
            myCalendar.almanaq(c,y);
            break;
        case 6:
            break;

    }
0
Juan Llanes

dans de nombreux cas, vous pouvez obtenir un travail supplémentaire en extrayant le premier caractère de la chaîne et en l'activant. peut avoir à faire un commutateur imbriqué sur charat (1) si votre cas commence par la même valeur. Quiconque lirait votre code apprécierait un indice cependant, car la plupart des prob seraient simplement si-sinon-si

0
Marshall Taylor

Solution de contournement plus fonctionnelle au problème de commutation:

class APIHandlerImpl
{

// define map of "cases"
std::map<string, std::function<void(server*, websocketpp::connection_hdl, string)>> in_events;

public:
    APIHandlerImpl()
    {
        // bind handler method in constructor
        in_events["/hello"] = std::bind(&APIHandlerImpl::handleHello, this, _1, _2, _3);
        in_events["/bye"] = std::bind(&APIHandlerImpl::handleBye, this, _1, _2, _3);
    }

    void onEvent(string event = "/hello", string data = "{}")
    {
        // execute event based on incomming event
        in_events[event](s, hdl, data);
    }

    void APIHandlerImpl::handleHello(server* s, websocketpp::connection_hdl hdl, string data)
    {
        // ...
    }

    void APIHandlerImpl::handleBye(server* s, websocketpp::connection_hdl hdl, string data)
    {
        // ...
    }
}
0
FelikZ

Les commutateurs ne fonctionnent qu'avec les types intégraux (int, char, bool, etc.). Pourquoi ne pas utiliser une carte pour associer une chaîne à un nombre, puis utiliser ce nombre avec le commutateur?

0
derpface

Vous ne pouvez pas utiliser de chaîne dans le casse switch.Only int & char sont autorisés. À la place, vous pouvez essayer enum pour représenter la chaîne et l’utiliser dans le bloc de casse comme 

enum MyString(raj,taj,aaj);

Utilisez-le dans la déclaration de cas swich.

0
anil