¿Hay una manera simple de convertir la enumeración de C ++ en una cadena?


123

Supongamos que tenemos algunas enumeraciones con nombre:

enum MyEnum {
      FOO,
      BAR = 0x50
};

Lo que busqué en Google es un script (cualquier idioma) que escanea todos los encabezados de mi proyecto y genera un encabezado con una función por enumeración.

char* enum_to_string(MyEnum t);

Y una implementación con algo como esto:

char* enum_to_string(MyEnum t){
      switch(t){
         case FOO:
            return "FOO";
         case BAR:
            return "BAR";
         default:
            return "INVALID ENUM";
      }
 }

El gotcha es realmente con enumeraciones typedefed y enumeraciones de estilo C sin nombre. ¿Alguien sabe algo para esto?

EDITAR: La solución no debe modificar mi fuente, excepto las funciones generadas. Las enumeraciones están en una API, por lo que usar las soluciones propuestas hasta ahora no es una opción.


La respuesta sobre la fábrica basada en macros se trasladó a stackoverflow.com/questions/147267/… : después de actualizar la pregunta, ya no es relevante aquí.
Suma

Respuestas:


48

Es posible que desee consultar GCCXML .

Ejecutar GCCXML en su código de muestra produce:

<GCC_XML>
  <Namespace id="_1" name="::" members="_3 " mangled="_Z2::"/>
  <Namespace id="_2" name="std" context="_1" members="" mangled="_Z3std"/>
  <Enumeration id="_3" name="MyEnum" context="_1" location="f0:1" file="f0" line="1">
    <EnumValue name="FOO" init="0"/>
    <EnumValue name="BAR" init="80"/>
  </Enumeration>
  <File id="f0" name="my_enum.h"/>
</GCC_XML>

Puede usar cualquier idioma que prefiera para extraer las etiquetas Enumeration y EnumValue y generar el código deseado.


¡Excelente! Funcionó de maravilla con un simple script de python. Gracias.
Edu Felipe

66
+1, ¡GCCXML se ve muy bien! (Aunque casi me -1ed como yo inicialmente malinterpretar esto como una sugerencia de utilizar lo anterior verbosa sintaxis XML para codificar su enumeración - una solución que huele a manipulación excesiva)
j_random_hacker

1
¿algún cambio puedes publicar el script de python?
phillipwei

74

Las macros X son la mejor solución. Ejemplo:

#include <iostream>

enum Colours {
#   define X(a) a,
#   include "colours.def"
#   undef X
    ColoursCount
};

char const* const colours_str[] = {
#   define X(a) #a,
#   include "colours.def"
#   undef X
    0
};

std::ostream& operator<<(std::ostream& os, enum Colours c)
{
    if (c >= ColoursCount || c < 0) return os << "???";
    return os << colours_str[c];
}

int main()
{
    std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}

colores.def:

X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)

Sin embargo, generalmente prefiero el siguiente método, para que sea posible ajustar un poco la cadena.

#define X(a, b) a,
#define X(a, b) b,

X(Red, "red")
X(Green, "green")
// etc.

11
ingenioso, aunque no me gusta el archivo extra
Ronny Brendel

2
Sólo asegúrese de que su proceso de construcción no #pragma no anteponer (una vez) antes de cada archivo de inclusión ...
xtofl

24
¡No estoy seguro de la "mejor" solución!
Carreras de ligereza en órbita el

2
Esta solución es muy superior a cualquier caja de conmutador o matriz basada en una, porque no duplica los nombres, lo que facilita el cambio de la enumeración.
Julien Guertault

2
@ ikku100 eres incorrecto #define X(a, b) #b. Esto solo es necesario si la definición se ve así X(Red, red), en lugar de la definición que se muestra en la respuesta,X(Red, "red")
learnvst

43

@hydroo: Sin el archivo extra:

#define SOME_ENUM(DO) \
    DO(Foo) \
    DO(Bar) \
    DO(Baz)

#define MAKE_ENUM(VAR) VAR,
enum MetaSyntacticVariable{
    SOME_ENUM(MAKE_ENUM)
};

#define MAKE_STRINGS(VAR) #VAR,
const char* const MetaSyntacticVariableNames[] = {
    SOME_ENUM(MAKE_STRINGS)
};

Amo esta solución Sin embargo, sería más claro si SOME_UNION y MAKE_UNION se llamaran SOME_ENUM y MAKE_ENUM.
Bruno Martinez

Esta es una gran solución. Tengo el administrador de recursos C ++ más fácil de mantener con el que he tratado.
DCurro

Debo agradecerle por esta solución simple :-) - Sin embargo, la modifiqué un poco, para que MetaSyntacticVariableNames[]sea ​​parte de una declaración de clase, haciendo un métodostatic const char* getNameByEnum(MetaSyntacticVariable e) { /*code to return the static string*/ }
DeckerDK

Fantástica respuesta! Lo simplifiqué aún más al agrupar MAKE_ENUM y MAKE_STRINGS en una sola macro, lo que simplifica aún más todo el proceso. Agregué una respuesta en este hilo con ese código si alguien está interesado.
Francois Bertrand

35

Lo que tiendo a hacer es crear una matriz C con los nombres en el mismo orden y posición que los valores de enumeración.

p.ej.

enum colours { red, green, blue };
const char *colour_names[] = { "red", "green", "blue" };

entonces puede usar la matriz en lugares donde desea un valor legible para humanos, por ejemplo

colours mycolour = red;
cout << "the colour is" << colour_names[mycolour];

Podrías experimentar un poco con el operador de encadenamiento (ver # en la referencia de tu preprocesador) que hará lo que quieras, en algunas circunstancias, por ejemplo:

#define printword(XX) cout << #XX;
printword(red);

imprimirá "rojo" en stdout. Desafortunadamente no funcionará para una variable (ya que obtendrá el nombre de la variable impreso)


La última advertencia (no funcionará para una variable) es un gran inconveniente, pero de todos modos +1.
chappjc

3
Funciona solo si no establece valores numéricos especiales para las entradas de enumeración.
kyb

11

Tengo una macro increíblemente simple de usar que hace esto de una manera completamente SECA. Involucra macros variables y algo de magia de análisis simple. Aquí va:

#define AWESOME_MAKE_ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT}; \
inline std::ostream& operator<<(std::ostream& os, name value) { \
std::string enumName = #name; \
std::string str = #__VA_ARGS__; \
int len = str.length(); \
std::vector<std::string> strings; \
std::ostringstream temp; \
for(int i = 0; i < len; i ++) { \
if(isspace(str[i])) continue; \
        else if(str[i] == ',') { \
        strings.push_back(temp.str()); \
        temp.str(std::string());\
        } \
        else temp<< str[i]; \
} \
strings.push_back(temp.str()); \
os << enumName << "::" << strings[static_cast<int>(value)]; \
return os;} 

Para usar esto en su código, simplemente haga:

AWESOME_MAKE_ENUM(Animal,
    DOG,
    CAT,
    HORSE
);

1
Buena idea usando un enum fuertemente tipado (clase enum). Aquí hay una demostración: cpp.sh/4ife
chappjc

¿Funciona esto con enumeraciones / símbolos definidos externamente? Por ejemplo, ¿símbolos definidos por el sistema operativo o definidos por la biblioteca con espacios en la numeración?
Jason Harrison

Muy agradable, pero no se compila si se coloca dentro de una clase (no pude entender por qué).
Siempre aprendiendo el

No pude conseguir esto para compilar en VS2015. Recibo una advertencia y un error: advertencia: comentario de varias líneas [-Wcomment] #define MAKE_ENUM (nombre, ...) nombre de clase de enumeración { VA_ARGS , __COUNT} error: parásito '#' en el programa std *: string enumName = #name
Craig.Feied

8

QT puede extraer eso de (gracias al compilador de metaobjetos):

QNetworkReply::NetworkError error;

error = fetchStuff();

if (error != QNetworkReply::NoError) {

    QString errorValue;

    QMetaObject meta = QNetworkReply::staticMetaObject;

    for (int i=0; i < meta.enumeratorCount(); ++i) {

        QMetaEnum m = meta.enumerator(i);

        if (m.name() == QLatin1String("NetworkError")) {

            errorValue = QLatin1String(m.valueToKey(error));

            break;

        }

    }

    QMessageBox box(QMessageBox::Information, "Failed to fetch",

                "Fetching stuff failed with error '%1`").arg(errorValue),

                QMessageBox::Ok);

    box.exec();

    return 1;

}

En Qt, cada clase que tenga la macro Q_OBJECT tendrá automáticamente un miembro estático "staticMetaObject" del tipo QMetaObject. A continuación, puede encontrar todo tipo de cosas interesantes, como las propiedades, señales, ranuras y enumeraciones.

Fuente


7

Esto se puede hacer en C ++ 11

#include <map>
enum MyEnum { AA, BB, CC, DD };

static std::map< MyEnum, const char * > info = {
   {AA, "This is an apple"},
   {BB, "This is a book"},
   {CC, "This is a coffee"},
   {DD, "This is a door"}
};

void main()
{
    std::cout << info[AA] << endl
              << info[BB] << endl
              << info[CC] << endl
              << info[DD] << endl;
}

1
Esto no responde a la pregunta del OP: estaba buscando una manera de generar automáticamente una función para devolver el nombre del miembro de una enumeración como una cadena.
Espeluznante

7

Hoy reinventé esta rueda y pensé en compartirla.

Esta implementación no requiere ningún cambio en el código que define las constantes, que pueden ser enumeraciones #defineo cualquier otra cosa que se transfiera a un número entero; en mi caso, tenía símbolos definidos en términos de otros símbolos. También funciona bien con valores dispersos. Incluso permite múltiples nombres para el mismo valor, devolviendo el primero siempre. El único inconveniente es que requiere que hagas una tabla de las constantes, que pueden quedar desactualizadas a medida que se agreguen nuevas, por ejemplo.

struct IdAndName
{
   int          id;
   const char * name;
   bool operator<(const IdAndName &rhs) const { return id < rhs.id; }
};
#define ID_AND_NAME(x) { x, #x }

const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end)
{
   if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id)
      std::stable_sort(table_begin, table_end);

   IdAndName searchee = { id, NULL };
   IdAndName *p = std::lower_bound(table_begin, table_end, searchee);
   return (p == table_end || p->id != id) ? NULL : p->name;
}

template<int N>
const char * IdToName(int id, IdAndName (&table)[N])
{
   return IdToName(id, &table[0], &table[N]);
}

Un ejemplo de cómo lo usarías:

static IdAndName WindowsErrorTable[] =
{
   ID_AND_NAME(INT_MAX),               // flag value to indicate unsorted table
   ID_AND_NAME(NO_ERROR),
   ID_AND_NAME(ERROR_INVALID_FUNCTION),
   ID_AND_NAME(ERROR_FILE_NOT_FOUND),
   ID_AND_NAME(ERROR_PATH_NOT_FOUND),
   ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES),
   ID_AND_NAME(ERROR_ACCESS_DENIED),
   ID_AND_NAME(ERROR_INVALID_HANDLE),
   ID_AND_NAME(ERROR_ARENA_TRASHED),
   ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY),
   ID_AND_NAME(ERROR_INVALID_BLOCK),
   ID_AND_NAME(ERROR_BAD_ENVIRONMENT),
   ID_AND_NAME(ERROR_BAD_FORMAT),
   ID_AND_NAME(ERROR_INVALID_ACCESS),
   ID_AND_NAME(ERROR_INVALID_DATA),
   ID_AND_NAME(ERROR_INVALID_DRIVE),
   ID_AND_NAME(ERROR_CURRENT_DIRECTORY),
   ID_AND_NAME(ERROR_NOT_SAME_DEVICE),
   ID_AND_NAME(ERROR_NO_MORE_FILES)
};

const char * error_name = IdToName(GetLastError(), WindowsErrorTable);

La IdToNamefunción se basa en std::lower_boundrealizar búsquedas rápidas, lo que requiere que se ordene la tabla. Si las dos primeras entradas de la tabla están fuera de servicio, la función lo ordenará automáticamente.

Editar: Un comentario me hizo pensar en otra forma de usar el mismo principio. Una macro simplifica la generación de una gran switchdeclaración.

#define ID_AND_NAME(x) case x: return #x

const char * WindowsErrorToName(int id)
{
    switch(id)
    {
        ID_AND_NAME(ERROR_INVALID_FUNCTION);
        ID_AND_NAME(ERROR_FILE_NOT_FOUND);
        ID_AND_NAME(ERROR_PATH_NOT_FOUND);
        ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES);
        ID_AND_NAME(ERROR_ACCESS_DENIED);
        ID_AND_NAME(ERROR_INVALID_HANDLE);
        ID_AND_NAME(ERROR_ARENA_TRASHED);
        ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY);
        ID_AND_NAME(ERROR_INVALID_BLOCK);
        ID_AND_NAME(ERROR_BAD_ENVIRONMENT);
        ID_AND_NAME(ERROR_BAD_FORMAT);
        ID_AND_NAME(ERROR_INVALID_ACCESS);
        ID_AND_NAME(ERROR_INVALID_DATA);
        ID_AND_NAME(ERROR_INVALID_DRIVE);
        ID_AND_NAME(ERROR_CURRENT_DIRECTORY);
        ID_AND_NAME(ERROR_NOT_SAME_DEVICE);
        ID_AND_NAME(ERROR_NO_MORE_FILES);
        default: return NULL;
    }
}

Buena solución. Pero para mí preferiría, switch and caseya que es simple y fácil de entender.
Deqing

6
#define stringify( name ) # name

enum MyEnum {
    ENUMVAL1
};
...stuff...

stringify(EnumName::ENUMVAL1);  // Returns MyEnum::ENUMVAL1

Más discusión sobre este método

Trucos de directivas de preprocesador para recién llegados


44
En realidad, esto es bastante inútil, ya que el método stringify está en tiempo de compilación y es bastante literal. Si dice que tiene el tipo de enumeración en cuestión dentro de una variable, intentar stringificar la variable solo le dará el nombre de la variable, no el nombre del tipo de enumeración.
srcspider

5

Interesante ver la cantidad de formas. Aquí hay uno que usé hace mucho tiempo:

en el archivo myenummap.h:

#include <map>
#include <string>
enum test{ one, two, three, five=5, six, seven };
struct mymap : std::map<unsigned int, std::string>
{
  mymap()
  {
    this->operator[]( one ) = "ONE";
    this->operator[]( two ) = "TWO";
    this->operator[]( three ) = "THREE";
    this->operator[]( five ) = "FIVE";
    this->operator[]( six ) = "SIX";
    this->operator[]( seven ) = "SEVEN";
  };
  ~mymap(){};
};

en main.cpp

#include "myenummap.h"

...
mymap nummap;
std::cout<< nummap[ one ] << std::endl;

No es constante, pero es conveniente.

Aquí hay otra forma que usa las características de C ++ 11. Esto es constante, no hereda un contenedor STL y es un poco más ordenado:

#include <vector>
#include <string>
#include <algorithm>
#include <iostream>

//These stay together and must be modified together
enum test{ one, two, three, five=5, six, seven };
std::string enum_to_str(test const& e)
{
    typedef std::pair<int,std::string> mapping;
    auto m = [](test const& e,std::string const& s){return mapping(static_cast<int>(e),s);}; 
    std::vector<mapping> const nummap = 
    { 
        m(one,"one"), 
        m(two,"two"), 
        m(three,"three"),
        m(five,"five"),
        m(six,"six"),
        m(seven,"seven"),
    };
    for(auto i  : nummap)
    {
        if(i.first==static_cast<int>(e))
        {
            return i.second;
        }
    }
    return "";
}

int main()
{
//  std::cout<< enum_to_str( 46 ) << std::endl; //compilation will fail
    std::cout<< "Invalid enum to string : [" << enum_to_str( test(46) ) << "]"<<std::endl; //returns an empty string
    std::cout<< "Enumval five to string : ["<< enum_to_str( five ) << "] "<< std::endl; //works
    return 0;
}

1
Es perfectamente legal. Lo hago todo el tiempo.
Jonathan Graehl

Buena solución. Esto es C ++, por lo que usar el mapa stl está bien.
Adam Bruss

4
#include <stdarg.h>
#include <algorithm>
#include <string> 
#include <vector>
#include <sstream>
#include <map>

#define SMART_ENUM(EnumName, ...)                                   \
class EnumName                                                      \
{                                                                   \
private:                                                            \
    static std::map<int, std::string> nameMap;                      \
public:                                                             \
    enum {__VA_ARGS__};                                             \
private:                                                            \
    static std::map<int, std::string> initMap()                     \
    {                                                               \
        using namespace std;                                        \
                                                                    \
        int val = 0;                                                \
        string buf_1, buf_2, str = #__VA_ARGS__;                    \
        replace(str.begin(), str.end(), '=', ' ');                  \
        stringstream stream(str);                                   \
        vector<string> strings;                                     \
        while (getline(stream, buf_1, ','))                         \
            strings.push_back(buf_1);                               \
        map<int, string> tmp;                                       \
        for(vector<string>::iterator it = strings.begin();          \
                                               it != strings.end(); \
                                               ++it)                \
        {                                                           \
            buf_1.clear(); buf_2.clear();                           \
            stringstream localStream(*it);                          \
            localStream>> buf_1 >> buf_2;                           \
            if(buf_2.size() > 0)                                    \
                val = atoi(buf_2.c_str());                          \
            tmp[val++] = buf_1;                                     \
        }                                                           \
        return tmp;                                                 \
    }                                                               \
public:                                                             \
    static std::string toString(int aInt)                           \
    {                                                               \
        return nameMap[aInt];                                       \
    }                                                               \
};                                                                  \
std::map<int, std::string>                                          \
EnumName::nameMap = EnumName::initMap();

Uso:

SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
cout<<MyEnum::toString(MyEnum::TWO);
cout<<MyEnum::toString(10);

1
Me gusta su API, pero desafortunadamente, su SmartEnum en realidad no crea un "tipo" enum. No se puede hacer MyEnum x = MyEnum::TWO;. He publicado mi edición de su clase para apoyar esto.
Mark Lakata

4

La macro solución de Suma es agradable. Sin embargo, no es necesario tener dos macros diferentes. C ++ felizmente incluirá un encabezado dos veces. Solo deja de lado al guardia incluido.

Entonces tendrías un foobar.h definiendo solo

ENUM(Foo, 1)
ENUM(Bar, 2)

y lo incluirías así:

#define ENUMFACTORY_ARGUMENT "foobar.h"
#include "enumfactory.h"

enumfactory.h hará 2 #include ENUMFACTORY_ARGUMENTs. En la primera ronda, expande ENUM como el de Suma DECLARE_ENUM; en la segunda ronda ENUM funciona como DEFINE_ENUM.

También puede incluir enumfactory.h varias veces, siempre que pase diferentes # define para ENUMFACTORY_ARGUMENT


parece que suma movió la respuesta aquí . Es posible que desee incluir el enlace en su respuesta. Solo encontré el comentario por casualidad y sin sumas, esta respuesta no tiene sentido
idclev 463035818

3

Tenga en cuenta que su función de conversión idealmente debería devolver un const char *.

Si puede permitirse poner sus enumeraciones en sus archivos de encabezado separados, tal vez podría hacer algo como esto con las macros (oh, esto será feo):

#include "enum_def.h"
#include "colour.h"
#include "enum_conv.h"
#include "colour.h"

Donde enum_def.h tiene:

#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) enum NAME {
#define ENUM_ADD(NAME, VALUE) NAME = VALUE,
#define ENUM_END };

Y enum_conv.h tiene:

#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) const char *##NAME##_to_string(NAME val) { switch (val) {
#define ENUM_ADD(NAME, VALUE) case NAME: return #NAME;
#define ENUM_END default: return "Invalid value"; } }

Y finalmente, colour.h tiene:

ENUM_START(colour)
ENUM_ADD(red,   0xff0000)
ENUM_ADD(green, 0x00ff00)
ENUM_ADD(blue,  0x0000ff)
ENUM_END

Y puede usar la función de conversión como:

printf("%s", colour_to_string(colour::red));

Esto es feo, pero es la única forma (en el nivel de preprocesador) que le permite definir su enumeración solo en un solo lugar en su código. Por lo tanto, su código no es propenso a errores debido a modificaciones en la enumeración. Su definición de enumeración y la función de conversión siempre estarán sincronizadas. Sin embargo, repito, esto es feo :)


3

Otra respuesta: en algunos contextos, tiene sentido definir su enumeración en un formato sin código, como un archivo CSV, YAML o XML, y luego generar tanto el código de enumeración C ++ como el código de cadena a partir de la definición. Este enfoque puede o no ser práctico en su aplicación, pero es algo a tener en cuenta.


3

Esta es una modificación a la respuesta @ user3360260. Tiene las siguientes características nuevas

  • MyEnum fromString(const string&) apoyo
  • compila con VisualStudio 2012
  • la enumeración es un tipo de POD real (no solo declaraciones constantes), por lo que puede asignarlo a una variable.
  • Se agregó la función "rango" de C ++ (en forma de vector) para permitir la iteración "foreach" sobre la enumeración

Uso:

SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
MyEnum foo = MyEnum::TWO;
cout << MyEnum::toString(foo);  // static method
cout << foo.toString();         // member method
cout << MyEnum::toString(MyEnum::TWO);
cout << MyEnum::toString(10);
MyEnum foo = myEnum::fromString("TWO");

// C++11 iteration over all values
for( auto x : MyEnum::allValues() )
{
  cout << x.toString() << endl;
}

Aquí está el código

#define SMART_ENUM(EnumName, ...)                                   \
class EnumName                                                      \
{                                                                   \
public:                                                             \
    EnumName() : value(0) {}                                        \
    EnumName(int x) : value(x) {}                                   \
public:                                                             \
    enum {__VA_ARGS__};                                             \
private:                                                            \
    static void initMap(std::map<int, std::string>& tmp)                     \
    {                                                               \
        using namespace std;                                        \
                                                                    \
        int val = 0;                                                \
        string buf_1, buf_2, str = #__VA_ARGS__;                    \
        replace(str.begin(), str.end(), '=', ' ');                  \
        stringstream stream(str);                                   \
        vector<string> strings;                                     \
        while (getline(stream, buf_1, ','))                         \
            strings.push_back(buf_1);                               \
        for(vector<string>::iterator it = strings.begin();          \
                                                it != strings.end(); \
                                                ++it)                \
        {                                                           \
            buf_1.clear(); buf_2.clear();                           \
            stringstream localStream(*it);                          \
            localStream>> buf_1 >> buf_2;                           \
            if(buf_2.size() > 0)                                    \
                val = atoi(buf_2.c_str());                          \
            tmp[val++] = buf_1;                                     \
        }                                                           \
    }                                                               \
    int value;                                                      \
public:                                                             \
    operator int () const { return value; }                         \
    std::string toString(void) const {                              \
            return toString(value);                                 \
    }                                                               \
    static std::string toString(int aInt)                           \
    {                                                               \
        return nameMap()[aInt];                                     \
    }                                                               \
    static EnumName fromString(const std::string& s)                \
    {                                                               \
        auto it = find_if(nameMap().begin(), nameMap().end(), [s](const std::pair<int,std::string>& p) { \
            return p.second == s;                                   \
        });                                                         \
        if (it == nameMap().end()) {                                \
        /*value not found*/                                         \
            throw EnumName::Exception();                            \
        } else {                                                    \
            return EnumName(it->first);                             \
        }                                                           \
    }                                                               \
    class Exception : public std::exception {};                     \
    static std::map<int,std::string>& nameMap() {                   \
      static std::map<int,std::string> nameMap0;                    \
      if (nameMap0.size() ==0) initMap(nameMap0);                   \
      return nameMap0;                                              \
    }                                                               \
    static std::vector<EnumName> allValues() {                      \
      std::vector<EnumName> x{ __VA_ARGS__ };                       \
      return x;                                                     \
    }                                                               \
    bool operator<(const EnumName a) const { return (int)*this < (int)a; } \
};         

Tenga en cuenta que la conversión a String es una búsqueda rápida, mientras que la conversión de String es una búsqueda lineal lenta. Pero las cadenas son tan caras de todos modos (y el archivo asociado IO), no sentí la necesidad de optimizar o usar un bimap.


Usted y el usuario 3360260 tienen una buena solución. ¿Por qué no tener un multimapa en su lugar?
Vincent

3

Aquí una solución de un archivo (basada en la elegante respuesta de @Marcin:

#include <iostream>

#define ENUM_TXT \
X(Red) \
X(Green) \
X(Blue) \
X(Cyan) \
X(Yellow) \
X(Magenta) \

enum Colours {
#   define X(a) a,
ENUM_TXT
#   undef X
    ColoursCount
};

char const* const colours_str[] = {
#   define X(a) #a,
ENUM_TXT
#   undef X
    0
};

std::ostream& operator<<(std::ostream& os, enum Colours c)
{
    if (c >= ColoursCount || c < 0) return os << "???";
    return os << colours_str[c] << std::endl;
}

int main()
{
    std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}

2

Hago esto con clases separadas de envoltura enum de lado a lado que se generan con macros. Hay varias ventajas:

  • Puede generarlos para enumeraciones que no defino (por ejemplo: enumeraciones de encabezado de plataforma del sistema operativo)
  • Puede incorporar la comprobación de rango en la clase de contenedor
  • Puede hacer un formato "más inteligente" con enumeraciones de campo de bits

La desventaja, por supuesto, es que necesito duplicar los valores de enumeración en las clases de formateador, y no tengo ningún script para generarlos. Aparte de eso, sin embargo, parece funcionar bastante bien.

Aquí hay un ejemplo de una enumeración de mi base de código, sin todo el código de marco que implementa las macros y las plantillas, pero puede hacerse una idea:

enum EHelpLocation
{
    HELP_LOCATION_UNKNOWN   = 0, 
    HELP_LOCAL_FILE         = 1, 
    HELP_HTML_ONLINE        = 2, 
};
class CEnumFormatter_EHelpLocation : public CEnumDefaultFormatter< EHelpLocation >
{
public:
    static inline CString FormatEnum( EHelpLocation eValue )
    {
        switch ( eValue )
        {
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCATION_UNKNOWN );
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCAL_FILE );
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_HTML_ONLINE );
        default:
            return FormatAsNumber( eValue );
        }
    }
};
DECLARE_RANGE_CHECK_CLASS( EHelpLocation, CRangeInfoSequential< HELP_HTML_ONLINE > );
typedef ESmartEnum< EHelpLocation, HELP_LOCATION_UNKNOWN, CEnumFormatter_EHelpLocation, CRangeInfo_EHelpLocation > SEHelpLocation;

La idea es, en lugar de usar EHelpLocation, usar SEHelpLocation; todo funciona igual, pero obtienes una comprobación de rango y un método 'Format ()' en la variable enum. Si necesita formatear un valor independiente, puede usar CEnumFormatter_EHelpLocation :: FormatEnum (...).

Espero que esto sea útil. Me doy cuenta de que esto tampoco aborda la pregunta original sobre un guión para generar realmente la otra clase, pero espero que la estructura ayude a alguien a tratar de resolver el mismo problema o escribir dicho guión.


2

Es un software inédito, pero parece que BOOST_ENUM de Frank Laub podría cumplir con los requisitos. La parte que me gusta es que puedes definir una enumeración dentro del alcance de una clase que la mayoría de las enumeraciones basadas en Macro generalmente no te permiten hacer. Se encuentra en Boost Vault en: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=& No ha visto ningún desarrollo desde 2006, así que no sepa qué tan bien se compila con los nuevos lanzamientos de Boost. Busque en libs / test un ejemplo de uso.


2

Esta fue mi solución con BOOST:

#include <boost/preprocessor.hpp>

#define X_STR_ENUM_TOSTRING_CASE(r, data, elem)                                 \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define X_ENUM_STR_TOENUM_IF(r, data, elem)                                     \
    else if(data == BOOST_PP_STRINGIZE(elem)) return elem;

#define STR_ENUM(name, enumerators)                                             \
    enum name {                                                                 \
        BOOST_PP_SEQ_ENUM(enumerators)                                          \
    };                                                                          \
                                                                                \
    inline const QString enumToStr(name v)                                      \
    {                                                                           \
        switch (v)                                                              \
        {                                                                       \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                X_STR_ENUM_TOSTRING_CASE,                                       \
                name,                                                           \
                enumerators                                                     \
            )                                                                   \
                                                                                \
            default:                                                            \
                return "[Unknown " BOOST_PP_STRINGIZE(name) "]";                \
        }                                                                       \
    }                                                                           \
                                                                                \
    template <typename T>                                                       \
    inline const T strToEnum(QString v);                                        \
                                                                                \
    template <>                                                                 \
    inline const name strToEnum(QString v)                                      \
    {                                                                           \
        if(v=="")                                                               \
            throw std::runtime_error("Empty enum value");                       \
                                                                                \
        BOOST_PP_SEQ_FOR_EACH(                                                  \
            X_ENUM_STR_TOENUM_IF,                                               \
            v,                                                                  \
            enumerators                                                         \
        )                                                                       \
                                                                                \
        else                                                                    \
            throw std::runtime_error(                                           \
                        QString("[Unknown value %1 for enum %2]")               \
                            .arg(v)                                             \
                            .arg(BOOST_PP_STRINGIZE(name))                      \
                                .toStdString().c_str());                        \
    }

Para crear una enumeración, declare:

STR_ENUM
(
    SERVICE_RELOAD,
        (reload_log)
        (reload_settings)
        (reload_qxml_server)
)

Para conversiones:

SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
QString serviceReloadStr = enumToStr(reload_log);

2

Quiero publicar esto en caso de que alguien lo encuentre útil.

En mi caso, simplemente necesito generar ToString()y FromString()funciones para una sola enumeración de C ++ 11 desde un solo .hpparchivo.

Escribí un script de Python que analiza el archivo de encabezado que contiene los elementos de enumeración y genera las funciones en un nuevo .cpparchivo.

Puede agregar este script en CMakeLists.txt con execute_process , o como un evento previo a la compilación en Visual Studio. El .cpparchivo se generará automáticamente, sin la necesidad de actualizarlo manualmente cada vez que se agregue un nuevo elemento de enumeración.

generate_enum_strings.py

# This script is used to generate strings from C++ enums

import re
import sys
import os

fileName = sys.argv[1]
enumName = os.path.basename(os.path.splitext(fileName)[0])

with open(fileName, 'r') as f:
    content = f.read().replace('\n', '')

searchResult = re.search('enum(.*)\{(.*?)\};', content)
tokens = searchResult.group(2)
tokens = tokens.split(',')
tokens = map(str.strip, tokens)
tokens = map(lambda token: re.search('([a-zA-Z0-9_]*)', token).group(1), tokens)

textOut = ''
textOut += '\n#include "' + enumName + '.hpp"\n\n'
textOut += 'namespace myns\n'
textOut += '{\n'
textOut += '    std::string ToString(ErrorCode errorCode)\n'
textOut += '    {\n'
textOut += '        switch (errorCode)\n'
textOut += '        {\n'

for token in tokens:
    textOut += '        case ' + enumName + '::' + token + ':\n'
    textOut += '            return "' + token + '";\n'

textOut += '        default:\n'
textOut += '            return "Last";\n'
textOut += '        }\n'
textOut += '    }\n'
textOut += '\n'
textOut += '    ' + enumName + ' FromString(const std::string &errorCode)\n'
textOut += '    {\n'
textOut += '        if ("' + tokens[0] + '" == errorCode)\n'
textOut += '        {\n'
textOut += '            return ' + enumName + '::' + tokens[0] + ';\n'
textOut += '        }\n'

for token in tokens[1:]:
    textOut += '        else if("' + token + '" == errorCode)\n'
    textOut += '        {\n'
    textOut += '            return ' + enumName + '::' + token + ';\n'
    textOut += '        }\n'

textOut += '\n'
textOut += '        return ' + enumName + '::Last;\n'
textOut += '    }\n'
textOut += '}\n'

fileOut = open(enumName + '.cpp', 'w')
fileOut.write(textOut)

Ejemplo:

ErrorCode.hpp

#pragma once

#include <string>
#include <cstdint>

namespace myns
{
    enum class ErrorCode : uint32_t
    {
        OK = 0,
        OutOfSpace,
        ConnectionFailure,
        InvalidJson,
        DatabaseFailure,
        HttpError,
        FileSystemError,
        FailedToEncrypt,
        FailedToDecrypt,
        EndOfFile,
        FailedToOpenFileForRead,
        FailedToOpenFileForWrite,
        FailedToLaunchProcess,

        Last
    };

    std::string ToString(ErrorCode errorCode);
    ErrorCode FromString(const std::string &errorCode);
}

correr python generate_enum_strings.py ErrorCode.hpp

Resultado:

ErrorCode.cpp

#include "ErrorCode.hpp"

namespace myns
{
    std::string ToString(ErrorCode errorCode)
    {
        switch (errorCode)
        {
        case ErrorCode::OK:
            return "OK";
        case ErrorCode::OutOfSpace:
            return "OutOfSpace";
        case ErrorCode::ConnectionFailure:
            return "ConnectionFailure";
        case ErrorCode::InvalidJson:
            return "InvalidJson";
        case ErrorCode::DatabaseFailure:
            return "DatabaseFailure";
        case ErrorCode::HttpError:
            return "HttpError";
        case ErrorCode::FileSystemError:
            return "FileSystemError";
        case ErrorCode::FailedToEncrypt:
            return "FailedToEncrypt";
        case ErrorCode::FailedToDecrypt:
            return "FailedToDecrypt";
        case ErrorCode::EndOfFile:
            return "EndOfFile";
        case ErrorCode::FailedToOpenFileForRead:
            return "FailedToOpenFileForRead";
        case ErrorCode::FailedToOpenFileForWrite:
            return "FailedToOpenFileForWrite";
        case ErrorCode::FailedToLaunchProcess:
            return "FailedToLaunchProcess";
        case ErrorCode::Last:
            return "Last";
        default:
            return "Last";
        }
    }

    ErrorCode FromString(const std::string &errorCode)
    {
        if ("OK" == errorCode)
        {
            return ErrorCode::OK;
        }
        else if("OutOfSpace" == errorCode)
        {
            return ErrorCode::OutOfSpace;
        }
        else if("ConnectionFailure" == errorCode)
        {
            return ErrorCode::ConnectionFailure;
        }
        else if("InvalidJson" == errorCode)
        {
            return ErrorCode::InvalidJson;
        }
        else if("DatabaseFailure" == errorCode)
        {
            return ErrorCode::DatabaseFailure;
        }
        else if("HttpError" == errorCode)
        {
            return ErrorCode::HttpError;
        }
        else if("FileSystemError" == errorCode)
        {
            return ErrorCode::FileSystemError;
        }
        else if("FailedToEncrypt" == errorCode)
        {
            return ErrorCode::FailedToEncrypt;
        }
        else if("FailedToDecrypt" == errorCode)
        {
            return ErrorCode::FailedToDecrypt;
        }
        else if("EndOfFile" == errorCode)
        {
            return ErrorCode::EndOfFile;
        }
        else if("FailedToOpenFileForRead" == errorCode)
        {
            return ErrorCode::FailedToOpenFileForRead;
        }
        else if("FailedToOpenFileForWrite" == errorCode)
        {
            return ErrorCode::FailedToOpenFileForWrite;
        }
        else if("FailedToLaunchProcess" == errorCode)
        {
            return ErrorCode::FailedToLaunchProcess;
        }
        else if("Last" == errorCode)
        {
            return ErrorCode::Last;
        }

        return ErrorCode::Last;
    }
}


2

Agregando aún más simplicidad de uso a la fantástica respuesta de Jasper Bekkers :

Configurar una vez:

#define MAKE_ENUM(VAR) VAR,
#define MAKE_STRINGS(VAR) #VAR,
#define MAKE_ENUM_AND_STRINGS(source, enumName, enumStringName) \
    enum enumName { \
    source(MAKE_ENUM) \
    };\
const char* const enumStringName[] = { \
    source(MAKE_STRINGS) \
    };

Luego, para su uso:

#define SOME_ENUM(DO) \
    DO(Foo) \
    DO(Bar) \
    DO(Baz)
...
MAKE_ENUM_AND_STRINGS(SOME_ENUM, someEnum, someEnumNames)

2

Podrías usar una biblioteca de reflexión, como Ponder . Registra las enumeraciones y luego puede convertirlas de un lado a otro con la API.

enum class MyEnum
{
    Zero = 0,
    One  = 1,
    Two  = 2
};

ponder::Enum::declare<MyEnum>()
    .value("Zero", MyEnum::Zero)
    .value("One",  MyEnum::One)
    .value("Two",  MyEnum::Two);

ponder::EnumObject zero(MyEnum::Zero);

zero.name(); // -> "Zero"

1

Un problema con la respuesta 0 es que los valores binarios enum no necesariamente comienzan en 0 y no son necesariamente contiguos.

Cuando necesito esto, generalmente:

  • sacar la definición de enumeración en mi fuente
  • editarlo para obtener solo los nombres
  • haga una macro para cambiar el nombre de la cláusula case en la pregunta, aunque generalmente en una línea: case foo: return "foo";
  • agregue el interruptor, el valor predeterminado y otra sintaxis para que sea legal

1

El siguiente script de Ruby intenta analizar los encabezados y acumula las fuentes necesarias junto con los encabezados originales.

#! /usr/bin/env ruby

# Let's "parse" the headers
# Note that using a regular expression is rather fragile
# and may break on some inputs

GLOBS = [
  "toto/*.h",
  "tutu/*.h",
  "tutu/*.hxx"
]

enums = {}
GLOBS.each { |glob|
  Dir[glob].each { |header|
    enums[header] = File.open(header, 'rb') { |f|
      f.read
    }.scan(/enum\s+(\w+)\s+\{\s*([^}]+?)\s*\}/m).collect { |enum_name, enum_key_and_values|
      [
        enum_name, enum_key_and_values.split(/\s*,\s*/).collect { |enum_key_and_value|
          enum_key_and_value.split(/\s*=\s*/).first
        }
      ]
    }
  }
}


# Now we build a .h and .cpp alongside the parsed headers
# using the template engine provided with ruby
require 'erb'

template_h = ERB.new <<-EOS
#ifndef <%= enum_name %>_to_string_h_
#define <%= enum_name %>_to_string_h_ 1

#include "<%= header %>"
char* enum_to_string(<%= enum_name %> e);

#endif
EOS

template_cpp = ERB.new <<-EOS
#include "<%= enum_name %>_to_string.h"

char* enum_to_string(<%= enum_name %> e)
{
  switch (e)
  {<% enum_keys.each do |enum_key| %>
    case <%= enum_key %>: return "<%= enum_key %>";<% end %>
    default: return "INVALID <%= enum_name %> VALUE";
  }
}
EOS

enums.each { |header, enum_name_and_keys|
  enum_name_and_keys.each { |enum_name, enum_keys|
    File.open("#{File.dirname(header)}/#{enum_name}_to_string.h", 'wb') { |built_h|
      built_h.write(template_h.result(binding))
    }

    File.open("#{File.dirname(header)}/#{enum_name}_to_string.cpp", 'wb') { |built_cpp|
      built_cpp.write(template_cpp.result(binding))
    }
  }
}

El uso de expresiones regulares hace que este "analizador" sea bastante frágil, es posible que no pueda manejar sus encabezados específicos con gracia.

Supongamos que tiene un encabezado toto / ah, que contiene definiciones para las enumeraciones MyEnum y MyEnum2. El script construirá:

toto/MyEnum_to_string.h
toto/MyEnum_to_string.cpp
toto/MyEnum2_to_string.h
toto/MyEnum2_to_string.cpp

Las soluciones más robustas serían:

  • Cree todas las fuentes que definan enumeraciones y sus operaciones desde otra fuente. Esto significa que definirá sus enumeraciones en un archivo XML / YML / lo que sea mucho más fácil de analizar que C / C ++.
  • Use un compilador real como el sugerido por Avdi.
  • Utilice macros de preprocesador con o sin plantillas.

0

Esa es prácticamente la única forma en que se puede hacer (una matriz de cadenas también podría funcionar).

El problema es que, una vez que se compila un programa en C, el valor binario de la enumeración es todo lo que se usa, y el nombre desaparece.


0

Aquí hay un programa CLI que escribí para convertir fácilmente las enumeraciones en cadenas. Es fácil de usar, y toma alrededor de 5 segundos para hacerlo (incluido el tiempo para cd al directorio que contiene el programa, luego ejecutarlo, pasándole el archivo que contiene la enumeración).

Descargue aquí: http://www.mediafire.com/?nttignoozzz

Tema de discusión aquí: http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html

Ejecute el programa con el argumento "--help" para obtener una descripción de cómo usarlo.


¿Podría poner esto en un repositorio en algún lugar (github, código de google o bitbucket) y publicar el enlace aquí, en lugar de mediafire? Me gustaría ayudar a la gente que quiera entenderlo :)
Edu Felipe

0

No hace mucho tiempo, hice un truco para que las enumeraciones se mostraran correctamente en QComboBox y para tener la definición de enum y representaciones de cadenas como una sola declaración

#pragma once
#include <boost/unordered_map.hpp>

namespace enumeration
{

   struct enumerator_base : boost::noncopyable
   {
      typedef
         boost::unordered_map<int, std::wstring>
         kv_storage_t;
      typedef
         kv_storage_t::value_type
         kv_type;
      kv_storage_t const & kv() const
      {
         return storage_;
      }

      LPCWSTR name(int i) const
      {
         kv_storage_t::const_iterator it = storage_.find(i);
         if(it != storage_.end())
            return it->second.c_str();
         return L"empty";
      }

   protected:
      kv_storage_t storage_;
   };

   template<class T>
   struct enumerator;

   template<class D>
   struct enum_singleton : enumerator_base
   {
      static enumerator_base const & instance()
      {
         static D inst;
         return inst;
      }
   };
}

#define QENUM_ENTRY(K, V, N)  K, N storage_.insert(std::make_pair((int)K, V));

#define QBEGIN_ENUM(NAME, C)   \
enum NAME                     \
{                             \
   C                          \
}                             \
};                            \
}                             \

#define QEND_ENUM(NAME) \
};                     \
namespace enumeration  \
{                      \
template<>             \
struct enumerator<NAME>\
   : enum_singleton< enumerator<NAME> >\
{                      \
   enumerator()        \
   {

//usage
/*
QBEGIN_ENUM(test_t,
   QENUM_ENTRY(test_entry_1, L"number uno",
   QENUM_ENTRY(test_entry_2, L"number dos",
   QENUM_ENTRY(test_entry_3, L"number tres",
QEND_ENUM(test_t)))))
*/

Ahora enumeration::enum_singleton<your_enum>::instance()puede convertir las enumeraciones en cadenas. Si reemplaza kv_storage_tcon boost::bimap, también podrá realizar la conversión hacia atrás. Se introdujo la clase base común para el convertidor para almacenarlo en el objeto Qt, porque los objetos Qt no podían ser plantillas

Apariencia previa


0

Como variante, use lib simple> http://codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C

En el codigo

#include <EnumString.h>

enum FORM {
    F_NONE = 0,
    F_BOX,
    F_CUBE,
    F_SPHERE,
};

agregar líneas

Begin_Enum_String( FORM )
{
    Enum_String( F_NONE );
    Enum_String( F_BOX );
    Enum_String( F_CUBE );
    Enum_String( F_SPHERE );
}
End_Enum_String;

Funciona bien, si los valores en enum no son duplicados .

Ejemplo de uso

enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );

y viceversa

assert( EnumString< FORM >::To( f, str ) );

0

Aquí hay un intento de obtener operadores de flujo << y >> en enumeración automáticamente con un comando de macro de una sola línea ...

Definiciones:

#include <string>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>

#define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__)
#define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__)
#define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__)
#define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__)
#define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__)
#define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__)
#define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__)
#define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__)
#define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__)
#define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__)
#define MAKE_STRING10_(str) #str

#define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__)
#define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__)

#define MAKE_ENUM_(attribute, name, ...) name { __VA_ARGS__ }; \
    attribute std::istream& operator>>(std::istream& is, name& e) { \
        const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
        std::string str; \
        std::istream& r = is >> str; \
        const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \
        const std::vector<std::string> enumStr(name##Str, name##Str + len); \
        const std::vector<std::string>::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \
        if (it != enumStr.end())\
            e = name(it - enumStr.begin()); \
        else \
            throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \
        return r; \
    }; \
    attribute std::ostream& operator<<(std::ostream& os, const name& e) { \
        const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
        return (os << name##Str[e]); \
    }

Uso:

// Declare global enum
enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43);

class Essai {
public:
    // Declare enum inside class
    enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4);

};

int main() {
    std::cout << Essai::Item1 << std::endl;

    Essai::Test ddd = Essai::Item1;
    std::cout << ddd << std::endl;

    std::istringstream strm("Item2");
    strm >> ddd;

    std::cout << (int) ddd << std::endl;
    std::cout << ddd << std::endl;
}

Sin embargo, no estoy seguro de las limitaciones de este esquema ... ¡los comentarios son bienvenidos!


0
#include <iostream>
#include <map>
#define IDMAP(x) (x,#x)

std::map<int , std::string> enToStr;
class mapEnumtoString
{
public:
    mapEnumtoString(){  }
    mapEnumtoString& operator()(int i,std::string str)
    {
        enToStr[i] = str;
        return *this;
    }
public:
   std::string operator [] (int i)
    {
        return enToStr[i];
    }

};
mapEnumtoString k;
mapEnumtoString& init()
{
    return k;
}

int main()
{

init()
    IDMAP(1)
    IDMAP(2)
    IDMAP(3)
    IDMAP(4)
    IDMAP(5);
std::cout<<enToStr[1];
std::cout<<enToStr[2];
std::cout<<enToStr[3];
std::cout<<enToStr[4];
std::cout<<enToStr[5];
}

2
Por favor explique por qué esta es la respuesta.
Alok
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.