Here is an answer that will work, be portable, not invoke undefined behavior on any compiler, and be optimized fairly effectively:
struct instruction {
typedef unsigned int uint_t;
explicit instruction(uint_t val) : val_(val) {}
instruction(uint_t op_code, uint_t reg_dest, uint_t reg_s1, uint_t offset)
: val_((op_code & 0x3fu << 26) | (reg_dest & 0x1fu << 21) |
(reg_s1 & 0x1fu << 16) | (offset & 0xffffu))
{
}
uint_t op_code() const { return (val_ >> 26) & 0x3fu; }
void op_code(uint_t newval) { val_ = (newval & 0x3fu << 26) | (val_ & 0x3ffffffu); }
uint_t reg_dest() const { return (val_ >> 21) & 0x1fu; }
void reg_dest(uint_t newval) { val_ = (newval & 0x1fu << 21) | (val_ & 0xfc1fffffu); }
uint_t reg_s1() const { return (val_ >> 16) & 0x1fu; }
void reg_s1(uint_t newval) { val_ = (newval & 0x1fu) << 16) | (val_ & 0xffe0ffffu); }
uint_t offset() const { return (val_ >> 16) & 0xffffu; }
void offset(uint_t newval) const { val_ = (newval & 0xffffu) | (val & 0xffff0000u); }
uint_t &int_ref() { return val_; }
uint_t int_ref() const { return val_; }
private:
uint_t val_;
};
This lets you access all of the bitfields with a very convenient notation. I think it's also a POD, which lets you use it in a few interesting ways. And a good compiler will do a fairly decent job of optimizing the bit munging operations, especially if you have several calls to the convenience functions in a row.
It's almost as nice as having an overlay bit field. It's just a bit more work to define in the first place.
Also, I changed the type to `unsigned int` because if you're fiddling around with the bits, you really want a simply represented number without a sign bit or anything funky like that. Ideally you'd be including the `<cstdint>` header and using `::std::uint32_t` or something in the typedef at the top.