
jaraaj
Das Kompilieren des folgenden Codes gibt die Fehlermeldung: type illegal
.
int main()
{
// Compilation error - switch expression of type illegal
switch(std::string("raj"))
{
case"sda":
}
}
Sie können auch keine Zeichenfolge verwenden switch
oder case
. Wieso den? Gibt es eine Lösung, die gut funktioniert, um eine Logik ähnlich wie beim Einschalten von Zeichenfolgen zu unterstützen?
Der Grund dafür hat mit dem Typensystem zu tun. C/C++ unterstützt Strings nicht wirklich als Typ. Es unterstützt zwar die Idee eines konstanten Zeichenarrays, versteht aber den Begriff einer Zeichenfolge nicht wirklich vollständig.
Um den Code für eine switch-Anweisung zu generieren, muss der Compiler verstehen, was es bedeutet, wenn zwei Werte gleich sind. Für Elemente wie ints und enums ist dies ein trivialer Bitvergleich. Aber wie soll der Compiler 2 Stringwerte vergleichen? Groß-/Kleinschreibung beachten, unsensibel, kulturbewusst usw. Ohne ein vollständiges Bewusstsein für eine Zeichenfolge kann dies nicht genau beantwortet werden.
Darüber hinaus werden C/C++-Switch-Anweisungen normalerweise als generiert Zweigtische. Es ist nicht annähernd so einfach, eine Verzweigungstabelle für einen Schalter im String-Stil zu erstellen.
Wie bereits erwähnt, erstellen Compiler gerne Nachschlagetabellen, die optimieren switch
Anweisungen wann immer möglich in der Nähe von O(1) Timing. Kombinieren Sie dies mit der Tatsache, dass die C++-Sprache keinen Zeichenfolgentyp hat – std::string
ist Teil der Standardbibliothek, die nicht Teil der Sprache an sich ist.
Ich werde eine Alternative anbieten, die Sie vielleicht in Betracht ziehen möchten, ich habe sie in der Vergangenheit mit gutem Erfolg eingesetzt. Anstatt den String selbst umzuschalten, schalten Sie das Ergebnis einer Hash-Funktion um, die den String als Eingabe verwendet. Ihr Code ist fast so klar wie das Umschalten der Zeichenfolge, wenn Sie einen vordefinierten Satz von Zeichenfolgen verwenden:
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:
...
}
}
Es gibt eine Menge offensichtlicher Optimierungen, die ziemlich genau dem folgen, was der C-Compiler mit einer switch-Anweisung machen würde … lustig, wie das passiert.

Nick
C++
constexpr-Hash-Funktion:
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
}
Aktualisieren:
Das obige Beispiel ist C++11. Dort constexpr
Funktion muss mit Einzelanweisung sein. Dies wurde in den nächsten C++-Versionen gelockert.
In C++14 und C++17 können Sie folgende Hash-Funktion verwenden:
constexpr uint32_t hash(const char* data, size_t const size) noexcept{
uint32_t hash = 5381;
for(const char *c = data; c < data + size; ++c)
hash = ((hash << 5) + hash) + (unsigned char) *c;
return hash;
}
Auch C++17 haben std::string_view
sodass Sie es anstelle von verwenden können const char *
.
In C++20 können Sie versuchen, dies zu verwenden consteval
.

Dirk Beste
C++ 11 Update von @MarmouCorp oben aber anscheinend nicht http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm
Verwendet zwei Maps, um zwischen den Zeichenfolgen und der Klassenaufzählung zu konvertieren (besser als eine einfache Aufzählung, da ihre Werte darin enthalten sind, und umgekehrte Suche nach netten Fehlermeldungen).
Die Verwendung von static im Codeguru-Code ist mit Compiler-Unterstützung für Initialisierungslisten möglich, was VS 2013 plus bedeutet. gcc 4.8.1 war damit einverstanden, nicht sicher, wie viel weiter hinten es kompatibel wäre.
/// <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]);
}
Das Problem ist, dass die switch-Anweisung in C++ aus Optimierungsgründen nur auf primitiven Typen funktioniert und Sie sie nur mit Kompilierzeitkonstanten vergleichen können.
Vermutlich liegt der Grund für die Einschränkung darin, dass der Compiler in der Lage ist, eine Form der Optimierung anzuwenden, indem er den Code in eine cmp-Anweisung und ein goto kompiliert, wobei die Adresse basierend auf dem Wert des Arguments zur Laufzeit berechnet wird. Da Verzweigungen und Schleifen mit modernen CPUs nicht gut funktionieren, kann dies eine wichtige Optimierung sein.
Um dies zu umgehen, müssen Sie leider auf if-Anweisungen zurückgreifen.
std::map
+ C++11-Lambdas-Muster ohne Aufzählungen
unordered_map
für das Potenzial amortisiert O(1)
: Was ist der beste Weg, eine HashMap in C++ zu verwenden?
#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;
}
}
Ausgabe:
one 1
two 2
three 3
foobar -1
Verwendung innerhalb von Methoden mit static
Um dieses Muster effizient innerhalb von Klassen zu verwenden, initialisieren Sie die Lambda-Map statisch, andernfalls zahlen Sie O(n)
jedes Mal, um es von Grund auf neu zu bauen.
Hier können wir mit dem durchkommen {}
Initialisierung von a static
Methodenvariable: Statische Variablen in Klassenmethoden , aber wir könnten auch die Methoden verwenden, die unter beschrieben sind: statische Konstruktoren in C++? Ich muss private statische Objekte initialisieren
Es war notwendig, die Lambda-Kontexterfassung zu transformieren [&]
in ein Argument, oder das wäre undefiniert gewesen: const static auto lambda wird mit capture by reference verwendet
Beispiel, das die gleiche Ausgabe wie oben erzeugt:
#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;
}
}
Um eine Variation mit dem einfachsten möglichen Container hinzuzufügen (keine Notwendigkeit für eine geordnete Karte) … Ich würde mich nicht um eine Aufzählung kümmern – setzen Sie einfach die Containerdefinition direkt vor den Schalter, damit Sie leicht sehen können, welche Nummer dargestellt wird Welcher Fall.
Dies führt eine gehashte Suche in der unordered_map
und verwendet die zugehörigen int
um die switch-Anweisung zu steuern. Sollte recht schnell gehen. Beachten Sie, dass at
wird stattdessen verwendet []
wie ich diesen Container gemacht habe const
. Verwenden []
kann gefährlich sein – wenn die Zeichenfolge nicht in der Karte enthalten ist, erstellen Sie eine neue Zuordnung und erhalten möglicherweise undefinierte Ergebnisse oder eine kontinuierlich wachsende Karte.
Notiere dass der at()
Die Funktion löst eine Ausnahme aus, wenn sich die Zeichenfolge nicht in der Karte befindet. Sie können also zuerst testen, ob Sie es verwenden 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;
}
Die Version mit einem Test für eine undefinierte Zeichenfolge folgt:
const static std::unordered_map<std::string,int> string_to_case{
{"raj",1},
{"ben",2}
};
// in C++20, you can replace .count with .contains
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
}
10159900cookie-checkWarum kann die switch-Anweisung nicht auf Strings angewendet werden?yes
Gibt es eine Boost-Alternative, die den Kartenaufbau hinter einem MACRO versteckt?
– Balki
2. März 2012 um 15:07 Uhr
@balki Ich bin mir bei Boost nicht sicher, aber es ist einfach, solche Makros zu schreiben. Im Fall von Qt können Sie die Zuordnung dann mit ausblenden
QMetaEnum
– phuklv
19. Januar 2018 um 8:44 Uhr