Enums
EnumDeclaration: enum Identifier EnumBody enum Identifier : EnumBaseType EnumBody AnonymousEnumDeclaration EnumBaseType: Type EnumBody: { EnumMembers } ; EnumMembers: EnumMember EnumMember , EnumMember , EnumMembers EnumMember: EnumMemberAttributesopt Identifier EnumMemberAttributesopt Identifier = AssignExpression EnumMemberAttributes: EnumMemberAttribute EnumMemberAttribute EnumMemberAttributes EnumMemberAttribute: DeprecatedAttribute UserDefinedAttribute @disable
AnonymousEnumDeclaration: enum : EnumBaseType { EnumMembers } enum { AnonymousEnumMembers } AnonymousEnumMembers: AnonymousEnumMember AnonymousEnumMember , AnonymousEnumMember , AnonymousEnumMembers AnonymousEnumMember: EnumMember EnumMemberAttributesopt Type Identifier = AssignExpression
Enum declarations are used to define a group of constants.
Named Enums
Named enums are used to declare related constants and group them by giving them a unique type. The EnumMembers are declared in the scope of the named enum. The named enum declares a new type, and all the EnumMembers have that type.
This defines a new type X which has values X.A=0, X.B=1, X.C=2:
enum X { A, B, C } // named enum
If the EnumBaseType is not explicitly set, and the first EnumMember has an AssignExpression, it is set to the type of that AssignExpression. Otherwise, it defaults to type int.
Named enum members may not have individual Types.
A named enum member can be implicitly cast to its EnumBaseType, but EnumBaseType types cannot be implicitly cast to an enum type.
The value of an EnumMember is given by its AssignExpression if present. If there is no AssignExpression and it is the first EnumMember, its value is EnumBaseType.init. If there is no AssignExpression and it is not the first EnumMember, it is given the value of the previous EnumMember+1:
- If the value of the previous EnumMember is EnumBaseType.max, it is an error. This prevents value overflow. It is an error if the previous member cannot be compared with EnumBaseType.max at compile-time.
- It is an error if the base type does not define a compile-time evaluable +1 operation.
- If the value of the previous EnumMember+1 is the same as the value of the previous EnumMember, it is an error. (This can happen with floating point types).
All EnumMembers are in scope for the AssignExpressions.
enum A = 3; enum B { A = A // error, circular reference } enum C { A = B, // A = 4 B = D, // B = 4 C = 3, // C = 3 D // D = 4 } enum E : C { E1 = C.D, E2 // error, C.D is C.max }
An empty enum body signifies an opaque enum - the enum members are unknown.
enum X; // opaque enum writeln(X.init); // error: enum X is opaque and has no default initializer
Enum Default Initializer
The .init property of an enum type is the value of the first member of that enum. This is also the default initializer for the enum type.
enum X { A=3, B, C } X x; // x is initialized to 3
Enum Properties
Enum properties only exist for named enums.
.init | First enum member value |
.min | Smallest enum member value |
.max | Largest enum member value |
.sizeof | Size of storage for an enumerated value |
For example:
enum X { A=3, B=1, C=4, D, E=2 } X.init // is X.A X.min // is X.B X.max // is X.D X.sizeof // is same as int.sizeof
The EnumBaseType of named enums must support comparison in order to compute the .max and .min properties.
Anonymous Enums
If the enum Identifier is not present, then the enum is an anonymous enum, and the EnumMembers are declared in the scope the EnumDeclaration appears in. No new type is created.
The EnumMembers can have different types. Those types are given by the first of:
- The Type, if present. Types are not permitted when an EnumBaseType is present.
- The EnumBaseType, if present.
- The type of the AssignExpression, if present.
- The type of the previous EnumMember, if present.
- int
enum { A, B, C } // anonymous enum
Defines the constants A=0, B=1, C=2, all of type int.
Enums must have at least one member.
The value of an EnumMember is given by its AssignExpression if present. If there is no AssignExpression and it is the first EnumMember, its value is the .init property of the EnumMember's type. If there is no AssignExpression and it is not the first EnumMember, it is given the value of the previous EnumMember+1:
- If the value of the previous EnumMember is the .max property of the previous EnumMember's type, it is an error. This prevents value overflow. It is an error if the previous member cannot be compared with its .max property at compile-time.
- It is an error if the type of the previous member does not define a compile-time evaluable +1 operation.
- If the value of the previous EnumMember+1 is the same as the value of the previous EnumMember, it is an error. (This can happen with floating point types).
All EnumMembers are in scope for the AssignExpressions.
enum { A, B = 5+7, C, D = 8+C, E }
Sets A=0, B=12, C=13, D=21, and E=22, all of type int.
enum : long { A = 3, B }
Sets A=3, B=4 all of type long.
enum : string { A = "hello", B = "betty", C // error, cannot add 1 to "betty" }
enum { A = 1.2f, // A is 1.2f of type float B, // B is 2.2f of type float int C = 3, // C is 3 of type int D // D is 4 of type int }
Single Member Syntax
If there is only one member of an anonymous enum, the { } can be omitted. Gramatically speaking, this is an AutoDeclaration.
enum i = 4; // i is 4 of type int enum long l = 3; // l is 3 of type long
Manifest Constants
Enum members are manifest constants, which exist only at compile-time.
Manifest constants are not lvalues, meaning their address cannot be taken. They exist only in the memory of the compiler.
enum size = __traits(classInstanceSize, Foo); // evaluated at compile-time
The initializer for a manifest constant is evaluated using compile time function evaluation.
template Foo(T) { // Not bad, but the 'size' variable will be located in the executable. const size_t size = T.sizeof; // evaluated at compile-time // ... use of 'size' at compile time ... } template Bar(T) { // Better, the manifest constant has no runtime location in the executable. enum size_t size = T.sizeof; // evaluated at compile-time // ... use of 'size' at compile time ... // Taking the address of Foo!T.size also causes it to go into the exe file. auto p = &Foo!T.size; }