Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: struct member in a switch statement

I am writing a microprocessor emulator in C++, and one of my goals was to make it the code very readable. To implement opcodes, I have a struct which I am using to represent individual processor instructions, and it contains both the opcode and how far to advance the program counter. The idea was to group related information about each instruction.

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

const instruction HALT{0x76, 1};
const instruction NOP {0x00, 1};

My original plan was to define all the opcodes using this struct, as I was under the impression that const was preferred to using #define for C++ constants. In addition, I would be able to cleanly group all related attributes of an opcode.

However, it seems that this will not work for switch statements, as I originally intended. The following code will not compile, and Visual Studio gives the error "case expression not constant".

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

I've also downloaded the latest Visual Studio compiler (MSVC November 2013 CTP) to try to leverage constexpr from C++11, but I had the same problem, and it will not compile. Here I converted my struct to a class and attempted to leverage constexpr, to ensure that the members of an instruction could be used as compile-time constants.

class Instruction
{
  public:
    constexpr Instruction(int code, int size) : opcode(code), op_size(size) {}
    const int opcode; // instruction opcode
    const int op_size; // how far to advance Program Counter
};

constexpr Instruction HALT(0x76, 1);
constexpr Instruction NOP (0x00, 1);

I'm not really sure what to do at this point, since it appears that the compiler does not understand that the struct values are assigned as constants.

So is there any way to use a struct member in a switch statement, or should I just switch to using #define? Alternatively, is there a better way to do this while still retaining some organization? I'd really appreciate any help or insight you can offer, thanks!

EDIT: Sorry, I should have made it clearer that next_instruction is just an int, not an instruction struct/object

like image 595
dcoffill Avatar asked Sep 12 '14 06:09

dcoffill


People also ask

Can we use switch-case in structure in C?

A general syntax of how switch-case is implemented in a 'C' program is as follows: switch( expression ) { case value-1: Block-1; Break; case value-2: Block-2; Break; case value-n: Block-n; Break; default: Block-1; Break; } Statement-x; The expression can be integer expression or a character expression.

Can C structs have member functions?

Member functions inside the structure: Structures in C cannot have member functions inside a structure but Structures in C++ can have member functions along with data members.

What is not allowed in a switch statement?

The value of the expressions in a switch-case statement must be an ordinal type i.e. integer, char, short, long, etc. Float and double are not allowed. The case statements and the default statement can occur in any order in the switch statement.

Can you use && in a switch statement?

The simple answer is No. You cant use it like that.


3 Answers

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.

like image 132
davidaf Avatar answered Oct 13 '22 06:10

davidaf


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;
    }
}
like image 5
Abhijit Avatar answered Oct 13 '22 05:10

Abhijit


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.

like image 2
dgnuff Avatar answered Oct 13 '22 06:10

dgnuff