views:

1668

answers:

8

I'm attempting to map a set of key presses to a set of commands. Because I process the commands from several places, I'd like to set up a layer of abstraction between the keys and the commands so that if I change the underlying key mappings, I don't have to change very much code. My current attempt looks like this:

// 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;
    ...
}

When I try to compile this (gcc 4.3.2) I get the error message:

error: 'LogicalMappings' cannot appear in a constant-expression

I don't see why the compiler has a problem with this. I understand why you're not allowed to have variables in a case statement, but I was under the impression that you could use constants, as they could be evaluated at compile-time. Do constant arrays not work with switch statements? If so, I suppose I could just replace the array with something like:

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

But that seems much less elegant. Does anybody know why you can't use a constant array here?

EDIT: I've seen the bit of the C++ standard that claims that, "An integral constant-expression can involve only literals (2.13), enumerators, const variables or static data members of integral or enumeration types initialized with constant expressions (8.5)...". I still don't see why a constant array doesn't count as an "enumeration type initialized with a constant expression." It could just be that the answer to my question is "because that's the way that it is," and I'll have to work around it. But if that's the case, it's sort of disappointing, because the compiler really could determine those values at compile-time.

A: 

Is there a comparison operator defined for the "LogicalMappings"? If not then that is the error.

fasih.ahmed
I'm not sure I understand what you mean...can you elaborate?
Jonathan Leffler
What I wanted to ask was if there is a equality operator overloaded for the class LogicalMappings. Seeing that the 'swicth' blocks use the equality operator to find the 'case'.
fasih.ahmed
+1  A: 

I'll go on a limb here since nobody else replied to this and I've been mostly doing Java recently, not C++, but as far as I seem to recall an array lookup is not considered a constant integer even if the result of the lookup can be determined at compile time. This may even be an issue in the syntax.

Uri
+2  A: 

Array references aren't "constant enough", regardless.

You just need to do the mapping slightly differently. You want the same action to occur when the logical key is pressed, so use the logical key codes in the case clauses of the switch statement. Then map the actual key code to the logical code, possibly in the switch itself, or possibly before-hand. You can still use the LogicalMappings array, or a similar construct. And, as an aid to G11N (globalization), you can even make the mapping array non-constant so that different people can remap the keys to suit their needs.

Jonathan Leffler
+2  A: 

Referring to sections of the C++ standard: 6.4.2 requires that case expressions evaluate to an integral or enumeration constant. 5.19 defines what that is:

An integral constant-expression can involve only literals (2.13), enumerators, const variables or static data members of integral or enumeration types initialized with constant expressions (8.5), non-type template parameters of integral or enumeration types, and sizeof expressions. Floating literals (2.13.3) can appear only if they are cast to integral or enumeration types. Only type conversions to integral or enumeration types can be used. In particular, except in sizeof expressions, functions, class objects, pointers, or references shall not be used, and assignment, increment, decrement, function-call, or comma operators shall not be used.

So if your question was "why does the compiler reject this", one answer is "because the standard says so".

Martin v. Löwis
A: 

Something like LOGICAL_MAPPING_DO_SOMETHING = SDLK_RETURN yields same values, so your mapping becomes an x->x operation.

Why not use template class with static const member or enumeration constant, say, Value, provide nessesary mapping with specializations, and then you could write

case LogicalMappings<DO_SOMETHING_KEY>::Value: ...
eugensk00
A: 

There is a library called signal in boost that will help you create a event mapping abstraction.If you have time this should be better approach

yesraaj
A: 

you could also use an array of function pointers or functors (I suppose functor addresses), to avoid the switch statement altogether & just go from array index -> function pointer / functors directly.

for example (warning, untested code follows)

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);
}
Jason S
A: 

A compiler guru at work explained this to me. The problem is that the array itself is constant, but indices to it aren't necessarily const. Thus, the expression LogicalMappings[some_variable] couldn't be evaluated at compile-time, so the array winds up being stored in memory anyway rather than getting compiled out. There's still no reason why the compiler couldn't statically evaluate array references with a const or literal index, so what I want to do should theoretically be possible, but it's a bit trickier than I'd thought, so I can understand why gcc doesn't do it.