C++: struct member in a switch statement

霸气de小男生 提交于 2019-12-05 03:23:16

I've tested your code in Qt Creator 3.1.2 with MinGW 4.8.3 compiler. Just replacing const by constexpr in each instruction definition made the compiler happy:

struct instruction
{
    const int opcode; // instruction opcode
    const int op_size; // how far to advance Program Counter
};

// Replacing "const" by "constexpr" int these two lines
constexpr instruction HALT{0x76, 1};
constexpr instruction NOP {0x00, 1};

int main() {
    int next_instruction = 0x76;
    switch (next_instruction) { // next_instruction is an int parsed from a file
        case HALT.opcode:
            // do stuff
            break;
        case NOP.opcode:
            // do stuff
            break;
        default:
            std::cout << "Unrecognized opcode" << std::endl;
                break;
        }
}

Edit to add some quotes:

The C++ Programming Language (Fourh Edition) says about labels in switch statements:

The expression in the case labels must be a constant expression of integral or enumeration type.” (9.4.2 switch Statements”).

From Section 10.4 Constant Expressions:

C++ offers two related meaning of “constant”:

  • constexpr: Evaluate at compile time
  • const: Do not modify in this scope

Basically, constexpr’s role is to enable and ensure compile-time evaluation, whereas const’s primary role is to specify immutability in interfaces.

[…]

10.4.2 const’s in Constant Expressions

[…] A const initialized with a constant expression can be used in a constant expression. A const differs from a constexpr in that it can be initialized by something that is not a constant expression; in that case, the const cannot be used as a constant expression.

Labels in switch statements requieres constexpr so that evaluation is done at compile time. So it seems that const instruction HALT {0x76,1} does not ensure compile time evaluation while constexpr instruction HALT {0x076,1} does.

If you wan't to get adventurous with templates, a possible solution without the dreaded macro could be as follows.

template<int code, int size>
struct InstructionType
{
    static const int opcode = code ;
    static const int op_size = size;
};
struct Instruction 
{
    int opcode;
    int op_size;
};
typedef  InstructionType<0x76, 1> HALT;
typedef  InstructionType<0x00, 1> NOP;


int main()
{
    Instruction next_instruction;
    switch (next_instruction.opcode) {
    case HALT::opcode:
        // do stuff
        break;
    case NOP::opcode:
        // do stuff
        break;
    default:
        std::cout << "Unrecognized opcode" << std::endl;
        break;
    }
}

Stepping back from the trees for just a moment, it's a fairly safe bet you're writing an 8080 / Z80 emulator. So don't use a switch at all. Arrange your instruction structures in an array, and use the opcode to be executed as an index.

struct instruction
{
    const int opcode; // instruction opcode
    const int op_size; // how far to advance Program Counter
    const void (*emulator)(parameter list);  // code for this opcode
};

void illegal(parameter list)
{
    std::cout << "Unrecognized opcode" << std::endl;
}

instruction opcodes[] =  // assuming 8080 for now
{
{0x00, 1, nop_emulator},  // NOP
{0x01, 3, lxib_emulator}, // LXI B
etc.
{0x08, 1, illegal},       // 0x08 is invalid on the 8080
etc.
};

Now your code just becomes

opcodes[next_instruction].emulator(parameter list);

You have the option of either discarding the opcode, or doing a precheck to ensure that every opcode is in the correct place in the table.

This also has the advantage that it'll stop your code from being one monolithic routine by breaking it up into one routine per opcode. If you're writing a Z80 emulator this will become a major concern because of the 0xCB, 0xDD, 0xED and 0xFD groups, which in a switch pattern would require a second switch within each of the the case handlers for those four pseudo-opcodes.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!