Использование элементов константного массива в качестве случаев в операторе switch

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

// input.h
enum LOGICAL_KEYS {
    DO_SOMETHING_KEY,
    DO_SOMETHING_ELSE_KEY,
    ...
    countof_LOGICAL_KEYS
};

static const SDLKey LogicalMappings[countof_LOGICAL_KEYS] = {
    SDLK_RETURN,    // Do Something
    SDLK_ESCAPE,    // Do Something Else
    ...
};

// some_other_file.cpp
...
switch( event.key.keysym.key ) {
    case LogicalMappings[ DO_SOMETHING_KEY ]:
        doSomething();
        break;
    case LogicalMappings[ DO_SOMETHING_ELSE_KEY ]:
        doSomethingElse();
        break;
    ...
}

Когда я пытаюсь скомпилировать это (gcc 4.3.2), я получаю сообщение об ошибке:

ошибка: «LogicalMappings» не может отображаться в постоянном выражении

Я не понимаю, почему у компилятора есть проблема с этим. Я понимаю, почему вам не разрешено иметь переменные в операторе case, но у меня сложилось впечатление, что вы можете использовать константы, поскольку они могут быть оценены во время компиляции. Константные массивы не работают с операторами switch? Если это так, я полагаю, я мог бы просто заменить массив чем-то вроде:

static const SDLKey LOGICAL_MAPPING_DO_SOMETHING      = SDLK_RETURN;
static const SDLKey LOGICAL_MAPPING_DO_SOMETHING_ELSE = SDLK_ESCAPE;
...

Но это выглядит гораздо менее элегантно. Кто-нибудь знает, почему здесь нельзя использовать константный массив?

РЕДАКТИРОВАТЬ: я видел часть стандарта С++, в котором утверждается, что «интегральное константное выражение может включать только литералы (2.13), перечислители, константные переменные или статические члены данных интегральных или перечисляемых типов, инициализированных константными выражениями (8.5) ...". Я до сих пор не понимаю, почему константный массив не считается «типом перечисления, инициализированным константным выражением». Просто может быть, что ответ на мой вопрос «потому что так оно и есть», и мне придется обойти это. Но если это так, то это немного разочаровывает, потому что компилятор действительно мог определить эти значения во время компиляции.


person awall    schedule 15.12.2008    source источник
comment
Тип перечисления, инициализированный константным выражением, похож на MyEnum a = 12, где MyEnum — тип перечисления (т. е. объявленный/определенный с помощью ключевого слова enum). Тип массива-перечисления отличается от типа перечисления, массивом которого он является.   -  person Steve Jessop    schedule 15.12.2008


Ответы (7)


Ссылаясь на разделы стандарта C++: 6.4.2 требует, чтобы выражения case оценивались как целое число или константа перечисления. 5.19 определяет, что это такое:

Интегральное выражение-константа может включать только литералы (2.13), перечислители, константные переменные или статические элементы данных целочисленного или перечисляемого типов, инициализированные константными выражениями (8.5), нетиповые параметры шаблона целочисленного или перечисляемого типов и выражения sizeof. Плавающие литералы (2.13.3) могут появляться только в том случае, если они приведены к целочисленному типу или типу перечисления. Можно использовать только преобразования типов в целочисленные или перечисляемые типы. В частности, за исключением выражений sizeof, не должны использоваться функции, объекты класса, указатели или ссылки, а также не должны использоваться операторы присваивания, увеличения, уменьшения, вызова функции или запятой.

Итак, если ваш вопрос был «почему компилятор отклоняет это», один ответ - «потому что так сказано в стандарте».

person Martin v. Löwis    schedule 15.12.2008

Несмотря на это, ссылки на массивы не являются «достаточно постоянными».

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

person Jonathan Leffler    schedule 15.12.2008

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

person Uri    schedule 15.12.2008

Определен ли оператор сравнения для «LogicalMappings»? Если нет, то это ошибка.

person fasih.rana    schedule 15.12.2008
comment
Я не уверен, что понимаю, что вы имеете в виду... Можете ли вы уточнить? - person Jonathan Leffler; 15.12.2008
comment
Я хотел спросить, есть ли перегруженный оператор равенства для класса LogicalMappings. Видя, что блоки «swith» используют оператор равенства для поиска «case». - person fasih.rana; 15.12.2008

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

person yesraaj    schedule 15.12.2008

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

например (предупреждение, следует непроверенный код)

class Event // you probably have this defined already
{
}

class EventHandler // abstract base class
{
public:
  virtual void operator()(Event& e) = 0;
};

class EventHandler1
{
  virtual void operator()(Event& e){
    // do something here 
  }
};
class EventHandler2
{
  virtual void operator()(Event& e){
    // do something here 
  }
};

EventHandler1 ev1;
EventHandler2 ev2;
EventHandler *LogicalMappings[countof_LOGICAL_KEYS] = {
  &ev1,
  &ev2,
  // more here...

};

// time to use code:
Event event;
if (event.key.keysym.key < countof_LOGICAL_KEYS)
{
   EventHandler *p = LogicalMappings[event.key.keysym.key];
   if (p != NULL)
      (*p)(event);
}
person Jason S    schedule 15.12.2008

Гуру компилятора на работе объяснил мне это. Проблема в том, что сам массив постоянен, но индексы к нему не обязательно константы. Таким образом, выражение LogicalMappings[some_variable] не может быть вычислено во время компиляции, поэтому массив в любом случае сохраняется в памяти, а не компилируется. По-прежнему нет причин, по которым компилятор не мог бы статически оценивать ссылки на массивы с константным или литеральным индексом, поэтому то, что я хочу сделать, теоретически должно быть возможно, но это немного сложнее, чем я думал, поэтому я могу понять, почему gcc не не делай этого.

person awall    schedule 17.12.2008