Подтвердить что ты не робот

C/С++: любой способ получить отражающие перечисления?

Я встречал эту ситуацию так много раз...

 enum Fruit {
  Apple,
  Banana,
  Pear,
  Tomato
 };

Теперь у меня есть Fruit f; // banana, и я хочу перейти от f к строке "Banana"; или у меня есть string s = "Banana", и я хочу перейти к Banana // enum value or int.

До сих пор я это делал. Предполагая, что перечисление находится в Fruit.h:

// Fruit.cpp
const char *Fruits[] = {
 "Apple",
 "Banana",
 "Pear",
 "Tomato",
 NULL
};

Очевидно, что это грязное решение. Если разработчик добавляет новый фрукт в заголовок и не добавляет новую запись в Fruits [] (не может винить его, они должны быть в двух разных файлах!) Приложение идет стрелой.

Есть ли простой способ делать то, что я хочу, где все находится в одном файле? Препроцессорные хаки, чужая магия, все, что угодно.

PS: Это, вопреки отражению "для всего", было бы действительно тривиально реализовать в компиляторах. Видя, насколько распространена проблема (по крайней мере для меня), я действительно не могу поверить, что нет reflective enum Fruit.. Даже в С++ 0x.

PS2: Я использую С++, но я отметил этот вопрос как C, потому что C имеет ту же проблему. Если ваше решение включает в себя только С++, это нормально для меня.

4b9b3361

Ответ 1

Это требует определения плодов во внешнем файле. Это будет содержание fruit.cpp:

#define FRUIT(name) name
enum Fruit {
#include "fruit-defs.h"
NUM_FRUITS
};
#undef FRUIT
#define FRUIT(name) #name
const char *Fruits [] = {
#include "fruit-defs.h"
NULL
};
#undef FRUIT

И это будет fruit-defs.h:

FRUIT(Banana),
FRUIT(Apple),
FRUIT(Pear),
FRUIT(Tomato),

Он работает до тех пор, пока значения начинаются с 0 и являются последовательными...

Обновление: смешайте это решение с тем, которое было у Ричарда Пеннингтона, используя C99, если вам нужны нескончаемые значения. Т.е. что-то вроде:

// This would be in fruit-defs.h
FRUIT(Banana, 7)
...
// This one for the enum
#define FRUIT(name, number) name = number
....
// This one for the char *[]
#define FRUIT(name, number) [number] = #name

Ответ 2

Найденный мной способ c99 помогает уменьшить ошибки:

enum Fruit {
  APPLE,
  BANANA
};
const char* Fruits[] = {
 [APPLE] = "APPLE",
 [BANANA] = "BANANA"
};

Вы можете добавить перечисления, даже посередине, и не нарушать старые определения. Вы все равно можете получить NULL строки для значений, которые вы забываете, конечно.

Ответ 3

Один трюк, который я делал в прошлом, - это добавить дополнительное перечисление, а затем выполнить утверждение времени компиляции (например, Boost) чтобы убедиться, что они синхронизированы:

enum Fruit {
    APPLE,
    BANANA,

    // MUST BE LAST ENUM
    LAST_FRUIT
};

const char *FruitNames[] =
{
    "Apple",
    "Banana",
};

BOOST_STATIC_ASSERT((sizeof(FruitNames) / sizeof(*FruitNames)) == LAST_FRUIT);

Это, по крайней мере, позволит кому-то забыть добавить и к перечислению, и к массиву имен, и сообщит им, как только они попытаются скомпилировать.

Ответ 4

Что делать, если вы сделали что-то подобное?

enum Fruit {
  Apple,
  Banana,
  NumFruits
};

const char *Fruits[NumFruits] = {
 "Apple",
 "Banana",
};

Затем, если вы добавите новую запись в перечисление Fruit, ваш компилятор должен пожаловаться на то, что в инициализаторе массива недостаточно записей, поэтому вам нужно будет добавить запись в массив.

Поэтому он защищает вас от неправильного размера массива, но это не поможет вам убедиться, что строки верны.

Ответ 5

Один комментарий к решению макроса - вам не нужен отдельный файл для счетчиков. Просто используйте другой макрос:

#define FRUITs \ 
    FRUIT(Banana), \ 
    FRUIT(Apple), \ 
    FRUIT(Pear), \ 
    FRUIT(Tomato)

(я бы, скорее всего, оставил запятые и включил их в макрос FRUIT по мере необходимости.)

Ответ 6

Как показали другие люди, ответившие на этот вопрос, на самом деле нет чистого ( "D.R.Y." ) способа, используя только препроцессор C. Проблема в том, что вам нужно определить массив размера вашего перечисления, содержащий строки, соответствующие каждому значению перечисления, а препроцессор C недостаточно умен, чтобы это можно было сделать. Я создаю текстовый файл примерно так:

%status ok
%meaning
The routine completed its work successfully.
%

%status eof_reading_content
%meaning

The routine encountered the end of the input before it expected
to. 

%

Здесь помечены разделители%.

Тогда Perl script, рабочая часть которого выглядит так:

sub get_statuses
{
    my ($base_name, $prefix) = @_;
    my @statuses;
    my $status_txt_file = "$base_name.txt";
    my $status_text = file_slurp ($status_txt_file);
    while ($status_text =~ 
       m/
        \%status\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\n
        \%meaning\s*(.*?)\s*\n\%\s*\n
        /gxs) {
    my ($code, $meaning) = ($1, $2);
    $code = $prefix."_$code";
    $meaning =~ s/\s+/ /g;
    push @statuses, [$code, $meaning];
    }
    return @statuses;
}

читает этот файл и записывает заголовочный файл:

typedef enum kinopiko_status {
    kinopiko_status_ok,
    kinopiko_status_eof_reading_content,

и файл C:

/* Generated by ./kinopiko-status.pl at 2009-11-09 23:45. */
#include "kinopiko-status.h"
const char * kinopiko_status_strings[26] = {
"The routine completed its work successfully.",
"The routine encountered the end of the input before it expected to. ",

используя входной файл вверху. Он также вычисляет число 26 здесь путем подсчета входных строк. (На самом деле существует 26 статусов).

Затем построение файла строки состояния выполняется с помощью make.

Ответ 7

Можно создать для него структуру класса:

class Fruit { 
   int value; char const * name ; 
   protected:
   Fruit( int v, char const * n ) : value(v), name(n) {}
   public:
   int asInt() const { return value ; }
   char const * cstr() { return name ; } 
} ;
#define MAKE_FRUIT_ELEMENT( x, v ) class x : public Fruit { x() : Fruit( v, #x ) {} }

// Then somewhere:
MAKE_FRUIT_ELEMENT(Apple, 1);
MAKE_FRUIT_ELEMENT(Banana, 2);
MAKE_FRUIT_ELEMENT(Pear, 3);

Тогда у вас может быть функция, которая принимает Fruit, и она будет даже более безопасной.

void foo( Fruit f ) {
  std::cout << f.cstr() << std::endl;
  switch (f.asInt()) { /* do whatever * } ;
}

Размер этого размера 2x больше, чем просто перечисление. Но более чем вероятно, что это не имеет значения.

Ответ 8

Взгляните на библиотеку Metaresc https://github.com/alexanderchuranov/Metaresc

Он предоставляет интерфейс для объявления типов, который также будет генерировать метаданные для типа. На основе метаданных вы можете легко сериализовать/десериализовать объекты любой сложности. Из коробки вы можете сериализовать/десериализовать XML, JSON, XDR, Lisp -подобную нотацию, нотацию C-init.

Вот простой пример:

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>

#include "metaresc.h"

TYPEDEF_ENUM (fruit_t,
              Apple,
              Banana,
              Pear,
              Tomato,
              );

int main (int argc, char * argv[])
{
  mr_td_t * tdp = mr_get_td_by_name ("fruit_t");

  if (tdp)
    {
      int i;
      for (i = 0; i < tdp->fields_size / sizeof (tdp->fields[0]); ++i)
        printf ("[%" SCNd64 "] = %s\n", tdp->fields[i].fdp->param.enum_value, tdp->fields[i].fdp->name.str);
    }
  return (EXIT_SUCCESS);
}

Эта программа выведет

$ ./enum
[0] = Apple
[1] = Banana
[2] = Pear
[3] = Tomato

Библиотека отлично работает для последних gcc и clang.

Ответ 9

Мне не нравятся макрорешения, в общем, хотя я признаю, что их трудно избежать, чтобы избежать их.

Лично я выбрал специальный класс для переноса моих перечислений. Цель заключалась в том, чтобы предложить немного больше традиционных переходов (например, итерации).

Под обложкой я использую std::map для сопоставления перечисления с его аналогом std::string. Затем я могу использовать это как для итерации по перечислению, так и для "красивой печати" моего перечисления или инициализации его из строки, считанной в файле.

Проблема, конечно же, в определении, так как я должен сначала объявить перечисление, а затем отобразить его... но это цена, которую вы платите за их использование.

Кроме того, я тогда использую не реальное перечисление, а const_iterator, указывающее на карту (под обложками) для представления значения перечисления (с end, представляющим недопустимое значение).

Ответ 10

Существует также Better Enums, который представляет собой библиотеку (файл) только для главы, которая требует С++ 11 и лицензируется в соответствии с Лицензия на программное обеспечение BSD. Официальное описание:

Рефлексивные списки времени компиляции для C +: Better Enums - это один, легкий заголовочный файл, который позволяет компилятору генерировать отражающие типы перечислений.

Вот пример кода с официального сайта:

#include <enum.h>

BETTER_ENUM(Channel, int, Red = 1, Green, Blue)

Channel     c = Channel::_from_string("Red");
const char  *s = c._to_string();

size_t      n = Channel::_size();
for (Channel c : Channel::_values()) {
    run_some_function(c);
}

switch (c) {
    case Channel::Red:    // ...
    case Channel::Green:  // ...
    case Channel::Blue:   // ...
}

Channel     c = Channel::_from_integral(3);

constexpr Channel c =
    Channel::_from_string("Blue");

Это выглядит очень многообещающе, хотя я еще не тестировал его. Кроме того, для С++ имеется множество (пользовательских) библиотек отражения. Я надеюсь, что что-то похожее на Better Enums станет частью стандартной библиотеки шаблонов (STL) (или, по крайней мере, Boost), рано или поздно.