I have not seen documentation that says that the word (bit)packed also applies to nested record types. Yes, now it works because of the compatibility with Delphi, but it does not look logical.
It is logical and it should work in this way. If the main record is marked as
packed or
bitpacked, its entire content should be packed — if the subrecord are declared directly, not as an external type.
Although for the sake of uniformity, this internal record can also be declared as
packed or
bitpacked.
After all, if you declare a nested structure as a separate type, that is, without words packed, and use it already in the declaration, you will get a different result.
And this behavior is also correct. Both are correct and logical.
Moreover, the code like "Include(Set, Flag)" or "if Flag in Set then" is not only more readable […]
No, it is not readable and it is not convenient to use, just like using the
Inc,
Dec,
Length and other ancient subroutines. And it does not give you the option to set a flag based on any type other than
TFlags, which is undesirable because it is limiting.
But the set of flags can be used together with boolean flags. If I declare the types for example in this way:
{$MODE OBJFPC}
{$PACKSET 1}
{$PACKENUM 1}
type
TEnumFlag = (Carry, Zero, Interrupt, Decimal, Break, NotUsed, Overflow, Negative);
TEnumFlags = set of TEnumFlag;
{$PACKSET DEFAULT}
{$PACKENUM DEFAULT}
type
TBoolFlags = bitpacked record
Carry: Boolean;
Zero: Boolean;
Interrupt: Boolean;
Decimal: Boolean;
Break: Boolean;
NotUsed: Boolean;
Overflow: Boolean;
Negative: Boolean;
end;
type
TCPUFlags = bitpacked record
case Integer of
0: (Byte: Byte);
1: (Bool: TBoolFlags);
2: (Enum: TEnumFlags);
end;
var
CPUFlags: TCPUFlags;
then I can initialize the state of flags by assigning the whole set:
CPUFlags.Enum := [Interrupt, Decimal];
If I need to include or exclude some flags, I can use the
+= and
-= operator:
CPUFlags.Enum += [NotUsed, Overflow];
CPUFlags.Enum -= [Decimal];
I can get or set the state of flags as a number without having to cast:
IntValue := CPUFlags.Byte;
and if I need it, I can set each flag separately based on testing values of any type:
CPUFlags.Bool.Break := IntValue <> 0;
or assigning a specific logical value without testing. And if I need to check if the flag is set, I can read the logical value directly, without testing:
if CPUFlags.Bool.Interrupt then
Easy to use, readable and functional.
[…] but also more efficient.
In both cases reading the state of the flag (bit) boils down to conjunction and comparison to
0. However, such a simple case (a primitive operation) should be optimized by the optimizer, not by the programmer.