Uma classe enum C ++ pode ter métodos?

145

Eu tenho uma classe enum com dois valores e quero criar um método que receba um valor e retorne o outro. Também quero manter a segurança do tipo (é por isso que uso a classe enum em vez de enumerações).

http://www.cplusplus.com/doc/tutorial/other_data_types/ não menciona nada sobre métodos No entanto, fiquei com a impressão de que qualquer tipo de classe pode ter métodos.

octaviano
fonte
4
Não, eu não posso. Veja aqui .
Juanchopanza
@ octavian Observe minha resposta e repensar sobre seus casos de uso, por favor!
πάντα ῥεῖ
@ πάνταῥεῖ você está totalmente certo, eu li enum, mas pensei em união, matou o comentário.
Eugen Constantin Dinca
@ octavian Você está mesmo pedindo um caso de uso específico ou apenas deseja confirmar as restrições de padrões do c ++ 11 enum class/struct ?
πάντα ῥεῖ
Eu tinha um uso em mente ... e essa era a questão fundamental
oitava

Respostas:

118

Não, eles não podem.

Eu posso entender que a enum classparte das enumerações fortemente tipadas no C ++ 11 pode parecer implicar que você também enumtem classcaracterísticas, mas não é o caso. Meu palpite é que a escolha das palavras-chave foi inspirada no padrão que usamos antes do C ++ 11 para obter enumerações no escopo:

class Foo {
public:
  enum {BAR, BAZ};
};

No entanto, isso é apenas sintaxe. Novamente, enum classnão é um class.

Stefano Sanfilippo
fonte
88
No ## C ++ me disseram que "o c ++ visa ser o mais confuso e amigável possível" . Obviamente que é uma piada, mas você começa a idéia :)
Stefano Sanfilippo
4
A unionnão é o que John Doe consideraria uma classe também. No entanto, eles podem ter funções de membro. E as aulas realmente não são obrigatórias para funções-membro. Usando um designador como valueou this, algo como enum Size { Huge, Mega, Apocalypse; bool operator<(X rhs) const { return *this < rhs; }(aqui também permite ;), ele pode fazer tanto sentido quanto outras formas de funções.
Sebastian Mach
85

Embora a resposta que "você não pode" seja tecnicamente correta, acredito que você consiga atingir o comportamento que procura usando a seguinte idéia:

Eu imagino que você queira escrever algo como:

Fruit f = Fruit::Strawberry;
f.IsYellow();

E você esperava que o código fosse algo assim:

enum class Fruit : uint8_t
{
  Apple, 
  Pear,
  Banana,
  Strawberry,

  bool IsYellow() { return this == Banana; }
};

...

Mas é claro que não funciona, porque as enumerações não podem ter métodos (e 'isso' não significa nada no contexto acima)

No entanto, se você usar a ideia de uma classe normal contendo uma enumeração que não seja de classe e uma variável de membro único que contenha um valor desse tipo, poderá ficar extremamente próximo da segurança de sintaxe / comportamento / tipo que deseja. ou seja:

class Fruit
{
public:
  enum Value : uint8_t
  {
    Apple,
    Pear,
    Banana,
    Strawberry
  };

  Fruit() = default;
  constexpr Fruit(Value aFruit) : value(aFruit) { }

#if Enable switch(fruit) use case:
  operator Value() const { return value; }  // Allow switch and comparisons.
                                            // note: Putting constexpr here causes
                                            // clang to stop warning on incomplete
                                            // case handling.
  explicit operator bool() = delete;        // Prevent usage: if(fruit)
#else
  constexpr bool operator==(Fruit a) const { return value == a.value; }
  constexpr bool operator!=(Fruit a) const { return value != a.value; }
#endif

  constexpr bool IsYellow() const { return value == Banana; }

private:
  Value value;
};

Agora você pode escrever:

Fruit f = Fruit::Strawberry;
f.IsYellow();

E o compilador impedirá coisas como:

Fruit f = 1;  // Compile time error.

Você pode facilmente adicionar métodos como:

Fruit f("Apple");

e

f.ToString();

pode ser suportado.

jtlim
fonte
1
Também não deve ser IsYellow (), operator ==,! = Marcado como constexpr?
Jarek C
Estou recebendo "erro: falta de operador binário antes do token" switch ""
Pedro77 27/02
18

Concentrando-se na descrição da pergunta em vez do título, uma resposta possível é

struct LowLevelMouseEvent {
    enum Enum {
        mouse_event_uninitialized = -2000000000, // generate crash if try to use it uninitialized.
        mouse_event_unknown = 0,
        mouse_event_unimplemented,
        mouse_event_unnecessary,
        mouse_event_move,
        mouse_event_left_down,
        mouse_event_left_up,
        mouse_event_right_down,
        mouse_event_right_up,
        mouse_event_middle_down,
        mouse_event_middle_up,
        mouse_event_wheel
    };
    static const char* ToStr (const type::LowLevelMouseEvent::Enum& event)
    {
        switch (event) {
            case mouse_event_unknown:         return "unknown";
            case mouse_event_unimplemented:   return "unimplemented";
            case mouse_event_unnecessary:     return "unnecessary";
            case mouse_event_move:            return "move";
            case mouse_event_left_down:       return "left down";
            case mouse_event_left_up:         return "left up";
            case mouse_event_right_down:      return "right down";
            case mouse_event_right_up:        return "right up";
            case mouse_event_middle_down:     return "middle down";
            case mouse_event_middle_up:       return "middle up";
            case mouse_event_wheel:           return "wheel";
            default:
                Assert (false);
                break;
        }
        return "";
    }
};
Márkus Attila
fonte
4

Como mencionado na outra resposta , não. Mesmo enum classnão é uma aula.


Geralmente, é necessário ter métodos para obter enumresultados, porque não é um enum regular (apenas incrementado), mas um tipo de definição bit a bit de valores a serem mascarados ou precisa de outras operações aritméticas de bits:

enum class Flags : unsigned char {
    Flag1 = 0x01 , // Bit #0
    Flag2 = 0x02 , // Bit #1
    Flag3 = 0x04 , // Bit #3
    // aso ...
}

// Sets both lower bits
unsigned char flags = (unsigned char)(Flags::Flag1 | Flags::Flag2);

// Set Flag3
flags |= Flags::Flag3;

// Reset Flag2
flags &= ~Flags::Flag2;

Obviamente, pensa-se em encapsular as operações necessárias para reconfigurar um único / grupo de bits, por exemplo, valor da máscara de bits ou mesmo operações orientadas por índices de bits, seriam úteis para a manipulação de um conjunto de 'sinalizadores'.

o struct/ class especificação apenas suporta melhor escopo dos valores de enumeração para acesso. Nem mais nem menos!

As formas de sair da restrição que você não pode declarar métodos para enum (classes) são: usar uma std::bitset(classe wrapper) ou um campo de bitsunion .

unions, e essas uniões de campo de bits podem ter métodos (veja aqui as restrições!).

Eu tenho uma amostra, como converter valores de máscara de bits (como mostrado acima) em seus índices de bits correspondentes, que podem ser usados std::bitsetaqui: BitIndexConverter.hpp
Eu achei isso muito útil para melhorar a legibilidade de algumas decisões 'flag' baseadas em algoritmos.

πάντα ῥεῖ
fonte
36
Existem mais casos de uso que justificam métodos na classe enum, por exemplo, toString () e fromString (). Toda linguagem principal moderna (ainda que não tão) possui isso (por exemplo, C #, Java, Swift), mas não C ++.
21815 Mike Lischke
1
Vamos esperar uma sintaxe de chamada unificada da próxima vez ... open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf
sdgfsdh
4

Existe uma capacidade bastante compatível (§) de refatorar uma enumeração em uma classe sem precisar reescrever seu código, o que significa que efetivamente você pode fazer o que estava pedindo sem muita edição.

(§) como o ElementW aponta em um comentário, o código dependente de type_traits não funcionará, portanto, por exemplo, não se pode usar auto, etc. Pode haver alguma maneira de lidar com essas coisas, mas no final a pessoa está convertendo uma enumeração em uma classe, e é sempre um erro subverter C ++

as especificações enum structe enum classsão sobre o escopo, portanto não fazem parte disso.

Seu enum original é, por exemplo, 'animal de estimação' (este é apenas um exemplo!).

enum pet { 
    fish, cat, dog, bird, rabbit, other 
};

(1) Você modifica isso para, por exemplo, petEnum (para ocultá-lo do seu código existente).

enum petEnum { 
    fish, cat, dog, bird, rabbit, other 
};

(2) Você adiciona uma nova declaração de classe abaixo (nomeada com a enumeração original)

class pet {
    private:
        petEnum value;
        pet() {}

    public:
        pet(const petEnum& v) : value{v} {} //not explicit here.
        operator petEnum() const { return value; }
        pet& operator=(petEnum v) { value = v; return *this;}
        bool operator==(const petEnum v) const { return value == v; }
        bool operator!=(const petEnum v) const { return value != v; }
 //     operator std::string() const;

};

(3) Agora você pode adicionar quaisquer métodos de classe que desejar à sua classe de animais de estimação. por exemplo. um operador de cadeia

    pet::operator std::string() const {
        switch (value) {
            case fish: return "fish";
            case cat:  return "cat";
            case dog:  return "dog";
            case bird: return "bird";
            case rabbit: return "rabbit";
            case other: return "Wow. How exotic of you!";
        }
    }

Agora você pode usar, por exemplo, std :: cout ...

int main() {
    pet myPet = rabbit;
    if(myPet != fish) {
        cout << "No splashing! ";
    }
    std::cout << "I have a " << std::string(myPet) << std::endl;
    return 0;
}
Konchog
fonte
1
É não compatível: se você usar os valores enum com qualquer tipo de tipo de dedução onde se espera para obter uma pettypename / instance, seja modelos, autoou decltype, isso quebra, como você começa uma petEnumvez.
ElementW 5/06/19
0

Pode não atender a todas as suas necessidades, mas com operadores não membros você ainda pode se divertir muito. Por exemplo:

#include <iostream>

enum class security_level
{
    none, low, medium, high
};

static bool operator!(security_level s) { return s == security_level::none; }

static security_level& operator++(security_level& s)
{
    switch(s)
    {
        case security_level::none: s = security_level::low; break;
        case security_level::low: s = security_level::medium; break;
        case security_level::medium: s = security_level::high; break;
        case security_level::high: break;
    }
    return s;
}

static std::ostream & operator<<(std::ostream &o, security_level s)
{
    switch(s)
    {
        case security_level::none: return o << "none";
        case security_level::low: return o << "low";
        case security_level::medium: return o << "medium";
        case security_level::high: return o << "high";
    }
}

Isso permite que códigos como

security_level l = security_level::none;   
if(!!l) { std::cout << "has a security level: " << l << std::endl; } // not reached
++++l;
if(!!l) { std::cout << "has a security level: " << l << std::endl; } // reached: "medium"
Johannes
fonte