Reputation: 79
tl;dr: Is there a way to derive enum value from typename?
details: I'm trying to template a function to send a header before each struct data. The headers are stored in an enum class, for example:
enum class TypeHeader {
Test1 = 4;
Test2 = 16;
Test3 = 50;
};
I have many structs with fields and types. example structs:
struct Test1 {
uint32_t field1;
uint32_t field2;
}
struct Test2 {
uint8_t field1;
uint8_t field2;
}
struct Test3 {
uint8_t field1;
uint16_t field2;
}
The name of the structs is same as the enum fields (doesn't have to be, I thought that might make things easier)
the function that sends data to server:
template<typename T>
void send(const uint8_t* const s) {
uint8_t data[2 + sizeof(T)];
// data[1] = TypeHeader::???? << 8
// data[0] = TypeHeader::????
// i want data[0] and data[1] to have the struct id
// as a uint16 which is in the enum TypeHeader
memcpy(data + 2, s, sizeof(T));
// then data is ready and I can send it...
}
I know that I can add an additional argument to the template or function and pass the TypeHeader value, but I was wondering if there is a way the compiler can link the typename to the enum tag so I can do something like this:
Test1 test1;
send(test1);
Upvotes: 0
Views: 1204
Reputation: 25593
You should add the value as static element to your struct definitions like this:
enum class TypeHeader: uint16_t { // make it really a 16bit as you later access it as such!
Test1 = 4,
Test2 = 16,
Test3 = 50,
};
struct Test1 {
uint32_t field1;
uint32_t field2;
static constexpr TypeHeader header = TypeHeader::Test1; // not part of object but of the type!
};
struct Test2 {
uint8_t field1;
uint8_t field2;
static constexpr TypeHeader header = TypeHeader::Test2;
};
struct Test3 {
uint8_t field1;
uint16_t field2;
static constexpr TypeHeader header = TypeHeader::Test3;
};
template<typename T>
void send(const T& obj)
{
uint8_t data[2 + sizeof(T)];
// converting to the given type, even if T changes
data[1] = (std::underlying_type_t<decltype(obj.header)>)T::header >> 8; // you wrongly used <<!
data[0] = (std::underlying_type_t<decltype(obj.header)>)T::header & 0xff;
memcpy(data + 2, &obj, sizeof(T));
}
int main()
{
Test1 t1{ 1,2 };
send( t1 );
}
EDIT: Add the description of the cast:
obj.header
is an enum which we can not directly use as an numeric value. But you want to write it to your sendbuffer as 2 byte buffer with 8 bit low and high value. As this, we have to cast it to an integer value. The enum value is defined as uint16_t
value. OK, but we don't want to copy that definition because maybe we want to change the definition later. So it is a good ida to exactly use the type of the variable we use. OR, if we want to use our send function more generic, we also can use different enum values which all can casted to our buffer numeric representation. Maybe we cn have some more stuff in the send function to enable it to use 32 bit values or whatever you want. I added this cast idea to give you an example how to prepare your templated functions to be more generic. Yes, you can also directly cast to uint16_t
. But it is always a good idea to make your program easier to maintain, if you use the knowledge which you already have from the compiler. If you write two times ( ones in enum definition and again while casting ) your type you have a good chance to later forget one, if you change your definition.
Here we go:
obj.header
is the variable, even if it is constdecltype(obj.header)
gives us the type we have which is the enum typestd::underlying_type_t
gives us now the integer type which represents our enum. We have defined that as uint16_t! And this type we use to cast into.Next addition: OP asks for the followig code, commented with my thoughts:
template <typename T> void send(const T& obj)
{
auto header = (std::underlying_type_t<decltype(obj.header)>)T::header;
// if your header now vary in the size, you must add an additional header
// size information! If not, the receiver did not know if data is from
// header or from the following data field
uint8_t data[sizeof(header) + sizeof(T)];
// now you have the order of bytes in the header
// in the same order as the machine uses ( endianess )
// That is ok as long you use the same system architecture
// to read back the data but it is not portable
memcpy(data, &header, sizeof(T));
// here you definitely write the data in the architecture dependent
// order and also with potential padding bytes...
// As long as you know what you do, everything is fine.
// But if you change from 32 bit to 64 bit or use pragma(pack)
// you will change your serialized format!
memcpy(data + sizeof(header), &obj, sizeof(T));
}
Upvotes: 4
Reputation: 217085
You cannot use type (from template) as enum name.
There are alternative, for example, template variables:
template <typename T> static const uint16_t TypeHeader;
template <> const uint16_t TypeHeader<Type1> = 4;
template <> const uint16_t TypeHeader<Type2> = 16;
template <> const uint16_t TypeHeader<Type3> = 50;
and then, use it like:
template<typename T>
void send(const uint8_t* const s) {
uint8_t data[2 + sizeof(T)];
data[0] = TypeHeader<T> & 0xFF;
data[1] = (TypeHeader<T> >> 8) & 0xFF;
memcpy(data + 2, s, sizeof (T));
// then data is ready and I can send it...
}
Upvotes: 1