Historicamente, eu sempre teria usado a enumeração antiga (pouco tipada) para nomear as constantes de bit, e apenas usava a classe de armazenamento explicitamente para armazenar o sinalizador resultante. Aqui, o ônus seria para garantir que minhas enumerações se encaixem no tipo de armazenamento e para acompanhar a associação entre o campo e suas constantes relacionadas.
Eu gosto da ideia de enums strongmente tipados, mas não estou muito confortável com a idéia de que variáveis de tipo enumerado podem conter valores que não estão entre as constantes dessa enumeração.
Por exemplo, assumindo o bit a bit ou foi sobrecarregado:
enum class E1 { A=1, B=2, C=4 };
void test(E1 e) {
switch(e) {
case E1::A: do_a(); break;
case E1::B: do_b(); break;
case E1::C: do_c(); break;
default:
illegal_value();
}
}
// ...
test(E1::A); // ok
test(E1::A | E1::B); // nope
Para a sua terceira opção, você precisa de algum clichê para extrair o tipo de armazenamento da enumeração. Supondo que queremos forçar um tipo subjacente não assinado (podemos lidar com sinal também, com um pouco mais de código):
template <size_t Size> struct IntegralTypeLookup;
template <> struct IntegralTypeLookup<sizeof(int64_t)> { typedef uint64_t Type; };
template <> struct IntegralTypeLookup<sizeof(int32_t)> { typedef uint32_t Type; };
template <> struct IntegralTypeLookup<sizeof(int16_t)> { typedef uint16_t Type; };
template <> struct IntegralTypeLookup<sizeof(int8_t)> { typedef uint8_t Type; };
template <typename IntegralType> struct Integral {
typedef typename IntegralTypeLookup<sizeof(IntegralType)>::Type Type;
};
template <typename ENUM> class EnumeratedFlags {
typedef typename Integral<ENUM>::Type RawType;
RawType raw;
public:
EnumeratedFlags() : raw() {}
EnumeratedFlags(EnumeratedFlags const&) = default;
void set(ENUM e) { raw |= static_cast<RawType>(e); }
void reset(ENUM e) { raw &= ~static_cast<RawType>(e); };
bool test(ENUM e) const { return raw & static_cast<RawType>(e); }
RawType raw_value() const { return raw; }
};
enum class E2: uint8_t { A=1, B=2, C=4 };
typedef EnumeratedFlags<E2> E2Flag;
Isso ainda não fornece IntelliSense ou autocompletion, mas a detecção do tipo de armazenamento é menos feia do que eu esperava originalmente.
Agora, eu encontrei uma alternativa: você pode especificar o tipo de armazenamento para uma enumeração de tipo fraco. Ele ainda tem a mesma sintaxe que em C #
enum E4 : int { ... };
Como ele é de tipagem fraca e converte implicitamente para / from int (ou qualquer tipo de armazenamento escolhido), parece menos estranho ter valores que não correspondam às constantes enumeradas.
A desvantagem é que isso é descrito como "transitório" ...
NB. essa variante adiciona suas constantes enumeradas ao escopo aninhado e fechado, mas você pode contornar isso com um namespace:
namespace E5 {
enum Enum : int { A, B, C };
}
E5::Enum x = E5::A; // or E5::Enum::A