The following code:
void f(const uint8_t* a) {} // <- this is an external library function
enum E : uint8_t { X, Y, Z };
int main(void) {
E e = X;
f(&e); // <- error here
}
Produces the following error:
/tmp/c.cc:10:3: error: no matching function for call to 'f'
f(&e);
^
/tmp/c.cc:5:6: note: candidate function not viable: no known conversion from 'E *' to 'const uint8_t *' (aka 'const unsigned char *') for 1st argument
void f(const uint8_t* e) { }
Which is surprising to me because I thought the : uint8_t
in the enum's definition would mean they are necessarily represented with that underlying type. I can easily get around this with a cast:
f((uint8_t*)&e);
which I don't mind too much, but given that it's an error to omit it, is this always safe or is the : uint8_t
not providing the guarantees I think it is?
reinterpret_cast is a type of casting operator used in C++. It is used to convert a pointer of some data type into a pointer of another data type, even if the data types before and after conversion are different.
The stringify() macro method is used to convert an enum into a string. Variable dereferencing and macro replacements are not necessary with this method. The important thing is that, only the text included in parenthesis may be converted using the stringify() method.
Reinterpret casts are only available in C++ and are the least safe form of cast, allowing the reinterpretation of the underlying bits of a value into another type. It should not be used to cast down a class hierarchy or to remove the const or volatile qualifiers.
C++03 enumerations are not devoid of type safety, however. In particular, direct assignment of one enumeration type to another is not permitted. Moreover, there is no implicit conversion from an integer value to an enumeration type.
Afaik this is only legal by accident:
What you are doing is performing a reinterpret_cast
and I'm assuming f
is dereferencing that pointer internally. This is only legal in a very restricted set of cases and while not normative, cppreference.com gives a good overview of those:
When a pointer or reference to object whose dynamic type is DynamicType is reinterpret_cast (or C-style cast) to a pointer or reference to object of a different type AliasedType, the cast always succeeds, but the resulting pointer or reference may only be used to access the object if one of the following is true:
AliasedType is (possibly cv-qualified) DynamicType
AliasedType and DynamicType are both (possibly multi-level, possibly cv-qualified at each level) pointers to the same type T (since C++11)
AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType
AliasedType is an aggregate type or a union type which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions): this makes it safe to obtain a usable pointer to a struct or union given a pointer to its non-static member or element.
AliasedType is a (possibly cv-qualified) base class of DynamicType and DynamicType is a standard-layout class that has has no non-static data members, and AliasedType is its first base class.
AliasedType is char, unsigned char, or std::byte: this permits examination of the object representation of any object as an array of bytes.
If AliasedType does not satisfy these requirements, accessing the object through the new pointer or reference invokes undefined behavior. This is known as the strict aliasing rule and applies to both C++ and C programming languages.
None of those cases includes casting to a pointer to an enum's underlying type!
However:
Casting to a pointer to unsigned char*
and dereferencing it is always legal and on most platforms uint8_t
is just a typedef for that. So it is ok in this case, but wouldn't be if the underlying type e.g. would be a uint16_t
.
That being said, I would not be surprised to hear that most compilers would allow such usage, even if the standard does not.
It is indeed safe (although I'm not a language-lawyer): What's stored in memory is a uint8_t
, and that's what you'll be pointing at. However, if f()
were to take a pointer to a non-const uint8_t
, then it could potentially change the value to something that's not explicitly defined as one of the E
enum values. (Edit:) While this is apparently allowed by the C++ standard, it is surprising to many (see discussion in the comments below on this point), and I would encourage you to ensure it does not happen.
... but as others suggest, you're not getting the error because of your notion of safety, but because implicit conversions aren't performed between pointed-to types. You can pass an E
to a function taking a uint8_t
, but not an E *
to a function taking a uint8_t *
; that would be - according to the language committee and in my opinion as well - an overly cavalier attitude to pointer types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With