Reputation: 2689
In modeling hardware registers in C++11/14, I'm doing some experimentation with constexpr and a few other things. I'm having an issue getting the compiler to complain where I want it to.
In my class below, I'm defining a register as being 32 bits, and containing fields of varying width with enumerated values. The enum name is unique to the register so there will never be FIELD1_ON and FIELD2_ON. I build up the enum to be <32 bits of field mask> and <32 bits of enum value> so I can manage only setting the required bits and not changing bits I'm not interested in.
I want to be able to express multiple fields by saying ENUM1 | ENUM2 | ENUM3, but this is where I'm having trouble.
If ENUM1 and ENUM2 are in the same field I want it to give me a compilation error if possible, and then a runtime error if not.
Here's my code
constexpr uint32_t ffs ( uint32_t value )
{
constexpr uint32_t DeBruijnSequence[32] =
{
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
return DeBruijnSequence[
(( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF)
>> 27];
}
#define MAKE_ENUM_VAL(field,value) ((static_cast<uint64_t>(field)<<32ULL) | (value<<ffs(static_cast<uint32_t>(field))))
class Register
{
public:
enum struct Fields
{
FIELD1 = 0b00000000000000000000000000000001,
FIELD2 = 0b00000000000000000000000000001110,
FIELD3 = 0b00000000000000000000000011110000,
FIELD4 = 0b00000000000000001111111100000000,
};
enum struct Enums : uint64_t
{
ON = MAKE_ENUM_VAL(Register::Fields::FIELD1, 1),
OFF = MAKE_ENUM_VAL(Register::Fields::FIELD1, 0),
MODE0 = MAKE_ENUM_VAL(Register::Fields::FIELD2, 1),
MODE1 = MAKE_ENUM_VAL(Register::Fields::FIELD2, 2),
MODE2 = MAKE_ENUM_VAL(Register::Fields::FIELD2, 3),
MODE3 = MAKE_ENUM_VAL(Register::Fields::FIELD2, 4),
MODE4 = MAKE_ENUM_VAL(Register::Fields::FIELD2, 5),
MODE5 = MAKE_ENUM_VAL(Register::Fields::FIELD2, 6),
};
};
constexpr Register::Enums operator | (Register::Enums a, Register::Enums b)
{
return
((static_cast<uint64_t>(a) & static_cast<uint64_t>(b)) == 0) ?
static_cast<Register::Enums>(static_cast<uint64_t>(a) | static_cast<uint64_t>(b))
: throw "Bad juju";
}
Here's my test code:
Register::Enums MyEnum1 = Register::Enums::ON | Register::Enums::OFF; //desire failed compilation
Register::Enums MyEnum2 = Register::Enums::ON | Register::Enums::MODE1;
Register::Enums MyEnum3 = Register::Enums::ON;
Register::Enums MyEnum4 = Register::Enums::OFF;
Register::Enums MyEnum;
MyEnum = MyEnum3 | MyEnum4; //assert/throw at runtime
I WANT the compilation of MyEnum1
to fail. (Trying to set FIELD1
to both ON
and OFF
). I've tried doing a check in the |
operator (&ing the bits of a and b should be result in 0, meaning no overlap), but the best I can get it to do is fail over to the runtime version and then hit the assert.
I've tried fooling with static assert(), but it seems like constexpr doesn't like that inside of it. I've fiddled with some other things (trying to detect when I'm in the constexpr using some oddball constructs I've found here on stackoverflow), but I can't seem to find anything that gives me the behavior I want.
Any suggestions that don't include C++17/20? My embedded compilers aren't that advanced.
Upvotes: 1
Views: 407
Reputation: 3763
The constexpr specifier declares that it is possible to evaluate the value of the function or variable at compile time. Such variables and functions can then be used where only compile time constant expressions are allowed (provided that appropriate function arguments are given). (cppref)
constexpr
does not guarantee forced compile-time evaluation, it allows parameters to be run-time values, so static_assert
does not work here.
A solution is to use the non-type template parameter, which requires the value to be a constant.
But the disadvantage is that you can no longer use the "|" (or) operator, but a new function instead.
The new static member function of Register
:
template <Register::Enums a, Register::Enums b>
static constexpr Register::Enums flag_or()
{
static_assert((static_cast<uint64_t>(a) & static_cast<uint64_t>(b)) == 0);
return static_cast<Register::Enums>(static_cast<uint64_t>(a) | static_cast<uint64_t>(b));
}
and usage:
int main()
{
Register::Enums MyEnum1 = Register::flag_or<Register::Enums::ON, Register::Enums::OFF>(); //desire failed compilation
Register::Enums MyEnum2 = Register::flag_or<Register::Enums::ON, Register::Enums::MODE1>();
constexpr Register::Enums MyEnum3 = Register::Enums::ON;
constexpr Register::Enums MyEnum4 = Register::Enums::OFF;
Register::Enums MyEnum;
MyEnum = Register::flag_or<MyEnum3, MyEnum4>(); //assert/throw at runtime
}
Output:
Compiler returned: 1
Compiler stderr
<source>: In instantiation of 'static constexpr Register::Enums Register::flag_or() [with Register::Enums a = Register::Enums::ON; Register::Enums b = Register::Enums::OFF]':
<source>:50:91: required from here
<source>:43:77: error: static assertion failed
43 | static_assert((static_cast<uint64_t>(a) & static_cast<uint64_t>(b)) == 0);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
Link to godbold.
In C++20, the consteval
keyword could probably be applied to the "|" (or) operator function, but unfortunately it doesn't work for your case.
Upvotes: 1