2009-10-06 3 views
21

Ich habe die folgende Funktion, die einen String in einen numerischen Datentyp konvertieren:Wie kann ich einen lexikalischen Cast erweitern, um Aufzählungstypen zu unterstützen?

template <typename T> 
bool ConvertString(const std::string& theString, T& theResult) 
{ 
    std::istringstream iss(theString); 
    return !(iss >> theResult).fail(); 
} 

Das funktioniert nicht für Aufzählungstypen, aber so habe ich so etwas getan:

template <typename T> 
bool ConvertStringToEnum(const std::string& theString, T& theResult) 
{ 
    std::istringstream iss(theString); 
    unsigned int temp; 
    const bool isValid = !(iss >> temp).fail(); 
    theResult = static_cast<T>(temp); 
    return isValid; 
} 

(Ich mache die Annahme, dass theString einen gültigen Wert für den Aufzählungstyp hat; ich verwende das hauptsächlich für einfache Serialisierung)

Gibt es eine Möglichkeit, eine einzelne Funktion zu erstellen, die beide kombiniert?

Ich habe ein wenig mit den Vorlagenargumenten gespielt, aber habe nichts gefunden; es wäre nur schön, wenn man nicht eine Funktion für aufgezählte Typen und eine andere für alles andere aufrufen müsste.

Vielen Dank

Antwort

42

Sie müssen zwei Schritte tun. Einen integralen Typ finden, der groß genug ist, um die Werte zu speichern. Sie könnten unsigned long verwenden, aber die Werte könnten negativ sein. Dann könnten Sie long verwenden, aber die Werte könnten sich in den Bereich von unsigned long erstrecken. Also gibt es nicht wirklich einen Alleskönner.

Es gibt jedoch einen Trick, die Überladungsauflösung zu verwenden. Hier ist es

template<typename T> 
struct id { typedef T type; }; 

id<char[1]>::type &find_etype(int); 
id<char[2]>::type &find_etype(unsigned int); 
id<char[3]>::type &find_etype(long); 
id<char[4]>::type &find_etype(unsigned long); 

Sie es entsprechend auch zur Deckung long long ändern oder unsigned long long wenn Ihre Implementierung Unterstützung für das hat. Das Übergeben eines Enum-Typs wird nun eines von diesen gegenüber allen anderen bevorzugen - das ist ein Typ, der alle Werte davon speichern kann. Sie müssen nur sizeof des Rückgabetyps an eine Vorlage übergeben.

template<int> struct get_etype; 
template<> struct get_etype<1> { typedef int type; }; 
template<> struct get_etype<2> { typedef unsigned int type; }; 
template<> struct get_etype<3> { typedef long type; }; 
template<> struct get_etype<4> { typedef unsigned long type; }; 

Jetzt können Sie einen richtigen Typ erhalten. Alles, was Sie jetzt brauchen, ist zu sehen, ob ein Typ eine Aufzählung ist. Wie dies zu tun ist, ist in dem Buch "C++ Templates - The Complete Guide" beschrieben, und leider ist eine Menge Code. Also würde ich Boost is_enum verwenden. Putting es zusammen, könnte es aussehen wie

template <typename T> 
typename boost::disable_if< boost::is_enum<T>, bool>::type 
ConvertString(const std::string& theString, T& theResult) 
{ 
    std::istringstream iss(theString); 
    return !(iss >> theResult).fail(); 
} 

template <typename T> 
typename boost::enable_if< boost::is_enum<T>, bool>::type 
ConvertString(const std::string& theString, T& theResult) 
{ 
    typedef typename get_etype<sizeof find_etype(theResult)>::type 
     safe_type; 

    std::istringstream iss(theString); 
    safe_type temp; 
    const bool isValid = !(iss >> temp).fail(); 
    theResult = static_cast<T>(temp); 
    return isValid; 
} 

Hoffe, dass dies hilft.

+0

+1. Ich war auch dabei, einen Kommentar darüber zu schreiben, warum dies nicht in der Standardbibliothek war, bis ich GMans Antwort unten las. – Jon

+0

boost wird nicht mehr benötigt, da std :: enable_if und std :: is_enum diese Funktionalitäten in C++ bieten 11 – moala

10

Und nur um "komplett" ist die Frage, in C++ 0x wir dies nur tun können:

typedef typename std::underlying_type<T>::type safe_type; 

Anstelle von Johannes get_etype Trick.

+2

Awesome; Was für ein bequemes Feature. Ich war so ein Noob, als ich diese Frage stellte ... Ich meine, ich bin immer noch ein Noob, aber ich war damals viel mehr ein Noob. –