Because C++ lacks enforcement ("strong types, weakly checked") and loves implied conversions, when you mimick these patterns you don't get all of their benefits, which can result in misplaced confidence.
Take the most simple example, suppose I have an enumerated type, all these HedgehogSizes are either TINY or NORMAL or HUGE. Awesome. But, in C++ a HedgehogSize can be 6. Um. What? Oh says C++ I could see that's just an integer, and 6 is an integer so that's fine... Right?
I broadly agree with the lots-of-pitfalls nature, but regarding the enum case specifically, which version of C++ are you talking about, and in what syntactic context? The later-introduced “enum class” makes things somewhat tighter than C-style enums.
All versions of C++, all existing versions and likely any potential future versions.
In C++ both these enum types have an "underlying type" which is just an integer. That's the type you're really getting for most purposes.
In terms of syntax specifically you can cast (in C++ specifically static cast) integers to an enumerated type. This makes mechanical sense to C++ because of the compatible "underlying type" but as a direct consequence you don't have the kind of type safety these exercises are relying on.
Notice that if I cast 1.234 to an integer, C++ says that's 1. It does know that cast doesn't mean "just change the type but keep the same bits" and yet, that's exactly what happens for enum.
std::variant is a poor imitation. The C++ type system can't really do this, so it's contorted to try to get as close as we can, and the results aren't pretty.
1. Consider this Rust type: enum InvisibleDog { } - this is an Empty Type, there are no Invisible Dogs, Rust is OK with the existence of this type, although since it's uninhabited there can be no instances (and the type accordingly has no size, not zero size, no size at all). Such types don't make a whole lot of sense concretely, but they're crucial to good generic programming.
In C++ we can't have such a type, the closest we can attempt is a std::variant whose only actual variant is valueless_by_exception and has a size of at least 1 byte. This is a disaster because it completely fails to achieve what we meant.
2. OK how about enum JustOneByte { Byte(u8) } - this type is as its name suggests, just a byte, Rust's u8 type. Its representation is accordingly one byte.
But with std::variant we can't do that either, C++ has to account for valueless_by_exception and so it has to carry around a discriminator, so that it can discriminate between Byte, the only actual possible value of this type, and valueless_by_exception. As a result in C this type is larger than its "real" content, which is just one byte.
3. We can't do the guaranteed niche optimisation either. Rust's Option is literally just a sum type. It's not magic (in that sense, it's effectively a langitem so it is magic in some other ways). But despite the lack of magic Option<&T> is guaranteed to be the same size as &T is thanks to the guaranteed niche optimisation. C++ can't do that either.
I just started reading about liquid types and I'm loving the idea : https://news.ycombinator.com/item?id=37349276