2013-09-01 11 views
6

Ich versuche wahrscheinlich, das Unmögliche zu erreichen, aber StackExchange überrascht mich immer, also bitte versuchen Sie es:Ist eine zur Kompilierungszeit geprüfte string-to-int-Map möglich?

Ich muss einen Namen zu einer ganzen Zahl zuordnen. Die Namen (ca. 2k) sind einzigartig. Diese Liste wird weder hinzugefügt noch gelöscht, und die Werte ändern sich während der Laufzeit nicht.

Implementieren sie als const int Variablen gibt mir Kompilierzeitüberprüfungen für Existenz und Typ. Auch das ist sehr klar und ausführlich im Code. Fehler sind leicht zu erkennen.

Implementieren sie als std::map<std::string, int> gibt mir eine Menge Flexibilität für den Aufbau der Namen mit String-Manipulation nachschlagen. Ich kann das verwenden, um Strings als Parameter für Funktionen zu geben, die die Liste nach mehreren Werten abfragen können, indem ich Pre-/Suffixe an diese Zeichenfolge anfüge. Ich kann auch mehrere Werte durchlaufen, indem ich einen numerischen Teil des Schlüsselnamens aus der Schleifenvariablen erstelle.

Jetzt ist meine Frage: Gibt es eine Methode, beide Vorteile zu kombinieren? Die fehlende Kompilierzeitüberprüfung (speziell für die Schlüsselexistenz) bringt für mich die zweite Methode fast zum Erliegen. (Besonders, da std::map automatisch 0 zurückgibt, wenn der Schlüssel nicht existiert, was schwer zu findende Bugs erzeugt.) Aber die Funktionen zum Schleifen und Pre-/Suffix-Hinzufügen sind so verdammt nützlich.

Ich würde eine Lösung bevorzugen, die keine zusätzlichen Bibliotheken wie Boost verwendet, aber bitte schlagen Sie sie dennoch vor, da ich sie vielleicht trotzdem umsetzen könnte.

Ein Beispiel auf dem, was ich mit der Karte tun:

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()]); 
} 

Ein weiteres Ziel ist, dass der Code in der main() Routine bleibt so einfach und ausführlichen wie möglich dargestellt. (Muss von Nicht-Programmierern verstanden werden.) Die Funktion init() wird von einigen Tools mit Code generiert. Die doSomething(int) Funktionen sind behoben, aber ich kann Wrapper-Funktionen um sie schreiben. Helfer wie checkName() können komplizierter sein, müssen aber leicht debuggbar sein.

+1

Es klingt wie Sie die Saiten zur Laufzeit konstruieren wollen, aber irgendwie zur Compile-Zeit überprüft haben? –

+0

Es Möglichkeiten sind Aufzählungen in den entsprechenden Zeichenfolge zu konvertieren, so sollte es möglich sein .. Obwohl schwer zu sehen, wie es kompiliert Zeit ist, wenn Sie die Zeichenfolge in der Laufzeit bauen –

+0

'std :: map' hat sicherlich die Fähigkeit, zu sagen, ob ein Element eingefügt wurde. Eine Möglichkeit ist 'Einfügen'. – chris

Antwort

1

Eine Möglichkeit, Ihr Beispiel zu implementieren, ein ENUM und Token-Einfügen verwendet, wie dies

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)); 
} 

Ob dies am besten ist, hängt davon ab, wo die Namen kommen.

Wenn Sie die for-Schleife Use-Case unterstützen müssen, dann betrachten

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

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

Obwohl der Präprozessor (der mit seinen eigenen Problemen beim Debuggen kommt) verwendet wird, löst dies das Problem ziemlich gut und liefert ziemlich ausführlichen und leicht verständlichen Code. Ich muss innerhalb des echten Codes überprüfen, ob es wirklich alle Kästchen ankreuzen, aber ich mag es schon. –

+0

For-Schleifen können jedoch nicht ausgeführt werden, da diese nur zur Laufzeit verfügbar sind. Oder gibt es einen nicht-bösen Trick, um For-Schleifen mit dem Präprozessor zu machen? –

1

Ich bin mir nicht sicher, ob ich alle Ihre Anforderungen verstehe, aber wie wäre es mit so etwas, ohne std::map zu verwenden. Ich nehme an, dass Sie drei Zeichenfolgen haben, "FIRST", "SECOND" und "THIRD", die Sie auf 42, 17 und 37 zuordnen möchten.

#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; 
} 

Der Nachteil ist, dass Sie nicht variable Strings mit LOOKUP verwenden können. Aber jetzt können Sie über die Werte iterieren.

+0

Interessante Idee. Begrenzt die Schleife auf aufeinanderfolgende Werte, aber das ist eigentlich durchaus akzeptabel. Leider ist es auch schwer zu debuggen (versuchen Sie den 1276ten Wert im Array zu finden). –

0

Mit Enum Sie haben beide Kompilierung-Check und wird auch hier über sie:

How can I iterate over an enum?

+0

Der Link sagt, dass ich es nicht überschleifen kann, wenn ich willkürliche Zahlen für das ENUM verwende, was der Fall wäre, wenn ich das Enum verwende, um eine Name-zu-Int-Map zu speichern. –

1

Vielleicht so etwas wie dieses (nicht getestet)?

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>(); 
} 
+0

Gute Idee, aber in meinem Fall nicht möglich. Die Komplexität des Code-Generators, der die Liste füllt, müsste enorm gesteigert werden. Ich kann ändern, wie jede Zeile ausgegeben wird, aber ich kann keine komplexen Entscheidungen treffen. –