enum-int casting: оператор или функция

Во внешнем коде, который я использую, есть перечисление:

enum En {VALUE_A, VALUE_B, VALUE_C};

В другом внешнем коде, который я использую, есть 3 директивы #define:

#define ValA 5
#define ValB 6
#define ValC 7

Много раз у меня есть int X, который равен ValA, ValB или ValC, и я должен привести его к соответствующему значению En (ValA в VALUE_A, ValB в VALUEB и т. д.), потому что некоторая сигнатура функции имеет enum En. И много раз мне приходилось делать обратную операцию, переводить enum En в ValA, ValB или ValC. Я не могу менять сигнатуры этих функций, а таких функций много.

Вопрос: Как сделать перевод? Должен ли я создать 2 оператора приведения, которые будут использоваться неявно? Или мне просто нужно иметь 2 функции перевода, которые будут использоваться явно:

En ToEn(int)
int FromEn(En)

Или любое другое решение?


person Igor Oks    schedule 15.12.2008    source источник


Ответы (5)


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

template<typename T>
T my_enum_convert(int);

template<>
En my_enum_convert<En>(int in) {
    switch(in) {
        case ValA: return VALUE_A;
        case ValB: return VALUE_B;
        case ValC: return VALUE_C;
        default: throw std::logic_error(__FILE__ ": enum En out of range");
    }
}

int my_enum_convert(En in) {
    switch(in) {
        case VALUE_A: return ValA;
        case VALUE_B: return ValB;
        case VALUE_C: return ValC;
        // no default, so that GCC will warn us if we've forgotten a case
    }
}

En enumValue = my_enum_convert<En>(ValA);
int hashDefineValue = my_enum_convert(VALUE_A);
enumValue = my_enum_convert<En>(0); // throws exception

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

Причина, по которой я бы не стал использовать неявное преобразование, заключается в том, что существует неявное преобразование из En в int, которое дает неправильный ответ. Даже если вы можете надежно заменить это чем-то, что дает правильный ответ, полученный код не будет выглядеть так, как будто он выполняет какое-либо преобразование. IMO, это будет мешать любому, кто позже просматривает код, больше, чем вам будет мешать ввод вызова процедуры преобразования.

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

В качестве альтернативы, если вы хотите, чтобы они выглядели одинаково (и больше походили на static_cast), вы можете сделать:

template<typename T>
T my_enum_convert(En in) {
    switch(in) {
        case VALUE_A: return ValA;
        case VALUE_B: return ValB;
        case VALUE_C: return ValC;
    }
}

int hashDefineValue = my_enum_convert<int>(VALUE_A);

Как написано, T должен иметь неявное преобразование из int. Если вы хотите поддерживать T, который имеет только явное преобразование, используйте «return T(ValA);» вместо этого (или «вернуть static_cast‹T›(ValA);», если вы считаете, что конструкторы с одним аргументом являются приведениями в стиле C и, следовательно, недопустимы).

person Steve Jessop    schedule 15.12.2008
comment
Мне нравится это решение, потому что у меня могут быть и другие перечисления, которые нужно преобразовать. Почему вы выбрали одно и то же имя для двух функций? Может быть, наличие to_enum_convert и from_enum_convert будет выглядеть яснее? - person Igor Oks; 15.12.2008
comment
Что бы вы ни предпочли - я сделал это так, потому что я намереваюсь, чтобы my_enum_convert заменял static_cast, но с настроенным сопоставлением между значениями и всегда преобразовывал в int при задании параметра перечисления. - person Steve Jessop; 15.12.2008
comment
Я отредактировал, пытаясь обсудить варианты того, должны ли два направления выглядеть правильно по-разному или должны быть одинаковыми. - person Steve Jessop; 15.12.2008
comment
Это круто. Я не понял отношения к static_cast, пока вы не упомянули об этом. Может быть, enum_cast или my_enum_cast было бы лучшим именем? - person foraidt; 15.12.2008

Хотя неявное приведение более удобно, чем функции перевода, также менее очевидно, что происходит. Подход, удобный и очевидный, может заключаться в использовании собственного класса с перегруженными операторами приведения типов. При преобразовании пользовательского типа в enum или int будет нелегко упустить из виду какое-либо пользовательское преобразование.

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

person foraidt    schedule 15.12.2008

Вы не можете перегружать операторы для перечислений. Или я что-то упускаю? Ну, вы могли бы создать своего рода фиктивные классы, у которых был бы неявный конструктор, принимающий целое число, а затем оператор приведения к перечислению (и наоборот).

Таким образом, единственное решение состоит в том, чтобы иметь функции. Кроме того, я бы сделал перегрузки, как предлагает Патрик.

person Paulius    schedule 15.12.2008

Иметь функции, а затем перегружать библиотечные функции?

//libFunc( enum a );

libFuncOverload( define a ) {
    libFunc( toEn( a ) );
}
person Patrick    schedule 15.12.2008
comment
Это может превратиться в много кода. Кроме того, как вы справляетесь с методами перегрузки, если вы не можете изменить заголовки? (Что часто случается с библиотеками.) - person Mr.Ree; 15.12.2008
comment
Я могу перегружать методы в другом заголовке. Но я не буду этого делать, потому что кода действительно много. - person Igor Oks; 15.12.2008

Преобразование enum в int, например. int(VALUE_A), происходит автоматически/прозрачно.

Преобразование int-to-enum, например. En(ValA) может выиграть от проверки работоспособности, чтобы убедиться, что значение int является допустимым элементом enum. ( Хотя, надеюсь, код библиотеки не предполагает, что значения enum допустимы.)

Хотя это не поможет в случаях "int x", вы можете немного помочь, изменив:

#define ValA 5

To:

#define ValA VALUE_A

При условии, что enum En() включен/определен везде, оба ValA и VALUE_A будут работать для обоих foo(int) и bar(En) везде автоматически/прозрачно.

Вы можете использовать:

#ifdef ValA
STATIC_ASSERT( ValA == VALUE_A, ValA_equal_VALUE_A );
#undef ValA
#else
#warning "ValA undefined.  Defining as VALUE_A"
#endif
#define ValA VALUE_A

Где STATIC_ASSERT выглядит примерно так:

    /* Use CONCATENATE_4_AGAIN to expand the arguments to CONCATENATE_4 */
#define CONCATENATE_4(      a,b,c,d)  CONCATENATE_4_AGAIN(a,b,c,d)
#define CONCATENATE_4_AGAIN(a,b,c,d)  a ## b ## c ## d

    /* Creates a typedef that's legal/illegal depending on EXPRESSION.       *
     * Note that IDENTIFIER_TEXT is limited to "[a-zA-Z0-9_]*".              *
     * (This may be replaced by static_assert() in future revisions of C++.) */
#define STATIC_ASSERT( EXPRESSION, IDENTIFIER_TEXT)                     \
  typedef char CONCATENATE_4( static_assert____,      IDENTIFIER_TEXT,  \
                              ____failed_at_line____, __LINE__ )        \
            [ (EXPRESSION) ? 1 : -1 ]
person Mr.Ree    schedule 15.12.2008