Enum в С++, как Enum в Ada?
В какой-то момент я рассмотрел реализацию класса/шаблона на С++, который поддерживал бы Enum, который будет вести себя так же, как в Ada. Прошло некоторое время с тех пор, как я подумал об этой проблеме, и мне было интересно, разрешил ли кто-нибудь эту проблему?
EDIT:
Мои извинения, я должен уточнить, какую функциональность я считал полезной в реализации Ada Enum. Учитывая перечисление
type fruit is (apple, banana, cherry, peach, grape);
Мы знаем, что фрукты являются одним из перечисленных фруктов: яблоко, банан, вишня, персик, виноград. Ничего действительно отличного от С++.
Что очень полезно, так это следующие функциональные возможности, которые вы получаете с каждым перечислением в Ada без дополнительной работы:
- Распечатка перечислимого значения генерирует версию строки
- вы можете увеличивать перечислимую переменную
- вы можете уменьшать перечислимую переменную
Надеюсь, что эта проблема еще немного изменится.
Заметки добавлены из комментариев:
Полезные функции перечислений Ada
- Первое значение в перечислении -
fruit'first
, которое дает apple
.
- Последнее значение в перечислении равно
fruit'last
, которое дает grape
.
- Операция приращения
fruit'succ(apple)
, которая дает banana
.
- Операция декремента
fruit'pred(cherry)
, которая также дает banana
.
- Преобразование из перечисления в целое число равно
fruit'pos(cherry)
, которое возвращает 2
, потому что Ada использует нулевые подсети.
- Преобразование из целых чисел в перечисление равно
fruit'val(2)
, который возвращает cherry
.
- Преобразование из перечисления в строку равно
fruit'Image(apple)
, которое возвращает строку (верхний регистр) "APPLE"
.
- Преобразование из строки в перечисление -
fruit'Value("apple")
, которая возвращает значение apple
.
См. также связанные вопросы SO:
Ответы
Ответ 1
Хорошо, оставлю С++ в стороне на мгновение. С++ - это просто супермножество C (что означает, что все, что можно сделать в C, можно сделать и на С++). Поэтому давайте сосредоточимся на простой-C (потому что это язык, который я хорошо знаю). C имеет перечисления:
enum fruit { apple, banana, cherry, peach, grape };
Это совершенно легальный C, и значения смежны, а у яблока есть значение ноль, а банан имеет значение apple + 1. Вы можете создавать перечисления с отверстиями, но только если вы явно делаете отверстия, подобные этому
enum fruit { apple = 0, banana, cherry = 20, peach, grape };
Пока яблоко равно 0, а банану 1, вишня - 20, персик - 21, а виноград - 22, а все между 1 и 20 - undefined. Обычно вам не нужны отверстия. Вы можете сделать следующее:
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");
Откроется YES. Вы также можете сделать следующее:
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");
Это будет печатать NO, а значение myFruit не совпадает с любой константой перечисления.
Кстати, чтобы избежать этого, вы должны сказать "enum fruit myFruit", вы можете избежать перечисления с помощью typedef. Просто используйте "typedef enum fruit fruit"; на собственной линии. Теперь вы можете сказать "фрукты myFruit" без перечисления спереди. Это часто делается непосредственно при определении перечисления:
typedef enum fruit { apple = 0, banana, cherry, peach, grape } fruit;
fruit myFruit;
Недостаток заключается в том, что вы больше не знаете, что фрукты - это перечисление, это может быть объект, структура или что-то еще. Обычно я избегаю этих типов typedef, я скорее пишу enum впереди, если перечисление и структура впереди, если структура (я просто буду использовать их здесь, потому что она выглядит лучше).
Получение строкового значения невозможно. Во время выполнения перечисление - это всего лишь число. Это означает, что невозможно, если вы не знаете, что такое перечисление (как 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
Это именно то, что вы хотели, или я полностью ошибаюсь здесь?
Ответ 2
Один из моих коллег реализовал инструмент для создания классов, которые делают большинство (если не все) того, что вы хотите:
http://code.google.com/p/enumgen/
Текущая реализация находится в Lisp, но не держите это против него: -)
Ответ 3
В С++ нет простого способа сделать это, не в последнюю очередь потому, что константы перечисления не обязательно должны быть уникальными или непрерывными. Преобразование из значения в строку также нетривиально; решения, о которых я знаю, связаны с хакерством препроцессора C/С++ - и это уничижительное использование термина hackery.
Я испытываю соблазн сказать "нет"; Я не уверен, что это правильно, но это, безусловно, нетривиально.
Ответ 4
вы можете взглянуть на java enum (http://madbean.com/2004/mb2004-3/) и эту идею: http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern
Ответ 5
Если вас интересует 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/#svn/trunk/demo
В то время как это довольно многофункциональное, как есть, есть много вещей
которые также могут быть изменены, путем установки переменных во входном файле
или указав атрибуты счетчиков.
Например, по умолчанию он представляет имена строк, используя
std::string, но он может использовать char const * или любую пользовательскую строку
класс дал небольшое усилие.
У вас может быть несколько карт имен с тем же значением перечисления, но
выберите один из них как "первичный", так что сопоставление значения с строкой
приведет к этому имени (в отличие от других).
Вы можете явно указывать значения для перечислений, и они не
должны быть уникальными. (Дубликаты - это неявные псевдонимы для предыдущего
перечисление с тем же значением.)
Кроме того, вы можете перебирать все уникальные значения, и для каждого
значение по всем его псевдонимам, что полезно, если вы хотите
сгенерируйте script -language "wrappers" для них, как мы используем ruby.
Если вы заинтересованы в использовании этого и имеете вопросы, не стесняйтесь
свяжитесь со мной по электронной почте. (cuzdav в gmail).
Надеюсь, это поможет. (Существует не так много документации, кроме
набор тестов и демонстрационный код, а также источник, если вам это нравится.)
Крис
Ответ 6
Я написал 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;
}
}
Он объявляет перечисление как обычное старое перечисление, поэтому вы все равно можете использовать его для "нормальных" целей. Итератор также может использоваться для других нормальных перечислений, имеющих последовательные значения, поэтому у него есть второй параметр шаблона, который по умолчанию равен litb::ConsequtiveRange<>
. Он соответствует требованиям двунаправленного итератора.
Глупый код может быть скачан здесь
Ответ 7
В этой статье показано, как сгенерировать строковую версию перечисляемого значения, хотя для этого требуется написать код, чтобы сделать это самостоятельно, Он также предоставляет макрос препроцессора, чтобы очень легко разрешить инкремент и декрементирование перечислимых переменных, если ваше перечисление является непрерывным.
Эта библиотека обеспечивает более гибкое приращение и уменьшение.
Библиотека enum_rev4.6.zip в Boost Vault обеспечивает легкие преобразования строк. Похоже, что он поддерживает увеличение и уменьшение с помощью итераторов (что, вероятно, менее удобно, но работает). Это в основном недокументировано, хотя каталог libs/test содержит хороший код примера.
Ответ 8
Это невыпущенное программное обеспечение, но, похоже, BOOST_ENUM от Frank Laub может соответствовать счету. Часть, которая мне нравится в этом, заключается в том, что вы можете определить перечисление в пределах класса, который большинство из перечислений на основе макросов обычно не позволяет вам делать. Он расположен в 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/