在 switch 语句中使用常量数组元素作为 case。

6

我正在尝试将一组按键映射到一组命令。由于我从几个地方处理命令,因此我希望在按键和命令之间建立一层抽象,以便如果我更改底层键映射,我不必更改太多的代码。我的当前尝试看起来像这样:

// 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),会出现以下错误信息:

error: 'LogicalMappings' 不能出现在常量表达式中

我不明白为什么编译器对此感到困惑。我知道在case语句中你不能使用变量,但我以为你可以使用常量,因为它们可以在编译时计算出来。是否switch语句不支持常量数组?如果是这样,我想我可以用类似以下方式替换数组:

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

但那看起来不太优雅。有人知道为什么不能在这里使用常量数组吗?
编辑:我看到了C++标准的一部分,它声称,“整数常量表达式只能涉及文字(2.13)、枚举器、整数或枚举类型的const变量或静态数据成员,这些变量或成员用常量表达式初始化(8.5)……”。我仍然不明白为什么常量数组不算作“用常量表达式初始化的枚举类型”。可能只是因为我的问题的答案是“因为它就是这样”,我必须绕过它。但如果是这样的话,有点令人失望,因为编译器确实可以在编译时确定这些值。

一个用常量表达式初始化的“枚举类型”类似于“MyEnum a = 12”,其中MyEnum是一个枚举类型(即使用枚举关键字声明/定义)。一个枚举类型的数组不同于它所属的枚举类型。 - Steve Jessop
7个回答

3

参考C++标准的章节:6.4.2要求case表达式必须评估为整数或枚举常量。5.19定义了什么是这样的表达式:

整数常量表达式只能涉及文字(2.13),枚举器,使用常量表达式初始化的整数或枚举类型的const变量或静态数据成员(8.5),整数或枚举类型的非类型模板参数和大小表达式。浮点文字(2.13.3)只能出现在它们被转换为整数或枚举类型的情况下。只能使用到整数或枚举类型的类型转换。特别地,在sizeof表达式中以外,不得使用函数、类对象、指针或引用,并且不得使用赋值、增量、减量、函数调用或逗号运算符。

因此,如果您的问题是“为什么编译器会拒绝这个”,一个答案是“因为标准规定如此”。


2

数组引用不够“常量”。

你只需要稍微改变映射方式。在switch语句的case子句中使用逻辑键代码,以便在按下逻辑键时执行相同的操作。然后将实际键代码映射到逻辑代码,可能是在switch本身中,也可能是事先完成。你仍然可以使用LogicalMappings数组或类似的结构。并且,为了全球化的方便,你甚至可以使映射数组非常量,以便不同的人可以根据自己的需求重新映射键。


1
我会冒险回答这个问题,因为没有其他人回复,而且我最近主要在做Java,而不是C++。据我所记,即使可以在编译时确定查找的结果,数组查找也不被认为是一个常量整数。这甚至可能是语法上的问题。

0

“LogicalMappings” 是否定义了比较运算符?如果没有,那么这就是错误的原因。


我不确定我理解你的意思...你能详细说明一下吗? - Jonathan Leffler
我想问的是,类LogicalMappings是否重载了等号运算符。因为'switch'块使用等号运算符来查找'case'。 - fasih.rana

0

在boost库中有一个叫做signal的库,它可以帮助你创建事件映射抽象。如果你有时间,这应该是更好的方法。


0
一位编译器专家向我解释了这个问题。问题在于数组本身是常量,但对它的索引不一定是常量。因此,表达式LogicalMappings[some_variable]无法在编译时计算,所以数组最终仍然存储在内存中而没有被编译掉。虽然编译器仍然可以静态地评估带有const或字面索引的数组引用,因此理论上我想做的应该是可能的,但比我想象的要棘手一些,所以我可以理解为什么gcc不这样做。

0

你也可以使用函数指针或者仿函数(我猜测是仿函数地址)的数组,来避免使用 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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接