C++, End to End
Enums and Structs
Enums give a name to a small, fixed set of values. Structs bundle related data together. Both are simple on the surface, and both have a modern-vs-legacy version worth knowing the difference between.
Unscoped enums
The original C-style enum. Its values live in the surrounding scope, not inside the enum's own namespace, which means they can collide with other names and, worse, implicitly convert to int without any cast.
Scoped enums (enum class)
C++11's enum class fixes both problems: values are scoped to the enum's own name (Color::red, not just red), and there's no implicit conversion to int, an explicit static_cast is required if you actually want the underlying integer.
Default to enum class in new code. The only reason to reach for the older unscoped enum is interfacing with an existing API that already uses one, or the rare case where you genuinely want the implicit int conversion (bit flags combined with |, for instance, though a scoped enum with explicit casts is usually still clearer).
Underlying types
Every enum has an underlying integer type, int by default for unscoped enums, and also int by default for scoped enums, though you can specify a smaller one explicitly if the value range doesn't need it, which can matter for memory layout in large arrays of enum values.
Structs and aggregate initialization
A struct bundles related fields together. When a struct has no constructors, no private/protected members, and no virtual functions (an "aggregate"), you can initialize all its members directly with brace initialization, in declaration order:
Designated initializers (C++20)
C++20 lets you name which member you're initializing, matching a feature C has had for a while. This makes initialization order-independent and self-documenting, especially valuable once a struct has more than two or three fields.
struct vs. class: the only real difference
This surprises a lot of people coming from languages where struct and class mean genuinely different things: in C++, they compile to the exact same kind of type, with exactly one difference. Members and base classes default to public in a struct, and to private in a class. That's it. Everything else, constructors, methods, inheritance, virtual functions, works identically for both keywords.
In practice, most code uses struct for simple data-holding types with no invariants to protect (plain aggregates like the Point and Config above), and class for types with real behavior, private state, and constructors that enforce invariants. That's a convention, not a language rule, but it's a strong and nearly universal one, and deviating from it without a good reason will confuse anyone reading your code.
Try it yourself: convert an unscoped enum and add designated initializers›
Convert Direction below to a scoped enum class, fix the resulting compile errors at each use site, then add a Player struct with at least three fields and construct one using designated initializers.