On 2016-06-29 00:30, Sergey Vidyuk wrote:
> On 2016-06-28 10:45, Matthew Woehlke wrote:
>> On 2016-06-28 06:27, Sergey Vidyuk wrote:
>>> One more issue here is inverse operation can't be implemented in
>>> order the following code work without triggering assertion:
>>>
>>> enum class Day {Mon, Tue, Wed, Thu, Fri, Sat, Sun};
>>> using Days = BitMask<Day>;
>>>
>>> const Days workdays = ~(Day::Sat | Day::Sun);
>>> assert(workdays == Day::Mon | Day::Tue | Day::Wed | Day:Thu | Day::Fry);
>>
>> That's... hard to get right. QFlags deals with this by promoting the
>> result of operator~ to int¹, and allowing int as one of the types to
>> operator&. Trying to comprehend the set of all actual flags is...
>> hard(er). Maybe with introspection it will be possible... but I'm
>> honestly not entirely convinced it wouldn't have corner cases...
>
> There is a huge issue with inverse operator. The following example works as
> expected:
>
> bool contains_weekend(Days days) {
> return (days & (Day::Sat | Day::Sun));
> }
>
> while the next one breaks if inversion was used during function parameter
> calculation:
>
> bool contains_workday(Days days) {
> return (days & ~(Day::Sat | Day::Sun));
> }
Not... quite. Inversion doesn't give you the flags type, it gives you
the integer type (call it a 'mask' type). So you have to bend over
backwards to coerce the result of an inversion back into the flags type.
At that point, you deserve to get bitten :-).
You could build the parameter something like:
auto days = Days::EveryDay & ~(Day::Sun);
...because applying a mask to a Flags still gives a Flags. But you
couldn't do this (without a static_cast):
auto w = contains_workday(~Day::Sun);
In fact, I think there is another reason for things to work like this...
ABI compatibility. Doing it this way, the result of operator~ is wholly
dependent on its inputs, which means it doesn't change if I add a flag,
which means applications compiled against an old library version (the
flags being defined in said library) are fine. If implemented with
reflection, adding a flag becomes a BIC and I now have to recompile the
application after such a change. That doesn't seem to me like a good
thing :-).
> With static reflection propsal it's possible to calculate additional
> numeric template parameter which will contain only bits related to the enum
> items and perform bitwise and with it in the operator~ implementation.
> Promoting to bitmask representation type (int in case of QFlags or extra
> template parameter in my ButMask) do not really solve's this issue. It have
> to be solved in a user code:
>
> bool contains_workday(Days days) {
> // TODO: Do not forget to update line bellow when adding new item to Day
> enum
> days = days & (Day::Mon | Day::Tue | Day::Wed | Day::Thu | Day::Fry |
> Day::Sat | Day::Sun);
> return (days & ~(Day::Sat | Day::Sun));
> }
Two reasons this isn't a problem. One, as above, it is an error to pass
a value of days that has bits not in Day. Two, most people writing code
like this are going to write a mask constant near where the Day enum is
defined so that only one place needs to be changed when adding a flag.
>> (¹ A std implementation should use underlying_type, obviously.)
>
> underlying_type do not fit here. If MyEnum underlying type uint8_t and it
> contains 10 items then BitMask<MyEnum> has 2^10 possible values while
> underlying_type can cover only 2^8.
No. If the enum has underlying type uint8_t and has 10 items, some of
those must either be duplicates or combinations, as there are only 8
bits available!
The flags helper class (at least, not QFlags, and not any version I
would be okay with) uses the underlying values as operands in binary
arithmetic. It is NOT a fancy std::[unordered_]set as you seem to be
thinking.
--
Matthew