Saltar a contenido

💡 Resumen:Tutorial de enum en C++: los enums clásicos, enum class de C++11, enums dentro de clases y cómo la reflexión de C++26 (std::meta) resuelve por fin la conversión de enum a string sin macros ni tablas de búsqueda.

Última actualización:2026-10-24

Tipos de enumeración enum en C++

Sobre el autor:

Cuenta pública personal de WeChat:

Tipos de enumeración enum en C++ | C++那些事

Comportamiento tradicional

Las enumeraciones tienen los problemas siguientes:

  • El ámbito no está restringido, lo que provoca fácilmente conflictos de nombres. Por ejemplo, lo siguiente no llega a compilar:

enum Color
{
    RED,
    BLUE
};
enum Feeling
{
    EXCITED,
    BLUE
};

int main()
{
    Color a = BLUE; // error
    Feeling b = EXCITED;
    std::cout << a << ":" << b << std::endl;
    return 0;
}
```- Se convierten implícitamente a int
- El tipo real que representa a la variable de enumeración no puede especificarse explícitamente, por lo que no se admite la declaración previa de tipos de enumeración.

Para la implementación concreta, consulta: [tradition_color.cpp](https://github.com/Light-City/CPlusPlusThings/tree/master/basic_content/enum/tradition_color.cpp)

## Enfoque clásico

Una forma simple de resolver los conflictos de nombres causados por el ámbito no restringido es añadir un prefijo al nombrar las variables de enumeración, cambiando por ejemplo el caso anterior a COLOR_BLUE y FEELING_BLUE.

En general, por coherencia, añadiríamos el mismo prefijo a todas las constantes. Pero definir así las variables de enumeración resulta pesado. En programas C quizá no quede más remedio. Sin embargo, a quien programa en C++ este método no le gusta nada. La alternativa son los espacios de nombres:
```c++
namespace Color 
{
    enum Type
    {
        RED=15,
        YELLOW,
        BLUE
    };
};
Después de esto ya se puede usar Color::Type c = Color::RED; para definir nuevas variables de enumeración. Si se escribe using namespace Color, el prefijo puede omitirse y el código se simplifica. Sin embargo, como un espacio de nombres puede ampliarse después, el cierre de ámbito que ofrece no es muy fuerte. En proyectos grandes sigue siendo posible que distintas personas den el mismo nombre de tipo de enumeración a cosas distintas.

El método más “eficaz” es usar una clase o una estructura para restringir su ámbito; por ejemplo: la forma de definir nuevas variables es la misma que con el espacio de nombres anterior. Eso sí, así no hay que preocuparse de que la clase se modifique en otro lugar. Aquí se usa una estructura en lugar de una clase porque, de entrada, se quiere que estas constantes sean accesibles públicamente.

struct Color1
{
    enum Type
    {
        RED=102,
        YELLOW,
        BLUE
    };
};
Para la implementación concreta, consulta: classic_practice.cpp

Las clases de enumeración de C++11

El enfoque anterior resuelve el primer problema, pero nada puede hacer con los otros dos. Afortunadamente, el estándar C++11 introdujo las “clases de enumeración” (enum class), que resuelven bastante bien los problemas anteriores.

  • El ámbito del nuevo enum ya no es global
  • No puede convertirse implícitamente a otros tipos

/**
 * @brief C++11的枚举类
 * 下面等价于enum class Color2:int
 */
enum class Color2
{
    RED=2,
    YELLOW,
    BLUE
};
Color2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl; //必须转!
- Se puede especificar un tipo concreto para almacenar el enum

enum class Color3:char;  // 前向声明

// 定义
enum class Color3:char 
{
    RED='r',
    BLUE
};
char c3 = static_cast<char>(Color3::RED);
Para la implementación concreta, consulta: classic_practice.cpp

Tipos de enumeración dentro de una clase

A veces queremos que ciertas constantes solo sean válidas dentro de una clase. Como las constantes de macro definidas con #define son globales y no sirven para ello, se piensa en usar miembros de datos calificados con const. Y ciertamente los miembros de datos const existen, pero su significado no es el que esperamos.

Un miembro de datos const solo es constante durante la vida de un objeto concreto, pero para la clase entera es variable, porque la clase puede crear varios objetos y el valor del miembro de datos const puede ser distinto en cada uno.

No se puede inicializar un miembro de datos const en la declaración de la clase. El uso siguiente es erróneo, porque mientras no se cree el objeto de la clase el compilador no sabe cuánto vale SIZE. (antes del estándar C++11)

class A 
{
  const int SIZE = 100;   // 错误,企图在类声明中初始化 const 数据成员 
  int array[SIZE];  // 错误,未知的 SIZE 
}; 
Lo correcto es hacerlo en la lista de inicialización del constructor de la clase:

class A 
{
  A(int size);  // 构造函数 
  const int SIZE ;    
}; 
A::A(int size) : SIZE(size)  // 构造函数的定义
{ 

} 
A  a(100); // 对象 a 的 SIZE 值为 100 
A  b(200); // 对象 b 的 SIZE 值为 200 
¿Cómo se puede establecer una constante que sea fija para toda la clase?

No cuentes con los miembros de datos const; debe hacerse con constantes de enumeración dentro de la clase. Por ejemplo:

class Person{
public:
    typedef enum {
        BOY = 0,
        GIRL
    }SexType;
};
//访问的时候通过,Person::BOY或者Person::GIRL来进行访问。
Las constantes de enumeración no ocupan espacio de almacenamiento del objeto; se evalúan todas en tiempo de compilación.

El inconveniente de las constantes de enumeración es que su tipo de datos implícito es entero, su valor máximo es limitado y no puede representar números en coma flotante.

De enum a string: la respuesta de la reflexión de C++26

Convertir un valor de enum en su nombre es un problema que casi todo proyecto C++ termina encontrando, y hasta C++26 la librería estándar no tenía respuesta. Los remedios, de más antiguo a más reciente: (1) X-macros, que abusan del preprocesador para mantener la lista de enumeradores en un solo sitio; (2) tablas de búsqueda escritas a mano o especialización de plantillas, donde cada enum nuevo implica otra tabla que mantener; (3) librerías de terceros como magic_enum, que sacan los nombres con trucos del compilador pero imponen límites de tamaño y tipo subyacente.

La reflexión estática de C++26 (propuesta P2996, std::meta) por fin lo convierte en unas líneas reales. Una función enumera los miembros de cualquier enum en tiempo de compilación y lee el nombre de cada enumerador:

#include <meta>

template <typename E>
    requires std::is_enum_v<E>
std::string enum_to_string(E value) {
    template for (constexpr auto e : std::meta::enumerators_of(^^E)) {
        if (value == [:e:]) {
            return std::meta::identifier_of(e);
        }
    }
    throw std::invalid_argument("unknown enumerator");
}

Con una sola función queda cubierto todo enum del código base. Renombrar un enumerador actualiza la cadena automáticamente, y un desajuste se detecta al compilar en lugar de mostrar una etiqueta equivocada en ejecución.

Una advertencia honesta: el fragmento sigue P2996 tal como se incorporó al borrador de C++26. Las grafías exactas aún se estaban asentando en las previews de compiladores (EDG y Clang explorers), así que revisa la tabla de soporte de tu herramienta antes de usarlo. Hasta entonces, la tabla de búsqueda sigue siendo la respuesta más portable y sin dependencias.

Preguntas frecuentes

¿Cómo convierto un enum a string en C++?

Antes de C++26 no hay forma estándar: toca elegir entre tabla de búsqueda, especialización de plantillas, X-macros o una librería como magic_enum. La reflexión estática de C++26 enumera los miembros en compilación y lee el nombre directamente, eliminando el mantenimiento de las cuatro.

¿La librería estándar tiene conversión integrada de enum a string?

No hasta C++26. Los flujos y std::to_string solo formatean el entero subyacente, y por eso cada proyecto acaba escribiendo su propio helper.

¿Cuándo podré usar la reflexión de C++26?

C++26 es el estándar próximo y la reflexión ya está en el borrador; EDG y los explorers de Clang ya compilan los ejemplos. Para producción, sigue el soporte de tu compilador y conserva una tabla de búsqueda como respaldo.

Temas relacionados

Comentarios