Maneira correta de usar enums com pass-by-value e retornar em funções?

Eu entendo que o título da pergunta é muito vago, daí o texto do corpo 🙂

Eu tenho vários enum s que eu uso para identificar tipos de arquivos e outras coisas que precisam de diferenciação fácil. Minha antiga abordagem foi esta:

 namespace my_namespace { namespace fileType { enum fileType { SOURCE, HEADER, RESOURCE }; } } using namespace my_namespace::fileType; 

O que me permitiu definir uma function:

 fileType someFunction( const std::string &someFile, const fileType someType ) { //... return fileType::HEADER; } 

E eu poderia definir e comparar variables ​​assim:

 fileType type = fileType::SOURCE; 

O que é incrível. Embora houvesse algumas ressalvas. Cabeçalhos (sem using diretivas) exigiam dobrar o nome do enum pretendido para que o compilador soubesse que você está usando o tipo, não o namespace:

 my_namespace::fileType::fileType soeFunction( const std::string &someFile, const my_namespace::fileType::fileType someType ); 

O que parece bobo, é difícil de ler e doloroso de entender. Além disso, o MSVC reclama no nível de aviso 1 sobre uma extensão não padrão usada (devido ao doule fileType no exemplo). Estranho que o GCC não reclame nos ambientes mais rigorosos, mas, ei, essa é uma história diferente.

Agora quero rewrite meus enum s de uma maneira que eles sejam (anonimamente) colocados em uma struct em vez de um namespace, permitindo a qualificação única ao declarar funções, encerrando assim o aviso do MSVC. Mas como eu escrevo a declaração de return neste caso. É absolutamente necessário fornecer um construtor / operador de conversão ou há uma maneira de contornar isso que eu não vi?

Exemplo:

 // enum definition namespace my_namespace { struct fileType { enum { SOURCE, HEADER, RESOURCE }; } } using my_namespace::fileType; // function declaration in header my_namespace::fileType someFunction( const std::string &s, const my_namespace::fileType type ); // function implementation in .cpp file using my_namespace::fileType; fileType someFunction( const string &s, const fileType type ) { //...(problem is situated below) return fileType::SOURCE; } 

Isso ilustrou o que eu gostaria de fazer. Eu gostaria de evitar chamar explicitamente o construtor da enum struct: fileType(fileType::SOURCE) que me deixaria com um uso duplo de fileType .

Obrigado pela ajuda!

PS: se essa pergunta já foi respondida antes, peço desculpas, mas não encontrei uma boa alternativa com o google ou sobre as perguntas anteriores do SO sobre esse assunto.

Pessoalmente eu uso um truque muito simples:

 struct EnumName { enum type { MemberOne, MemberTwo, ... }; }; typedef EnumName::type EnumName_t; // Usage EnumName_t foo = EnumName::MemberOne; 

Em C ++ 0x, você pode ter enumeradores de escopo diretamente:

 enum struct EnunName // note: struct and class equivalent here { MemberOne, MemberTwo, ... }; // Usage EnumName foo = EnumName::MemberOne; 

Que vai ser muito bom 🙂

Nota: enum escopo também não estão sujeitos a promoção integral, o que é realmente ótimo

Do meu ponto de vista ter um namespace ou uma estrutura que não contém nada, mas um único enum já provou que namespace / struct para ser inútil. Assim você deve simplesmente soltá-lo.

Você deveria tentar se

 namespace my_namespace { enum fileType { SOURCE, HEADER, RESOURCE }; } 

combina melhor com você. Muitas vezes, porém, nesse caso, os programadores preferem prefixar os valores reais de enum com o nome do enum, resultando assim em

 namespace my_namespace { enum fileType { FILE_TYPE_SOURCE, FILE_TYPE_HEADER, FILE_TYPE_RESOURCE }; } 

aqui você pode até optar por renomear o próprio enum para

  enum FILE_TYPE { ... 

Também enums são melhor usados ​​para grupos de valores que são em si incompletos e nunca serão estendidos no futuro, por exemplo, um enum para dias da semana.
Se você considerar que um enum fileType provavelmente estará eternamente incompleto (já que existem centenas de tipos de arquivos por aí), talvez você queira adotar outra abordagem – use grupos constantes. Usar grupos constantes tem a vantagem sobre enum que, se você estender esse grupo constante, precisará apenas recompilar módulos / arquivos que utilizem os novos valores adicionados, ao passo que se você adicionar um valor a um enum, poderá ser necessário recompilar todas as fonts que usam esse enum.

Um grupo constante pode ser assim:

 namespace nms { typedef const unsigned short fileType_t; namespace fileType { fileType_t SOURCE = 1; fileType_t HEADRE = 2; fileType_t RESOURCE = 3; } } 

Claro, se você preferir, pode mover a declaração typedef para o namespace fileType ou se livrar dela completamente e usar ‘const unsigned short’ diretamente em qualquer lugar.

Como não tenho compilador em casa, o código acima pode não ser compilado a partir do zero. Mas estou confiante de que você terá a ideia.

Com essa abordagem, seu código-fonte deve ser assim:

 using nms; nms::fileType_t someFunction( const string &s, const nms::fileType_t type ) { return nms::fileType::SOURCE; } 

Verdade, se você quiser enums diferentes no mesmo escopo para ter um ‘último’ membro, isso não pode ser feito. Nesse caso, você precisaria prefixar esse valor ‘last’ com o nome do enum, por exemplo (semelhante ao segundo exemplo de enum.

Quanto a ter uma ‘última’ input para verificação de argumentos, eu posso ter perdido o seu ponto. Por exemplo, se você passar o enum como argumento para uma function, por exemplo

 void f( enum fileType type ) 

Ele não fornece mais ‘segurança de tipo’ do que o fileType sem um elemento ‘last’.

A única razão pela qual eu posso ver para um último elemento (e, em seguida, para um primeiro elemento também!) É se você deseja iterar sobre os elementos dos elementos enum em um loop. Por exemplo:

 enum E { first, e1 = first, e2, e3, last } for (int i = first; i < last; ++i) ... 

Se esse for o caso, não é um pouco artificial querer que duas enums diferentes tenham o mesmo elemento 'last' listado? Onde está a vantagem de ter um 'último' em dois enums diferentes?
Melhora a legibilidade em comparação a, por exemplo, ter E_last e F_last?
Ou ajuda salvar a escrita de uma única linha de código?
Também o que está usando 'last' em enums com escopo definido? Enums com escopo não são convertidos para int por padrão. (Mas talvez eu tenha entendido errado o que você queria fazer ...)

Sobre "você não pode manter os valores de enum curto ..." Eu tenho medo que eu não consegui o que você queria dizer aqui. Foi sobre mim usar 'const unsigned short' no exemplo do grupo constante?

Também não me entenda mal, no seu caso (querendo lidar com tipos de arquivos) defendi usar grupos constantes. E com eles é fácil e natural usar o mesmo membro 'primeiro' e 'último' (mas com valores diferentes) em grupos diferentes. Vejo

 namespace nms { typedef const unsigned short G1_t namespace G1 { G1_t first = 1; G1_t type1 = 1; G1_t type2 = 2; G1_t type3 = 3; G1_t last = 3; } typedef const long G2_t namespace G2 { G1_t first = -1; G1_t obj1 = -1; G1_t obj2 = 0; G1_t obj3 = 1; G1_t last = 1; } } 

Quanto ao 'namespace - namespace - enum' eu não gosto da indirecção adicional sem ter benefícios substanciais. Quanto ao 'namespace - struct / class - enum', isso é mais filosófico:

C é, por assim dizer, uma moto lisa e rápida que tornava a escrita de código assembler virtualmente desnecessária. Isso também é muito comum para o operador C cast, que permite descartar completamente a segurança do tipo e fazer o que você deseja sem ter o compilador no seu caminho.

O C ++ ainda gosta de ser um super conjunto de C. Ele adicionou classs e programação orientada a objects, mas ainda queria manter a motocicleta. O que foi bom. Agora, adicionando mais enums com o escopo definido como 'type safe' e ainda mantendo os operadores do casting, parece-me querer adicionar airbags e cintos de segurança à motocicleta.

Eu não sou contra a revisão de enums para obter melhores enums (se realmente for necessário), mas se alguém fizer isso deve também ocasionalmente (por exemplo, a cada 5 anos) deixar as coisas agora reconhecidas como antigas, desatualizadas e malignas. Caso contrário, a linguagem fica cada vez mais confusa e cada vez mais difícil de aprender pelos iniciantes. E como todas as linguagens de programação de ferramentas precisam ser fáceis de usar e isso inclui não ter uma dúzia de maneiras de fazer a mesma coisa.