Enum в C++, как Enum в Ada?
В какой-то момент я посмотрел на реализацию класса / шаблона в C++, который бы поддерживал Enum, который вел себя так же, как в Ada. Прошло некоторое время с тех пор, как я думал об этой проблеме, и мне было интересно, кто-нибудь когда-нибудь решал эту проблему?
РЕДАКТИРОВАТЬ:
Приношу свои извинения, я должен уточнить, какие функциональные возможности, по моему мнению, были полезны при реализации Enum в Ada. Учитывая перечисление
type fruit is (apple, banana, cherry, peach, grape);
Мы знаем, что фрукты являются одним из перечисленных фруктов: яблоко, банан, вишня, персик, виноград. Ничего особо не отличается от C++.
Что очень полезно, так это следующие функциональные возможности, которые вы получаете с каждым перечислением в Ada без какой-либо дополнительной работы:
- распечатка перечисляемого значения генерирует строковую версию
- Вы можете увеличивать перечисляемую переменную
- Вы можете уменьшить перечисляемую переменную
Я надеюсь, что это определит проблему немного больше.
Примечания добавлены из комментариев:
Полезные функции перечислений Ады
- Первое значение в перечислении
fruit'first
который даетapple
, - Последнее значение в перечислении
fruit'last
который даетgrape
, - Операция увеличения
fruit'succ(apple)
который даетbanana
, - Операция декремента
fruit'pred(cherry)
что также даетbanana
, - Преобразование из перечисления в целое число
fruit'pos(cherry)
который возвращается2
потому что Ада использует нумерации на основе 0. - Преобразование из целого в перечисление
fruit'val(2)
который возвращаетсяcherry
, - Преобразование из перечисления в строку
fruit'Image(apple)
который возвращает строку (в верхнем регистре)"APPLE"
, - Преобразование из строки в перечисление
fruit'Value("apple")
который возвращает значениеapple
,
Смотрите также связанные вопросы SO:
8 ответов
Хорошо, давайте на минуту оставим C++ в стороне. C++ - это просто расширенный набор C (что означает, что все, что можно сделать в C, можно сделать и в C++). Итак, давайте сосредоточимся на обычном C (потому что это язык, который я хорошо знаю). C имеет перечисления:
enum fruit { apple, banana, cherry, peach, grape };
Это вполне допустимый C, и значения являются смежными, и яблоко имеет значение ноль, а банан имеет значение яблоко + 1. Вы можете создавать перечисления с дырками, но только если вы явно делаете дырки, подобные этой
enum fruit { apple = 0, banana, cherry = 20, peach, grape };
В то время как яблоко - 0, а банан - 1, вишня - 20, таким образом, персик - 21, а виноград - 22, и все между 1 и 20 не определено. Обычно вы не хотите дыр. Вы можете сделать следующее:
enum fruit { apple = 0, banana, cherry, peach, grape };
enum fruit myFruit = banana;
myFruit++;
// myFruit is now cherry
printf("My fruit is cherry? %s\n", myFruit == cherry ? "YES" : "NO");
Это напечатает ДА. Вы также можете сделать следующее:
enum fruit { apple = 0, banana, cherry = 20, peach, grape };
enum fruit myFruit = banana;
myFruit++;
// myFruit is now cherry
printf("My fruit is cherry? %s\n", myFruit == cherry ? "YES" : "NO");
Это напечатает НЕТ, и значение myFruit не совпадает ни с одной из констант перечисления.
Кстати, чтобы избежать того, что вы должны сказать "enum fruit myFruit", вы можете избежать перечисления с помощью typedef. Просто используйте "typedef enum fruit fruit"; на собственной линии. Теперь вы можете сказать "Фрукты myFruit" без перечисления впереди. Это часто делается непосредственно при определении enum:
typedef enum fruit { apple = 0, banana, cherry, peach, grape } fruit;
fruit myFruit;
Недостаток в том, что вы больше не знаете, что фрукт - это перечисление, это может быть объект, структура или что-то еще. Я обычно избегаю такого типа typedef, я скорее пишу enum перед, если enum, и struct перед, если struct (я просто буду использовать их здесь, потому что это выглядит лучше).
Получить строковое значение невозможно. Во время выполнения перечисление - это просто число. Это означает, что это невозможно, если вы не знаете, что это за перечисление (0 может быть яблоком, но это может быть и другая вещь из другого набора перечислений). Однако, если вы знаете, что это фрукт, тогда легко написать функцию, которая сделает это за вас. Препроцессор - твой друг:-)
typedef enum fruit {
apple = 0,
banana,
cherry,
peach,
grape
} fruit;
#define STR_CASE(x) case x: return #x
const char * enum_fruit_to_string(fruit f) {
switch (f) {
STR_CASE(apple); STR_CASE(banana); STR_CASE(cherry);
STR_CASE(peach); STR_CASE(grape);
}
return NULL;
}
#undef STR_CASE
static void testCall(fruit f) {
// I have no idea what fruit will be passed to me, but I know it is
// a fruit and I want to print the name at runtime
printf("I got called with fruit %s\n", enum_fruit_to_string(f));
}
int main(int argc, char ** argv) {
printf("%s\n", enum_fruit_to_string(banana));
fruit myFruit = cherry;
myFruit++; // myFruit is now peach
printf("%s\n", enum_fruit_to_string(myFruit));
// I can also pass an enumeration to a function
testCall(grape);
return 0;
}
Выход:
banana
peach
I got called with fruit grape
Это именно то, что вы хотели, или я здесь не на том пути?
Один из моих коллег реализовал инструмент для генерации классов, которые выполняют большинство (если не все) того, что вы хотите:
http://code.google.com/p/enumgen/
Текущая реализация в Лиспе, но не против этого:-)
Я написал enum_iterator
что делает это вместе с ENUM
макрос с использованием Boost.Preprocessor:
#include <iostream>
#include "enum.hpp"
ENUM(FooEnum,
(N)
(A = 1)
(B = 2)
(C = 4)
(D = 8));
int main() {
litb::enum_iterator< FooEnum, litb::SparseRange<FooEnum> > i = N, end;
while(i != end) {
std::cout << i.to_string() << ": " << *i << std::endl;
++i;
}
}
Он объявляет enum как простой старый enum, так что вы все равно можете использовать его для "обычных" целей. Итератор может использоваться и для других обычных перечислений, которые имеют последовательные значения, поэтому у него есть второй параметр шаблона, который по умолчанию равен litb::ConsequtiveRange<>
, Это соответствует требованиям двунаправленного итератора.
Глупый код можно скачать отсюда
Если вы заинтересованы в enumgen, я сделал простую демонстрацию на вашем примере. Как уже упоминалось, я реализовал его, используя общий lisp, поэтому входной файл, который вы пишете, был lispy, но я очень старался сделать синтаксис разумным.
Вот:
$ cat Fruit.enum
(def-enum "Fruit" (("apple")
("banana")
("cherry")
("peach")
("grape")
("INVALID_")))
$ enumgen Fruit.enum
Using clisp
;; Loading file /tmp/enumgen/enumgen.lisp ...
;; Loaded file /tmp/enumgen/enumgen.lisp
loading def file:
;; Loading file /tmp/enumgen/enumgen.def ...
;; Loaded file /tmp/enumgen/enumgen.def
generating output:
Fruit.cpp
Fruit.ipp
Fruit.hpp
DONE
Чтобы просмотреть сгенерированный код, посетите этот URL: http://code.google.com/p/enumgen/source/browse/
Несмотря на то, что он довольно богатый набор функций, есть много вещей, которые можно настроить, установив переменные во входном файле или указав атрибуты перечислителей.
Например, по умолчанию он представляет имена строк с использованием std::string, но может использовать char const * или любой пользовательский класс строк, если приложить немного усилий.
У вас может быть несколько имен, сопоставляемых одному и тому же значению перечисления, но вы должны выбрать одно, чтобы оно было "основным", так что сопоставление значения со строкой приведет к этому имени (в отличие от других.)
Вы можете явно предоставить значения перечислениям, и они не должны быть уникальными. (Дубликаты являются неявными псевдонимами для предыдущего перечисления с тем же значением.)
Кроме того, вы можете перебирать все уникальные значения и для каждого значения по всем его псевдонимам, что полезно, если вы хотите сгенерировать для них "обертки" на языке сценариев, как мы это делаем с помощью ruby.
Если вы заинтересованы в использовании этого и у вас есть вопросы, не стесняйтесь связаться со мной по электронной почте. (Потому что в Gmail).
Надеюсь это поможет. (Помимо набора тестов и демонстрационного кода, а также источника, если вы заботитесь об этом, не так много документации).
Крис
В C++ нет простого способа сделать это, не в последнюю очередь потому, что константы перечисления не обязательно должны быть уникальными или смежными. Преобразование из значения в строку также нетривиально; известные мне решения включают хакерство препроцессора C/C++ - и это уничижительное использование термина хакерство.
Я испытываю желание сказать "нет"; Я не уверен, что это правильно, но это, безусловно, нетривиально.
Вы можете взглянуть на перечисление java ( http://madbean.com/2004/mb2004-3/) и эту идею: http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern
В этой статье показано, как создать строковую версию перечислимого значения, хотя для этого требуется, чтобы вы написали код, чтобы сделать это самостоятельно. Он также предоставляет макрос препроцессора, который очень легко разрешает увеличивать и уменьшать перечисляемые переменные, пока ваше перечисление непрерывно.
Эта библиотека обеспечивает более гибкое увеличение и уменьшение.
Библиотека enum_rev4.6.zip в Boost Vault обеспечивает простое преобразование строк. Похоже, что он поддерживает увеличение и уменьшение с использованием итераторов (что, вероятно, менее удобно, но работает). Это в основном недокументировано, хотя каталог libs/test содержит хороший пример кода.
Это неизданное программное обеспечение, но, похоже, BOOST_ENUM от Фрэнка Лауба вполне может подойти. Что мне нравится в этом, так это то, что вы можете определить перечисление в пределах класса, которое обычно не позволяет делать большинство перечислений на основе макросов. Он находится в Boost Vault по адресу: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=&; Он не видел каких-либо разработок с 2006 года, поэтому я не знать, насколько хорошо он компилируется с новыми выпусками Boost. Посмотрите в libs / test пример использования. Существует также Boost smart_enum (также не выпущен). Это делает итератор частью вашего вопроса, но не выводит строку. http://cryp.to/smart-enum/