[Flags] attribute for enum class bitfields?

202 views
Skip to first unread message

snk_kid

unread,
Jun 27, 2016, 9:19:49 AM6/27/16
to ISO C++ Standard - Future Proposals
Hi, it would be nice to have a [Flags] attribute for enum classes like .NET which would either allow using bit-wise operators without casting to underlying type or generates bit-wise operator overloads.



Matthew Woehlke

unread,
Jun 27, 2016, 10:20:20 AM6/27/16
to std-pr...@isocpp.org
Somewhere on my "TO DO" list is to propose a std::flags modeled after
QFlags. This would be a library solution rather than a language
solution, which is usually preferred, and also allows keeping separate
types for "a set of flags" and "exactly one flag".

The main problem is how to provide the operators for the enum type
itself. Usual solutions involve using a macro, to which some folks take
a very strong disliking.

--
Matthew

Anthony Williams

unread,
Jun 27, 2016, 10:23:48 AM6/27/16
to std-pr...@isocpp.org
You don't need to use macros for that. You can specialize a traits
class, or use a constexpr function. I wrote about this last year:

https://www.justsoftwaresolutions.co.uk/cplusplus/using-enum-classes-as-bitfields.html

Anthony
--
Author of C++ Concurrency in Action http://www.stdthread.co.uk/book/
just::thread C++11 thread library http://www.stdthread.co.uk
Just Software Solutions Ltd http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

Matthew Woehlke

unread,
Jun 27, 2016, 10:37:26 AM6/27/16
to std-pr...@isocpp.org
On 2016-06-27 10:23, Anthony Williams wrote:
> On 27/06/16 15:20, Matthew Woehlke wrote:
>> On 2016-06-27 09:19, snk_kid wrote:
>>> Hi, it would be nice to have a [Flags] attribute for enum classes like .NET
>>> which would either allow using bit-wise operators without casting to
>>> underlying type or generates bit-wise operator overloads.
>>
>> Somewhere on my "TO DO" list is to propose a std::flags modeled after
>> QFlags. This would be a library solution rather than a language
>> solution, which is usually preferred, and also allows keeping separate
>> types for "a set of flags" and "exactly one flag".
>>
>> The main problem is how to provide the operators for the enum type
>> itself. Usual solutions involve using a macro, to which some folks take
>> a very strong disliking.
>
> You don't need to use macros for that. You can specialize a traits
> class, or use a constexpr function. I wrote about this last year:
>
> https://www.justsoftwaresolutions.co.uk/cplusplus/using-enum-classes-as-bitfields.html

That's hardly an improvement. Unless I am missing something, your
example involves writing *more* code vs. using a macro, and also loses
the feature of single-flag and flag-set being separate types.

--
Matthew

Anthony Williams

unread,
Jun 27, 2016, 11:37:18 AM6/27/16
to std-pr...@isocpp.org
enum class SingleFlag{ flag1,flag2};

SingleFlag x=SingleFlag::flag1;
//SingleFlag x2=SingleFlag::flag1|SingleFlag::flag2; // fails to compile

enum class FlagSet{ option1=1,option2=2,option3=4};

template<>
struct enable_bitmask_operators<FlagSet>{
static constexpr bool enable=true;
};
FlagSet y=FlagSet::option1|FlagSet::option2;


It's marginally more than a macro, but not much.

With a bit more work in the generic code, you can make it use a
constexpr function instead of a traits specialization, which is then
even less work for the user:

constexpr bool is_bitmask_enum(FlagSet){return true;}

Matthew Woehlke

unread,
Jun 27, 2016, 12:10:15 PM6/27/16
to std-pr...@isocpp.org
On 2016-06-27 11:37, Anthony Williams wrote:
> On 27/06/16 15:36, Matthew Woehlke wrote:
>> On 2016-06-27 10:23, Anthony Williams wrote:
>>> You don't need to use macros for that. You can specialize a traits
>>> class, or use a constexpr function. I wrote about this last year:
>>>
>>> https://www.justsoftwaresolutions.co.uk/cplusplus/using-enum-classes-as-bitfields.html
>>
>> That's hardly an improvement. Unless I am missing something, your
>> example involves writing *more* code vs. using a macro, and also loses
>> the feature of single-flag and flag-set being separate types.
>
> enum class SingleFlag{ flag1,flag2};
>
> SingleFlag x=SingleFlag::flag1;
> //SingleFlag x2=SingleFlag::flag1|SingleFlag::flag2; // fails to compile
>
> enum class FlagSet{ option1=1,option2=2,option3=4};
>
> template<>
> struct enable_bitmask_operators<FlagSet>{
> static constexpr bool enable=true;
> };
> FlagSet y=FlagSet::option1|FlagSet::option2;

That's... lovely. And completely misses the issue.

Given an enum (call it `F`) of flags, how do I write one function that
takes exactly one flag of `F`, and one function that takes a collection
of one or more flags of `F`?

QFlags can do this. The std::flags implementation I derived from QFlags
can do it. I don't see how your implementation can. You would have to write:

enum Flag { ... }; // same for any implementation

using Flags = std::flags<flag>; // Reasonable enough

template<>
struct enable_bitmask_operators<Flags>{
static constexpr bool enable=true;
}; // Ugh, long and repetitive (read: error-prone)

Honestly, I would strongly prefer a macro over that four-line mess. One
benefit of a macro is that it is an *error* if I typo it, *at* the site
where I make the typo... not a hair-pulling-out why-is-this-happening
error at some far removed location where I'm trying to use it.

Better would be if declaring a type-alias of std::flags somehow did this
"magically". But I have not come up with any reasonable way for that to
happen. (SFINAE + attribute introspection might make it possible, but I
can hear people screaming about that already...)

--
Matthew

Jonathan Müller

unread,
Jun 27, 2016, 1:39:31 PM6/27/16
to std-pr...@isocpp.org
On Mon, Jun 27, 2016 at 6:10 PM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
That's... lovely. And completely misses the issue.

Given an enum (call it `F`) of flags, how do I write one function that
takes exactly one flag of `F`, and one function that takes a collection
of one or more flags of `F`?

Why do you need two different types for that?
 
QFlags can do this. The std::flags implementation I derived from QFlags
can do it. I don't see how your implementation can. You would have to write:

  enum Flag { ... }; // same for any implementation

  using Flags = std::flags<flag>; // Reasonable enough

  template<>
  struct enable_bitmask_operators<Flags>{
      static constexpr bool enable=true;
  }; // Ugh, long and repetitive (read: error-prone)

Honestly, I would strongly prefer a macro over that four-line mess. One
benefit of a macro is that it is an *error* if I typo it, *at* the site
where I make the typo... not a hair-pulling-out why-is-this-happening
error at some far removed location where I'm trying to use it.

Better would be if declaring a type-alias of std::flags somehow did this
"magically". But I have not come up with any reasonable way for that to
happen. (SFINAE + attribute introspection might make it possible, but I
can hear people screaming about that already...)

What about:

  enum FlagImpl { ... };

  using Flag = std::flag<FlagImpl>;

  using Flags = std::flags<FlagImpl>;

Or am I missing something? 

Nicol Bolas

unread,
Jun 27, 2016, 1:43:08 PM6/27/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

Is there some reason not to put "that four-line mess" in a macro:

ENABLE_FLAGS(flag_type, enum_type) \
using flag_type = std::flags<enum_type>;\
template<>\
struct enable_bitmask_operators<flag_type>{ static constexpr bool enable=true; };

That way, the heavy lifting work is being done by genuine C++ code, and someone can reasonably easily read and understand what the macro is doing.

My point is that you ship the C++ code; you package it in a macro as you see fit.

Nicol Bolas

unread,
Jun 27, 2016, 1:46:49 PM6/27/16
to ISO C++ Standard - Future Proposals
On Monday, June 27, 2016 at 1:39:31 PM UTC-4, Jonathan Müller wrote:
On Mon, Jun 27, 2016 at 6:10 PM, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
That's... lovely. And completely misses the issue.

Given an enum (call it `F`) of flags, how do I write one function that
takes exactly one flag of `F`, and one function that takes a collection
of one or more flags of `F`?

Why do you need two different types for that?

Because they are two different use cases of flag enumerations, and you want compiler support to help minimize the chance of someone doing the wrong thing. If you have a function that asks if a specific flag is set, you want the compiler to fail if they try to do an `or` between two different flags. The function asks if a specific flag is set, not if a group of flags is set.

Matthew Woehlke

unread,
Jun 27, 2016, 3:24:54 PM6/27/16
to std-pr...@isocpp.org
On 2016-06-27 13:39, Jonathan Müller wrote:
> On Mon, Jun 27, 2016 at 6:10 PM, Matthew Woehlke <mwoehlk...@gmail.com>
> wrote:
>
>> That's... lovely. And completely misses the issue.
>>
>> Given an enum (call it `F`) of flags, how do I write one function that
>> takes exactly one flag of `F`, and one function that takes a collection
>> of one or more flags of `F`?
>
> Why do you need two different types for that?

...because I might have a function that needs to take exactly one flag?

For example, a QDockWidget has supported areas (flags) and current area
(flag). It can't concurrently be in multiple areas, so a method to set
the current area taking more than one flag would be an error, and one
returning the current area is better API if it encodes that the result
will be one flag, not a collection of flags.

>> Better would be if declaring a type-alias of std::flags somehow did this
>> "magically". But I have not come up with any reasonable way for that to
>> happen. (SFINAE + attribute introspection might make it possible, but I
>> can hear people screaming about that already...)
>
> What about:
>
> enum FlagImpl { ... };
>
> using Flag = std::flag<FlagImpl>;
>
> using Flags = std::flags<FlagImpl>;
>
> Or am I missing something?

How does that enable Boolean operators?

operator|(FlagImpl, FlagImpl) -> Flags
operator|(Flags, FlagImpl) -> Flags
operator|(FlagImpl, Flags) -> Flags

...etc.?

--
Matthew

Matthew Woehlke

unread,
Jun 27, 2016, 3:30:28 PM6/27/16
to std-pr...@isocpp.org
On 2016-06-27 13:43, Nicol Bolas wrote:
> On Monday, June 27, 2016 at 12:10:15 PM UTC-4, Matthew Woehlke wrote:
>> template<>
>> struct enable_bitmask_operators<Flags>{
>> static constexpr bool enable=true;
>> }; // Ugh, long and repetitive (read: error-prone)
>>
>> Honestly, I would strongly prefer a macro over that four-line mess.
>
> Is there some reason not to put "that four-line mess" in a macro:

...certain people absolutely loathe macros? ;-)

MHO is that any flags implementation requiring more than two additional
lines of code (the flag type alias, and one to enable flags) is
suboptimal and grounds to keep looking for a better way. Ideally,
declaring the type alias would be sufficient to enable operators, but
that's probably not possible without a very special (read: very limited
use case) language rule or abuse such as SFINAE on attribute introspection.

Note that I'm counting lines that the library author has to write. Those
still have to be reimplemented if multiple independent projects want to
use the feature.

That said, maybe folks would be less opposed to a (standard) macro that
just specializes a type trait than one that actually declares operators
(a la Q_DECLARE_OPERATORS_FOR_FLAGS).

--
Matthew

Jonathan Müller

unread,
Jun 27, 2016, 4:35:18 PM6/27/16
to std-pr...@isocpp.org
On 27.06.2016 21:24, Matthew Woehlke wrote:
> On 2016-06-27 13:39, Jonathan Müller wrote:
>> Why do you need two different types for that?
>
> ...because I might have a function that needs to take exactly one flag?
>
> For example, a QDockWidget has supported areas (flags) and current area
> (flag). It can't concurrently be in multiple areas, so a method to set
> the current area taking more than one flag would be an error, and one
> returning the current area is better API if it encodes that the result
> will be one flag, not a collection of flags.

Okay, I'll get that.

>> What about:
>>
>> enum FlagImpl { ... };
>>
>> using Flag = std::flag<FlagImpl>;
>>
>> using Flags = std::flags<FlagImpl>;
>>
>> Or am I missing something?
>
> How does that enable Boolean operators?
>
> operator|(FlagImpl, FlagImpl) -> Flags
> operator|(Flags, FlagImpl) -> Flags
> operator|(FlagImpl, Flags) -> Flags
>
> ...etc.?
>

Yes, I thought about something like that.
You still need to cast one argument though.

So overall there are the following options:

* manual overload bitwise operators

* manual overload through macro

* automatic generation via traits or similar

* semi-automatic generation by casting one argument

* language feature

Sergey Vidyuk

unread,
Jun 28, 2016, 6:27:54 AM6/28/16
to ISO C++ Standard - Future Proposals


вторник, 28 июня 2016 г., 2:35:18 UTC+6 пользователь Jonathan Müller написал:

Take a look on implementation here:  http://my-it-experiments.blogspot.ru/2015/11/blog-post.html (article is in russian but the code is self documented). The only thing which one have to do manually is operator|(FlagImpl, FlagImpl) which can be written once with trait enabled template suggested by Anthony Williams. 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);

snk_kid

unread,
Jun 28, 2016, 7:32:51 AM6/28/16
to ISO C++ Standard - Future Proposals
Are those enum members suppose to represent bit positions because they don't look like bit-masks as you're not setting the values for them. I don't like the fact that you have 2 types instead of just one.

None of the library solutions look ideal too me, why are people against having a standardized attribute? it seems like it should be a trivial thing for compiler vendors to implement. I don't believe in this mantra that every new feature should (preferably) be or start off as a library solution.

Sergey Vidyuk

unread,
Jun 28, 2016, 7:51:03 AM6/28/16
to ISO C++ Standard - Future Proposals


вторник, 28 июня 2016 г., 17:32:51 UTC+6 пользователь snk_kid написал:
Are those enum members suppose to represent bit positions because they don't look like bit-masks as you're not setting the values for them. I don't like the fact that you have 2 types instead of just one.

I agree with Matthew Woehlke that 2 types are needed here. In most of the cases I need to use enum as combinable flags I also have special cases when I need to work with a single flag. Having 2 types allows one to solve both kind of tasks. If you never need to distinguish single flag and set of flags you are using Flags type and don't care about Flag type at all but when you need to distinguish them "1 type"-solution just don't fit you task at all.
 

None of the library solutions look ideal too me, why are people against having a standardized attribute? it seems like it should be a trivial thing for compiler vendors to implement. I don't believe in this mantra that every new feature should (preferably) be or start off as a library solution.


Because attributes are intendend to be hint's for a compiler which do not change code semantic and can be ignored if compiler do not support this particular attribute without breaking aplication behaviour. In your proposal adding attribute to enum adds extra operations to this enum which are not supported otherwise.

One more strong objection: adding attributes to majically solve one particular task, then adding one more attribute to solve another task and so on and so on is the worst way to evolve the language. Instead of standartising one simple attribute to solve one simple task one have to standartize some syntax extention which will allow to add user defined attribute and use them to solve huge set of task including the task discussed here.

Matthew Woehlke

unread,
Jun 28, 2016, 10:45:37 AM6/28/16
to std-pr...@isocpp.org
On 2016-06-28 06:27, Sergey Vidyuk wrote:
> вторник, 28 июня 2016 г., 2:35:18 UTC+6 пользователь Jonathan Müller
> написал:
>> On 27.06.2016 21:24, Matthew Woehlke wrote:
>>> On 2016-06-27 13:39, Jonathan Müller wrote:
>>>> What about:
>>>>
>>>> enum FlagImpl { ... };
>>>>
>>>> using Flag = std::flag<FlagImpl>;
>>>>
>>>> using Flags = std::flags<FlagImpl>;
>>>>
>>>> Or am I missing something?
>>>
>>> How does that enable Boolean operators?
>>>
>>> operator|(FlagImpl, FlagImpl) -> Flags
>>> operator|(Flags, FlagImpl) -> Flags
>>> operator|(FlagImpl, Flags) -> Flags
>>>
>>> ...etc.?
>>
>> Yes, I thought about something like that.
>> You still need to cast one argument though.
>>
>> So overall there are the following options:
>>
>> * manual overload bitwise operators
>>
>> * manual overload through macro
>>
>> * automatic generation via traits or similar
>>
>> * semi-automatic generation by casting one argument
>>
>> * language feature
>>
>> Or am I missing something?

Sounds right.

> Take a look on implementation here:
> http://my-it-experiments.blogspot.ru/2015/11/blog-post.html (article is in
> russian but the code is self documented). The only thing which one have to
> do manually is operator|(FlagImpl, FlagImpl) which can be written once with
> trait enabled template suggested by Anthony Williams.

That doesn't seem to add anything that hasn't already been discussed.

> 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...

(¹ A std implementation should use underlying_type, obviously.)

--
Matthew

Matthew Woehlke

unread,
Jun 28, 2016, 10:50:07 AM6/28/16
to std-pr...@isocpp.org
On 2016-06-28 07:32, snk_kid wrote:
> None of the library solutions look ideal too me, why are people against
> having a standardized attribute? it seems like it should be a trivial thing
> for compiler vendors to implement. I don't believe in this mantra that
> every new feature should (preferably) be or start off as a library solution.

As Sergey notes, an attribute is pretty much a non-starter. As any form
of language feature, well, the bar for language features is higher, and
this is a fairly esoteric (i.e. limited) use case. We generally like
language features to be widely useful. That's less important for library
features.

A language feature that enabled this *and* a bunch of other awesome
things would be much more palatable.

--
Matthew

Sergey Vidyuk

unread,
Jun 29, 2016, 12:30:45 AM6/29/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


вторник, 28 июня 2016 г., 20:45:37 UTC+6 пользователь Matthew Woehlke написал:

Yes it's just implementation example which I use when need to mix enum into bitmasks.
 

> 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));
}

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));
}

And this solution is not reliable since it requires to search for all todo comments when extending the enum.
 
(¹ 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. That's why I have additional template parameter T in my implementation. With static reflection it's possible co choose better default type then just uint64_t. From my personal point of view static reflection is the only blocker to implement this class properly but it will be great to have it as std::flags someday.

Edward Catmur

unread,
Jun 29, 2016, 7:19:51 AM6/29/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
For an enumeration without fixed underlying type, it should be possible to infer its value range ([dcl.enum]/8) by exploiting [class.bit]/4 and [expr.const]/2, since an equality operation on an enumeration bitmask has unspecified result if the bitmask is too small to cover all the values of the enumeration, and so a conditional-expression containing that equality operation is not core constant. 

Unfortunately I haven't found any compiler that gets this correct. It would be reasonable IMO to request a type trait giving the value range of an enumeration, if we don't get static reflection first.
 
(¹ 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. That's why I have additional template parameter T in my implementation. With static reflection it's possible co choose better default type then just uint64_t. From my personal point of view static reflection is the only blocker to implement this class properly but it will be great to have it as std::flags someday.

Under the assumption that the enumerators of MyEnum have values 1, 2, 4, etc. then MyEnum's underlying type would have to have at least 10 bits.

Sergey Vidyuk

unread,
Jun 29, 2016, 9:05:45 AM6/29/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


среда, 29 июня 2016 г., 17:19:51 UTC+6 пользователь Edward Catmur написал:
 
My BitMask implementation uses enum with default values (0,1,2..N) and perform 'RepresentationIntType(1) << stutic_cast<RepresentationIntType>(enum_item);' internaly so one can write

enum class Perm {Read, Write, Exec};
using Perms = BitMask<Perm>;

instead of

enum class Perm {Read = 1, Write = 2, Exec = 4};
using Perms = BitMask<Perm>;

So I need max enum item value to find unsigned integer type which has at least MaxVal bits length.

Edward Catmur

unread,
Jun 29, 2016, 9:07:17 AM6/29/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
Perhaps allowing enumerations to have base type another enumeration (with enumerators declared at at most one level) could work here? That way the operators could be picked up via ADL on the base type (with CRTP where appropriate).

Use cases:

1. Creating a set type from an existing enumeration, without interfering with the original enum's operators:
template<class E> enum enum_set : E {};
template<class E> enum_set<E> operator|(enum_set<E>, enum_set<E>);
enum E { x = 1, y = 2, z = 4 };
using S = enum_set<E>;
auto s = S::x | S::y; // decltype(s) == S

2. Creating interoperable flag and set enumerations:
template<class E> enum enum_bitwise_operations {};
template<class E> enum_set<E> operator|(enum_bitwise_operations<E>, enum_bitwise_operations<E>);
enum E : enum_bitwise_operations<E> { x = 1, y = 2, z = 4 };
auto s = E::x | E::y; // decltype(s) == enum_set<E>

3. Creating a combined flag and set enumeration:
template<class E> enum enum_combined_set {};
template<class E> enum_combined_set<E> operator|(enum_combined_set<E>, enum_combined_set<E>);
enum E : enum_combined_set<E> { x = 1, y = 2, z = 4 };
auto e = E::x | E::y; // decltype(e) == E


Edward Catmur

unread,
Jun 29, 2016, 9:15:42 AM6/29/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Wednesday, 29 June 2016 14:05:45 UTC+1, Sergey Vidyuk wrote:
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. That's why I have additional template parameter T in my implementation. With static reflection it's possible co choose better default type then just uint64_t. From my personal point of view static reflection is the only blocker to implement this class properly but it will be great to have it as std::flags someday.

Under the assumption that the enumerators of MyEnum have values 1, 2, 4, etc. then MyEnum's underlying type would have to have at least 10 bits.
 
My BitMask implementation uses enum with default values (0,1,2..N) and perform 'RepresentationIntType(1) << stutic_cast<RepresentationIntType>(enum_item);' internaly so one can write

enum class Perm {Read, Write, Exec};
using Perms = BitMask<Perm>;

instead of

enum class Perm {Read = 1, Write = 2, Exec = 4};
using Perms = BitMask<Perm>;

So I need max enum item value to find unsigned integer type which has at least MaxVal bits length.

Knowing the value range would just about be sufficient for your use case, on the (realistic) assumption that bit lengths of integral types are powers of two.

Matthew Woehlke

unread,
Jun 29, 2016, 10:42:34 AM6/29/16
to std-pr...@isocpp.org
On 2016-06-29 09:07, Edward Catmur wrote:
> On Tuesday, 28 June 2016 15:50:07 UTC+1, Matthew Woehlke wrote:
>> On 2016-06-28 07:32, snk_kid wrote:
>>> None of the library solutions look ideal too me, why are people against
>>> having a standardized attribute?
>>
>> As any form of language feature, well, the bar for language
>> features is higher, and this is a fairly esoteric (i.e. limited)
>> use case. We generally like language features to be widely useful.
>>
>> A language feature that enabled this *and* a bunch of other awesome
>> things would be much more palatable.
>
> Perhaps allowing enumerations to have base type another enumeration (with
> enumerators declared at at most one level) could work here? That way the
> operators could be picked up via ADL on the base type (with CRTP where
> appropriate).

Hmm...

> 2. Creating interoperable flag and set enumerations:
> template<class E> enum enum_bitwise_operations {};
> template<class E> enum_set<E> operator|(enum_bitwise_operations<E>,
> enum_bitwise_operations<E>);
> enum E : enum_bitwise_operations<E> { x = 1, y = 2, z = 4 };
> auto s = E::x | E::y; // decltype(s) == enum_set<E>

...yes, I think that would work! And I've wanted enum inheritance for
ages anyway.

Even better, that implementation only needs one operator|, not the three
pairs (Flag, Flags), (Flags, Flag) and (Flags, Flags), because the Flags
type is-a Flag.

A related reason why I want inheritance:

enum class Flag : std::flag_enum<Flag>
{
Flag1 = 1<<0,
Flag2 = 1<<1,
Flag3 = 1<<2,
};
enum class FlagExt : Flag
{
LowFlags = Flag1 | Flag2,
};
using Flags = std::flags<FlagExt>;

That is, the ability to have one enum that has literally just the flags,
but also a second enum inheriting from the first that also has useful
combinations of flags that I can use anywhere a Flags is accepted.

--
Matthew

Matthew Woehlke

unread,
Jun 29, 2016, 10:48:41 AM6/29/16
to std-pr...@isocpp.org
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

Reply all
Reply to author
Forward
0 new messages