enum traits

1,840 views
Skip to first unread message

fmatth...@gmail.com

unread,
Jan 11, 2014, 1:47:48 AM1/11/14
to std-pr...@isocpp.org
Enums are one of my favorite tools for efficently labeling collections, but we need to be able to query more information about them.

I'd like to suggest some form of enum traits. 

template <typename E>
struct enum_traits<E> {
  static_assert(is_enum<E>::value, "Only valid for enums!");
  
  typedef /* underlying type */ value_type;
  static constexpr const value_type min = /* minimum value of enum */
  static constexpr const value_type max = /* maximum value of enum */
  static constexpr const value_type ubound = /* maximum value of enum+1. Not present (does not compile if queried) if max == numeric_limits<value_type>::max(). (Note: this is the most useful value)*/

  static constexpr const size_t count = /* Number of enum tags */
  static constexpr const size_t distinct = /* Number of enum tags with distinct values */ 
  static constexpr const bool is_contiguious = /* true if enum values are contiguous */
  static constexpr const bool is_regular = is_contiguous && min == value_type(0);
  static constexpr const bool has_zero = /* true if zero is an enum value */
  static constexpr const bool has_postive = /* true if at least one enum value is positive */
  static constexpr const bool all_postive = /* true if all enum values are positive */
  static constexpr const bool has_negative = /* true if at least one enum value is negative */
  static constexpr const bool all_negative = /* true if all enum values are negative */

//Utilities to enable foreach, compiler generated
  class iterator : public std::bidirectional_iterator<E> {
    public:
      E operator*();
      //iterator stuff, operator++ goes to next enum value in operator<() order.
  };
  class reverse_iterator;
  struct range {

    iterator begin();
    iterator end();

    reverse_iterator rbegin();
    reverse_iterator rend();
  };

  range<E> loop();
};

//foreach loop over enum values
for(auto e : enum_traits<E>::loop()) {
};

//Create tables keyed by enums, standard library component
template <typename E, typename T, typename CMP = std::equal_to>
class enum_table {
  //static asserts if li.size() != enum_traits<E>::distinct;
  constexpr enum_table(std::initializer_list<T> li);
  constexpr enum_table(const enum_table&) = default;
  constexpr enum_table(enum_table&&) = default;
  constexpr enum_table& operator=(const enum_table&) = default;
  constexpr enum_table& operator=(enum_table&&) = default;

  constexpr const T& operator[E tag] const;
  constexpr bool lookup(const T& value, E& tag) const;
  constexpr bool lookup(T&& value, E& tag) const;
  constexpr size_t size() const;

  class iterator : std::bidirectional_iterator<std::pair<E, const T&>> {};
  class reverse_iterator;
  
  constexpr iterator begin();
  constexpr iterator end();
  constexpr reverse_iterator rbegin();
  constexpr reverse_iterator rend();

  constexpr iterator find(E tag);
  template <typename Comparable>
  constexpr iterator find(Comparable&& c); //lookup using CMP(const T&, C&&) or CMP(const T&, const C&) using perfect forwarding
  constexpr iterator find(T&& value); //lookup using CMP()
};

template <typename E>
using enum_strtable = enum_table<E, const char*, [](const char* l, const char* r){ return strcmp(l, r) == 0; }>;


//Usage
enum Color {
  kRed,
  kGreen,
  kBlue
};

//Could be place in binary .rodata section.
constexpr const enumstr_table<Color> color_tbl = { "Red", "Blue", "Green" };

assert(!strcmp(color_tbl[kRed], "Red"));
assert(color_tbl.find("Red") != color_tbl.end());

I really want this for ubound, and loop(), and runtime string tables but while we're at it, there's a lot of cool stuff we could add.
Rationale here:

Other extensions I'd love to see:
* enum bitmasks as a real type, easy and expressive to declare, strict rules about logical operator comparisons. Only the tags in the enum can use binary logical operations with one another.
* concepts in the enum declaration to enforce restrictions (is_contiguous, min == 0, etc..)


fmatth...@gmail.com

unread,
Jan 11, 2014, 1:50:46 AM1/11/14
to std-pr...@isocpp.org, fmatth...@gmail.com
Or even better, use std::string_view for the enum_strtable.

David Krauss

unread,
Jan 11, 2014, 2:34:05 AM1/11/14
to std-pr...@isocpp.org
On 1/11/14 2:50 PM, fmatth...@gmail.com wrote:
> Or even better, use std::string_view for the enum_strtable.

See recent threads, "Enumerator traits, rev. 2", and "enumeration pack
and __enumerator__ template variable".

An existing Clang prototype including generated strings was mentioned in
the latter thread.

Remotion

unread,
Jan 11, 2014, 9:01:11 AM1/11/14
to std-pr...@isocpp.org, fmatth...@gmail.com
Yes we need enum_traits but not only them.
Here you can find experimental implementation of traits and clang based compiler.
https://bitbucket.org/remotion/c-reflection

Here is also proposal for this.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3815.html

fmatth...@gmail.com

unread,
Jan 11, 2014, 9:59:50 AM1/11/14
to std-pr...@isocpp.org, fmatth...@gmail.com
I think having a built in string table where the strings match the enum tag names exactly is too limiting. I'd like to define my own string mapping and I do this extremely often in my current projects. Even having multiple string mappings such as name and description is incredibly useful. One example is command line arguments but there are many others. Having a generic table of other types would also be useful.

Perhaps this enum table could be a separate proposal. It's more of a library component to extend enums. While its use case is not really related to reflection, its implementation would require reflection.

Andrew Tomazos

unread,
Jan 11, 2014, 11:39:50 PM1/11/14
to std-pr...@isocpp.org, fmatth...@gmail.com
Could you please review N3815 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3815.html) and confirm that you can implement your enum_traits and enum_table class as a pure library feature on top of the three provided type traits, and that the solution would be as efficient as if the compiler did it for you?

If you are unsure or think you have a feature you want that could not be implemented on top of the N3815 type traits as an efficient pure library feature, could you please point it out?

Our current plan is to standardize N3815 in the Library Fundamentals TS as a minmal complete interface that enables library / framework authors to provide all this functionality that you want.

From a review of your enum_traits and enum_table class I believe that this is indeed the case - that both could all be built efficiently as a pure library solution on top of the N3815.  Could you see if you agree.

Thanks,
Andrew.



On Saturday, January 11, 2014 7:47:48 AM UTC+1, fmatth...@gmail.com wrote:

fmatth...@gmail.com

unread,
Jan 12, 2014, 12:16:52 AM1/12/14
to std-pr...@isocpp.org, fmatth...@gmail.com
Sure, is there a working implementation of your proposal? I don't have a lot of free time so it will be slow, but I can take a look. If so, I might like to help work on the library features that come after your proposal. 

The list I made is a general sketch of some of the things I think might be immediatly useful. Many of these I already do manually using crude interfaces now. It will take some time to come up with a solid library design with a correctly chosen and designed features.

Andrew Tomazos

unread,
Jan 12, 2014, 2:01:12 AM1/12/14
to std-pr...@isocpp.org, fmatth...@gmail.com
So, first yes, my co-author Christian has written a reference implementation that contains a superset of the N3815 proposed three property queries.  You will find the link on the bottom of N3815.  This has been forked and extended by Remotion (linked previously in this thread).

But before you go diving into that and compiling custom implementation, the three traits are really simple to understand and to mock up by hand with an ordinary compiler:

First recall the three property queries are:

    enumerator_list_size<E> is the number of enumerators in the enumeration E
    enumerator_value<E,I> is a value of type E that is the I'th enumerator in declared order.
    enumerator_identifier<E,I> is a string literal with the identifier of the I'th enumerator in declared order.

So you can mock them up for experimentation "by hand" without compiler changes as follows:

First declare but don't define the three N3815 primaries...

    template<typename E> struct enumerator_list_size;
    template<typename E, size_t I> struct enumerator_value;
    template<typename E, size_t I> struct enumerator_identifier;

Then for each enumeration type in your test program, for example...

    enum foo
    {
        bar,
        Baz,
        Qux = 42,
        Quux = 42,
    };

...manually define a set of specializations by hand as follows...

    template<> struct enumerator_list_size<foo> { static constexpr size_t value = 4; };

    template<> struct enumerator_value<foo,0> { static constexpr foo value = bar; };
    template<> struct enumerator_value<foo,1> { static constexpr foo value = Baz; };
    template<> struct enumerator_value<foo,2> { static constexpr foo value = Qux; };
    template<> struct enumerator_value<foo,3> { static constexpr foo value = Quux; };

    template<> struct enumerator_identifier<foo,0> { static constexpr char value[4] = u8"bar"; };
    template<> struct enumerator_identifier<foo,1> { static constexpr char value[4] = u8"Baz"; };
    template<> struct enumerator_identifier<foo,2> { static constexpr char value[4] = u8"Qux"; };
    template<> struct enumerator_identifier<foo,3> { static constexpr char value[5] = u8"Quux"; };

    constexpr char enumerator_identifier<foo,0>::value[];
    constexpr char enumerator_identifier<foo,1>::value[];
    constexpr char enumerator_identifier<foo,2>::value[];
    constexpr char enumerator_identifier<foo,3>::value[];

(The effect of N3815 is that these hand-written specializations will be done automatically for you by the compiler when you use them.)

Here is a complete self-contained compiling program with the above integrated together you can copy and paste plus some tests:

    #include <cassert>
    #include <cstddef>
    #include <string.h>

    // N3815 primaries
    template<typename E> struct enumerator_list_size;
    template<typename E, size_t I> struct enumerator_value;
    template<typename E, size_t I> struct enumerator_identifier;

    // your enumeration
    enum foo
    {
        bar,
        Baz,
        Qux = 42,
        Quux = 42,
    };

    // define these specializations by hand:
    template<> struct enumerator_list_size<foo> { static constexpr size_t value = 4; };

    template<> struct enumerator_value<foo,0> { static constexpr foo value = bar; };
    template<> struct enumerator_value<foo,1> { static constexpr foo value = Baz; };
    template<> struct enumerator_value<foo,2> { static constexpr foo value = Qux; };
    template<> struct enumerator_value<foo,3> { static constexpr foo value = Quux; };

    template<> struct enumerator_identifier<foo,0> { static constexpr char value[4] = u8"bar"; };
    template<> struct enumerator_identifier<foo,1> { static constexpr char value[4] = u8"Baz"; };
    template<> struct enumerator_identifier<foo,2> { static constexpr char value[4] = u8"Qux"; };
    template<> struct enumerator_identifier<foo,3> { static constexpr char value[5] = u8"Quux"; };

    constexpr char enumerator_identifier<foo,0>::value[];
    constexpr char enumerator_identifier<foo,1>::value[];
    constexpr char enumerator_identifier<foo,2>::value[];
    constexpr char enumerator_identifier<foo,3>::value[];

    // test they work:
    int main()
    {
        static_assert(enumerator_list_size<foo>::value == 4, "");

        assert(strcmp(enumerator_identifier<foo,0>::value, u8"bar") == 0);
        assert(strcmp(enumerator_identifier<foo,1>::value, u8"Baz") == 0);
        assert(strcmp(enumerator_identifier<foo,2>::value, u8"Qux") == 0);
        assert(strcmp(enumerator_identifier<foo,3>::value, u8"Quux") == 0);
       
        static_assert(enumerator_value<foo,0>::value == bar, "");
        static_assert(enumerator_value<foo,1>::value == Baz, "");
        static_assert(enumerator_value<foo,2>::value == Qux, "");
        static_assert(enumerator_value<foo,3>::value == Quux, "");
       
        constexpr const char* str = enumerator_identifier<foo,0>::value;
        static_assert(str[0] == 'b', "");
        static_assert(str[1] == 'a', "");
        static_assert(str[2] == 'r', "");
        static_assert(str[3] == '\0', "");
    }

Enjoy,
Andrew.

inkwizyt...@gmail.com

unread,
Jan 12, 2014, 10:41:30 AM1/12/14
to std-pr...@isocpp.org, fmatth...@gmail.com
Would be possible to create similar functionality for class? If this enum approach is possible to implement by every compiler, creating version for class should not be impossible.
This would be more complex because class have private, static, unions, fields, base class, sub class and functions, but if we can made interface for one thing then creating for rest wouldnt be harder.

Ville Voutilainen

unread,
Jan 12, 2014, 10:56:52 AM1/12/14
to std-pr...@isocpp.org
On 12 January 2014 17:41, <inkwizyt...@gmail.com> wrote:
> Would be possible to create similar functionality for class? If this enum
> approach is possible to implement by every compiler, creating version for
> class should not be impossible.
> This would be more complex because class have private, static, unions,
> fields, base class, sub class and functions, but if we can made interface
> for one thing then creating for rest wouldnt be harder.


Short answer: yes, it's possible.

Longer answer: What SG7 (the Reclection Study Group) is more or less doing
is allowing things that an implementation knows, but the standard does not yet
specify, to be exposed to programmers in a hopefully portable fashion. This
likely includes information about which members a class has and where
they are. ;)

fmatth...@gmail.com

unread,
Jan 12, 2014, 12:00:17 PM1/12/14
to std-pr...@isocpp.org, fmatth...@gmail.com, inkwizyt...@gmail.com
Check out the work being done for SG7. 


This thread is specifically about enums.

Andrew Tomazos

unread,
Jan 12, 2014, 8:08:45 PM1/12/14
to std-pr...@isocpp.org, fmatth...@gmail.com, inkwizyt...@gmail.com
On Sunday, January 12, 2014 4:41:30 PM UTC+1, inkwizyt...@gmail.com wrote:
Would be possible to create similar functionality for class? If this enum approach is possible to implement by every compiler, creating version for class should not be impossible.
 
If I can fit a mouse in my pocket, I must be able to fit an elephant too, because they are both animals.  Also known as a hasty generalization.

Enumeration types are just a multiset of named integral constants where the names are always identifiers.  We will support complete efficient reflection of enumeration types with the three N3815 type traits as a tiny extension to that existing library.  The additional standard wording to specify them was only 50 words.

This would be more complex because class have private, static, unions, fields, base class, sub class and functions, but if we can made interface for one thing then creating for rest wouldnt be harder.

You've massively understated the difference in complexity between enumeration types and class types.

Enumeration types are specified by 4 pages in the standard (7.2)

Class types are specified by 69 pages (clauses 9, 10, 11, 12) even if you handwaive the fact that they can contain member templates (an additional 74 pages).

Having said that the reference implementation of N3815 does also include a bunch of additional intrinsics to perform some forms of class type reflection, but nothing we are proposing for standardization.

On top of the sheer difficulty of designing a complete standard schema for class types that the compiler should expose, we also have the problem that the C++ language isn't there yet in being able to handle the complexity of such a schema at compile-time.  At compile-time you have to model the schema using some combination of literal types, constant expressions, constexpr functions and template-metaprogramming.  The type traits approach chokes on the complexity at this point.  For a sane approach we really need basic primitives like dynamic memory and polymorphic types like the compiler itself uses internally to describe the model, but these are not yet available during translation in C++.

I do have some rough designs for a larger extensible C++ reflection framework that would include class types.  It works with opaque handles and a couple of new core language operators called reflect and reify, but it would be something for C++17 at the earliest if it works out at all.

Matthew Woehlke

unread,
Jan 13, 2014, 1:13:37 PM1/13/14
to std-pr...@isocpp.org
On 2014-01-11 01:47, fmatth...@gmail.com wrote:
> Enums are one of my favorite tools for efficently labeling collections, but
> we need to be able to query more information about them.
>
> I'd like to suggest some form of enum traits.
>
> template <typename E>
> struct enum_traits<E> {
> static_assert(is_enum<E>::value, "Only valid for enums!");
>
> typedef /* underlying type */ value_type;
> static constexpr const value_type min = /* minimum value of enum */
> static constexpr const value_type max = /* maximum value of enum */
> static constexpr const value_type ubound = /* maximum value of enum+1.
> Not present (does not compile if queried) if max ==
> numeric_limits<value_type>::max(). (Note: this is the most useful value)*/
> static constexpr const size_t count = /* Number of enum tags */
> static constexpr const size_t distinct = /* Number of enum tags with
> distinct values */

Referring to Andrew's comment, I think most of these that I've seen so
far and following can be implemented with N3815 and the relaxed
constexpr rules, but this one might be hard. (I'm not sure how crucial
it is, however; the bound ones are probably most important... also
count, but that one is obviously trivial with N3815.)

> static constexpr const bool is_contiguious = /* true if enum values are
> contiguous */
> static constexpr const bool is_regular = is_contiguous && min ==
> value_type(0);
> static constexpr const bool has_zero = /* true if zero is an enum value */
> static constexpr const bool has_postive = /* true if at least one enum
> value is positive */
> static constexpr const bool all_postive = /* true if all enum values are
> positive */

This feels like 'all_nonnegative' is missing...

> static constexpr const bool has_negative = /* true if at least one enum
> value is negative */
> static constexpr const bool all_negative = /* true if all enum values are
> negative */
>
> //Utilities to enable foreach, compiler generated
> class iterator : public std::bidirectional_iterator<E> {
> public:
> E operator*();
> //iterator stuff, operator++ goes to next enum value in operator<()
> order.
> };
> class reverse_iterator;
> struct range {
>
> iterator begin();
> iterator end();
>
> reverse_iterator rbegin();
> reverse_iterator rend();
> };
>
> range<E> loop();
> };
>
> //foreach loop over enum values
> for(auto e : enum_traits<E>::loop()) {
> };

I wonder how hard to implement the iterators are...


I would name 'loop' something like 'values' instead. That said, I would
really love to just be able to say:

enum Enum
{
...
};
for (auto e : Enum)
{
...
}

...without the extra enum_traits::loop bits. (Actually, if it were/is(?)
possible to specialize std::begin() on an enum type, with the above
and/or N3815 this should already be possible?)


And actually, another thing I would really like to be able to do (and
yes, this *does* happen in real code) is:

for (auto e : std::range(Enum::A, Enum::B, std::range::closed))

(Oh, look; a case for 'inline enum class' :-).)

> Other extensions I'd love to see:
> * enum bitmasks as a real type, easy and expressive to declare

Yes, please!

For API binary stability, there still needs to be a way to specify exact
values. Probably the 'old style' should still be allowed, but it might
greatly simplify matters to allow e.g. 'foo = :5 // equivalent to foo =
1<<5' (assumes ':0' => 0x1). The main thing that's needed is a way to
say that the next value is automatically the highest bit of (2*prev)
rather than (prev+1). Unfortunately I don't have a suggestion for that
syntax.

I could also see allowing 'foo = :2..5' => 0b111100 for ease of
declaring mask values.

> strict
> rules about logical operator comparisons. Only the tags in the enum can use
> binary logical operations with one another.

This already exists, and has since quite some time; see
http://qt-project.org/doc/qt-4.8/qflags.html (dates back even to Qt3 I
think). I'd love to see a std::flags standardized, however. This
shouldn't need language/compiler changes, though (as various parts of
the above might), just an addition to STL.

Actually, I have an implementation of this if anyone is interested?

--
Matthew

Remotion

unread,
Jan 13, 2014, 2:47:10 PM1/13/14
to std-pr...@isocpp.org, fmatth...@gmail.com, inkwizyt...@gmail.com
IMHO it is possible but much much more complicated as for enums.
This is exactly what I have tried to do here:
https://bitbucket.org/remotion/c-reflection
Right now there are
field_trait to access member variable of the class.
method_traits to access (some) member function of the class.
Constructors and destructor are visible in current implementation.
Also operators and conversion should be visible too.
Static function are not visible, but I think this is OK.
Template functions are not visible and this is one of the problems.
Function that are available by using from base calls are not visible too...

There are also fried_traits to access friend functions and classes.

But there are still a lot of problem with this approach, and right now I am not sure that it is possible to solve most of them only using 'trait' way.

Namespaces for example are not types so there is no direct way to access them.
There are also a lot of totally different declaration in namespace.
It is also possible to redeclare (extend) namespace any time at any place.

 
On Sunday, January 12, 2014 4:41:30 PM UTC+1, inkwizyt...@gmail.com wrote:

inkwizyt...@gmail.com

unread,
Jan 13, 2014, 4:04:39 PM1/13/14
to std-pr...@isocpp.org, fmatth...@gmail.com, inkwizyt...@gmail.com
Mouse have "variadic" size :) Its not uncommon to have enum that have more than 200 values (e.g. keyboard mappings), this will require lot more from compiler reflection than trivial struct like `struct Foo{};`.
Some previous proposition used `tuple` to store information. That cant handle easily lot of data.

I dont think that we need complex reflection that will handle all possible user case and properties of class. We could start form small subset that isnt more complex than enum case. And after is is implements by major compilers we could expand it further. I think first goal should be functionality that allow automatic serialization of POD type to text format like JSON.

Something like Remotion proposition is close to that goal.

fmatth...@gmail.com

unread,
Jan 13, 2014, 9:52:37 PM1/13/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net


On Monday, January 13, 2014 1:13:37 PM UTC-5, Matthew Woehlke wrote:
On 2014-01-11 01:47, fmatth...@gmail.com wrote:
> Enums are one of my favorite tools for efficently labeling collections, but
> we need to be able to query more information about them.
>
> I'd like to suggest some form of enum traits.
>
> template <typename E>
> struct enum_traits<E> {
>    static_assert(is_enum<E>::value, "Only valid for enums!");
>
>    typedef /* underlying type */ value_type;
>    static constexpr const value_type min = /* minimum value of enum */
>    static constexpr const value_type max = /* maximum value of enum */
>    static constexpr const value_type ubound = /* maximum value of enum+1.
> Not present (does not compile if queried) if max ==
> numeric_limits<value_type>::max(). (Note: this is the most useful value)*/
>    static constexpr const size_t count = /* Number of enum tags */
>    static constexpr const size_t distinct = /* Number of enum tags with
> distinct values */

Referring to Andrew's comment, I think most of these that I've seen so
far and following can be implemented with N3815 and the relaxed
constexpr rules, but this one might be hard. (I'm not sure how crucial
it is, however; the bound ones are probably most important... also
count, but that one is obviously trivial with N3815.)

For iteration, you'd want to only iterate over the distinct values. That could require some trickery to get right, which could be optimized away if distinct == true. I need to sit down and think about implementations though.

>    static constexpr const bool is_contiguious = /* true if enum values are
> contiguous */
>    static constexpr const bool is_regular = is_contiguous && min ==
> value_type(0);
>    static constexpr const bool has_zero = /* true if zero is an enum value */
>    static constexpr const bool has_postive = /* true if at least one enum
> value is positive */
>    static constexpr const bool all_postive = /* true if all enum values are
> positive */

This feels like 'all_nonnegative' is missing...

Yes of course, this was just a general idea of what could be done. I actually don't know if all_positive and friends are actually useful. I certainly don't need them for anything.

>    static constexpr const bool has_negative = /* true if at least one enum
> value is negative */
>    static constexpr const bool all_negative = /* true if all enum values are
> negative */
>
> //Utilities to enable foreach, compiler generated
>    class iterator : public std::bidirectional_iterator<E> {
>      public:
>        E operator*();
>        //iterator stuff, operator++ goes to next enum value in operator<()
> order.
>    };
>    class reverse_iterator;
>    struct range {
>
>      iterator begin();
>      iterator end();
>
>      reverse_iterator rbegin();
>      reverse_iterator rend();
>    };
>
>    range<E> loop();
> };
>
> //foreach loop over enum values
> for(auto e : enum_traits<E>::loop()) {
> };

I wonder how hard to implement the iterators are...

Second to having enum_strtable, this feature is crucial to me. I use enums a lot in C++ and iterating over the values is something I've needed to do many times. right now its just the simple for loop with a cast. It would be nice to have this feature provided by the standard library and be nice and type safe.

I would name 'loop' something like 'values' instead. That said, I would
really love to just be able to say:

enum Enum
{
   ...
};
for (auto e : Enum)
{
   ...
}

Enum is a type so this is inventing a whole new syntax (which I am not terribly opposed to, less typing is always good). I think its more sensible to just create a temporary object and iterate over that, letting the compiler optimize away the temporary object and just give you a loop.

Something like this:

template <typename E>
class enum_range {};

for(auto e: enum_range<Enum>()) { }

if enum_traits<Enum>::is_contiguous == true, the goal is that this should optimize down to a simple integer for loop. If is_contiguous == false, then the implementation could use a simple lookup table:

enum Bar {
A = 1,
B = 5,
C = 8,
D = 5
};

for(auto e: enum_range<Bar>()) {}

constexpr int table[] = { 1, 5, 8 }; //Only 3 elements, since D == B
for(int i = 0; i < sizeof(table)/sizeof(*table); ++i) {
  Bar b = Bar(table[i]);
}

 

...without the extra enum_traits::loop bits. (Actually, if it were/is(?)
possible to specialize std::begin() on an enum type, with the above
and/or N3815 this should already be possible?)

I'm not sure having begin()/end() on the enum type itself actually makes sense. An enum object is just one tagged object, not the whole collection

Color color = kRed;
for(auto c: color) { /*???*/ } 


And actually, another thing I would really like to be able to do (and
yes, this *does* happen in real code) is:

for (auto e : std::range(Enum::A, Enum::B, std::range::closed))
I haven't studied the ranges proposal, but from what I think this is supposed to do it looks like a nice feature.
 

(Oh, look; a case for 'inline enum class' :-).)

> Other extensions I'd love to see:
> * enum bitmasks as a real type, easy and expressive to declare

Yes, please!

For API binary stability, there still needs to be a way to specify exact
values. Probably the 'old style' should still be allowed, but it might
greatly simplify matters to allow e.g. 'foo = :5 // equivalent to foo =
1<<5' (assumes ':0' => 0x1). The main thing that's needed is a way to
say that the next value is automatically the highest bit of (2*prev)
rather than (prev+1). Unfortunately I don't have a suggestion for that
syntax.

I could also see allowing 'foo = :2..5' => 0b111100 for ease of
declaring mask values.

I'm thinking more something like this:
enum Flags : some_syntax_that_makes_this_a_bitmask_enum {
  f1, //Defaults to 1 << 1
  f2, // 1 << 2
  f3, // 1 << 3
  f4, // 1 << 4
  f5 = 8, //1 << 8
  f6 = 42, //1 << 42, also promotes the unspecified underlying type to at least 64 bit integer
  f8 = 129, // 1 << 129, promotes type to std::bitset? Or maybe we static_assert here?
};
 

> strict
> rules about logical operator comparisons. Only the tags in the enum can use
> binary logical operations with one another.

This already exists, and has since quite some time; see
http://qt-project.org/doc/qt-4.8/qflags.html (dates back even to Qt3 I
think). I'd love to see a std::flags standardized, however. This
shouldn't need language/compiler changes, though (as various parts of
the above might), just an addition to STL.

Actually, I have an implementation of this if anyone is interested?

I'd be interested to know if you changed your design from qt's qflags and if so why and how that worked out for you.
 

--
Matthew

Tony V E

unread,
Jan 14, 2014, 12:42:09 AM1/14/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
This already exists, and has since quite some time; see
http://qt-project.org/doc/qt-4.8/qflags.html (dates back even to Qt3 I
think). I'd love to see a std::flags standardized, however. This
shouldn't need language/compiler changes, though (as various parts of
the above might), just an addition to STL.

Actually, I have an implementation of this if anyone is interested?

> I'd be interested to know if you changed your design from qt's qflags and if so why and how that worked out for you.
 

Yes, this is my question as well. It looks like I may be writing yet another one of these (can't use qt's for whatever reason) and I've been wondering about some changes, possibly with help from is_enum<>.
In particular, I would like to get rid of any/all use of macros, for defining the operators,  etc.

Tony

Andrew Tomazos

unread,
Jan 14, 2014, 3:09:10 AM1/14/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
On Monday, January 13, 2014 7:13:37 PM UTC+1, Matthew Woehlke wrote:
Referring to Andrew's comment, I think most of these that I've seen so
far and following can be implemented with N3815 and the relaxed
constexpr rules, but this one might be hard. (I'm not sure how crucial
it is, however; the bound ones are probably most important... also
count, but that one is obviously trivial with N3815.)

Which one is "this one"?  Do you mean count the distinct values?  I'll assume so.

First unpack the enumerator values into a constexpr array using an index sequence as shown in the proposal:

    template<class E>
    constexpr E enumerator_values[] = { std::enumerator_value<E,i>::value... };

Then we need a set data structure of literal type.  Once you have that you can simply:

    template<class E>
    constexpr size_t count_distinct_values()
    {
        literal_set<E> S;

        for (E e : enumerator_values)
            S.insert(e);

        return S.size();
    }

    template<class E>
    constexpr size_t distinct_enumerators = count_distinct_enumerators<E>();

This counts the number of distinct values at compile-time.

To implement a set of literal type you can use a hash table with open addressing.

Here is a similar hash table data structure from an explanation I gave for a different use case:

    template<class E>
    struct literal_hash_table
    {
        struct entry
        {
            bool occupied = false;
            E key = std::enumerator_value<E,0>;
            size_t position = 0;
        };

        static constexpr size_t num_buckets = std::enumerator_list_size<E>::value * 2;  // load factor of 2

        entry buckets[num_buckets]; 
     };

     template<class E>
     constexpr literal_hash_table<E> make_literal_hash_table()
     {
         literal_hash_table<E> table;

         for (size_t i = 0; i < std::enumerator_list_size<E>::value; i++)
         {
              E e = enumerator_values[i];
              
              size_t hash_code = e % literal_hash_table<E>::num_buckets;

              for (size_t j = hash_code; true; (j++) %= literal_hash_table<E>::num_buckets)
                  if (!table.buckets[j].occupied)
                  {
                      table.buckets[j].occupied = true;
                      table.buckets[j].key = e;
                      table.buckets[j].position = i;
                  }
          }

          return table;
     };

This one uses the hash table to invert the enumerator value array from index-to-value, to value-to-index - so implementing a simple literal set is even easier.

Let me know if you need any further help.

Matthew Woehlke

unread,
Jan 14, 2014, 1:49:46 PM1/14/14
to std-pr...@isocpp.org
On 2014-01-14 00:42, Tony V E wrote:
>>> This already exists, and has since quite some time; see
>>> http://qt-project.org/doc/qt-4.8/qflags.html (dates back even to Qt3 I
>>> think). I'd love to see a std::flags standardized, however. This
>>> shouldn't need language/compiler changes, though (as various parts of
>>> the above might), just an addition to STL.
>>>
>>> Actually, I have an implementation of this if anyone is interested?
>>
>> I'd be interested to know if you changed your design from qt's qflags and
>> if so why and how that worked out for you.
>
> Yes, this is my question as well. It looks like I may be writing yet
> another one of these (can't use qt's for whatever reason) and I've been
> wondering about some changes, possibly with help from is_enum<>.

I didn't use is_enum. I don't see that it is needed except possibly to
prevent misuse of a non-enum type as the template parameter.

> In particular, I would like to get rid of any/all use of macros, for
> defining the operators, etc.

Unfortunately this is "not possible". Because the flags type is not
itself the enum type, the desired binary operators taking (Enum, Flag)
must be defined separately from the definition of the Flag template. So
there is no way (AFAIK at least) to avoid a macro except to have to
repeat this code yourself. (Which you could do if you really want to
avoid the macro, but...)

That said, my implementation is just:

MYPROJECT_FLAGS(EnumType, FlagType)

...which is both the typedef of FlagType and the operators (and can be
used in a namespace scope), rather than two separate macros. (Though,
the first of Qt's macros IIRC is *just* a typedef.)

--
Matthew

Matthew Woehlke

unread,
Jan 14, 2014, 1:49:56 PM1/14/14
to std-pr...@isocpp.org
On 2014-01-13 21:52, fmatth...@gmail.com wrote:
> On Monday, January 13, 2014 1:13:37 PM UTC-5, Matthew Woehlke wrote:
Hmm... perhaps. That might be worth bringing up w.r.t. N3815, maybe to
add a 'distinct' form of the size and value.

>> I wonder how hard to implement the iterators are...
>>
> Second to having enum_strtable, this feature is crucial to me. I use enums
> a lot in C++ and iterating over the values is something I've needed to do
> many times. right now its just the simple for loop with a cast. It would be
> nice to have this feature provided by the standard library and be nice and
> type safe.

Don't get me wrong; I agree entirely :-). And actually they're (well,
except for above comment) really easy in light of N3815.

>> I would name 'loop' something like 'values' instead. That said, I would
>> really love to just be able to say:
>>
>> enum Enum
>> {
>> ...
>> };
>> for (auto e : Enum)
>> {
>> ...
>> }
>
> Enum is a type so this is inventing a whole new syntax (which I am not
> terribly opposed to, less typing is always good). I think its more sensible
> to just create a temporary object and iterate over that, letting the
> compiler optimize away the temporary object and just give you a loop.
>
> Something like this:
>
> template <typename E>
> class enum_range {};
>
> for(auto e: enum_range<Enum>()) { }

Yes, that may be better. If possible to specialize on an enum type¹,
even better would be if this can be written:

for (auto e : std::range<Enum>())

...where the start and end are optional for an enum type. (If specified,
presumably they would allow template argument deduction.)

(¹ If not, I wonder if this should be added...)

>> ...without the extra enum_traits::loop bits. (Actually, if it were/is(?)
>> possible to specialize std::begin() on an enum type, with the above
>> and/or N3815 this should already be possible?)
>
> I'm not sure having begin()/end() on the enum type itself actually makes
> sense. An enum object is just one tagged object, not the whole collection
>
> Color color = kRed;
> for(auto c: color) { /*???*/ }

Right. I was still thinking like 'enum E {...}; for (auto e : E)'. But
I'm not seeing how you would get that to work without some sort of
iterator temporary object, as noted above, as a typename is not a valid
token in this context.

>> (Oh, look; a case for 'inline enum class' :-).)
>>
>>> Other extensions I'd love to see:
>>> * enum bitmasks as a real type, easy and expressive to declare
>>
>> Yes, please!
>>
>> For API binary stability, there still needs to be a way to specify exact
>> values. Probably the 'old style' should still be allowed, but it might
>> greatly simplify matters to allow e.g. 'foo = :5 // equivalent to foo =
>> 1<<5' (assumes ':0' => 0x1). The main thing that's needed is a way to
>> say that the next value is automatically the highest bit of (2*prev)
>> rather than (prev+1). Unfortunately I don't have a suggestion for that
>> syntax.
>>
>> I could also see allowing 'foo = :2..5' => 0b111100 for ease of
>> declaring mask values.
>>
>
> I'm thinking more something like this:
> enum Flags : some_syntax_that_makes_this_a_bitmask_enum {
> f1, //Defaults to 1 << 1
> f2, // 1 << 2
> f3, // 1 << 3
> f4, // 1 << 4

Right. Except I assume you meant for the default first value to be '1'
(1<<0), not '2' (1<<1) :-). (I tried to make a similar mistake in my
above comment.)

> f5 = 8, //1 << 8

This I'd disagree with, because there are times I want an enum value
with more than one bit set (e.g. convenience combinations, masks, etc.).
Which is why I proposed a new value syntax ' = :<bit>' instead. (Also, I
can imagine it being confusing that '= 8' doesn't really mean '8'. Using
the ':<bit>' syntax, which is the same as already used for bit-packed
values, makes it more clear that the number is a bit index and not a raw
value.)

> f6 = 42, //1 << 42, also promotes the unspecified underlying type to at
> least 64 bit integer

Naturally :-).

> f8 = 129, // 1 << 129, promotes type to std::bitset? Or maybe we
> static_assert here?

I'd just emit an 'integer constant is too large for its type' warning,
same as if it were a regular enum. (Or else make it an error also for
regular enums. I don't think it should behave differently, however.)

>>> strict rules about logical operator comparisons. Only the tags in
>>> the enum can use binary logical operations with one another.
>>
>> This already exists, and has since quite some time; see
>> http://qt-project.org/doc/qt-4.8/qflags.html (dates back even to Qt3 I
>> think). I'd love to see a std::flags standardized, however. This
>> shouldn't need language/compiler changes, though (as various parts of
>> the above might), just an addition to STL.
>>
>> Actually, I have an implementation of this if anyone is interested?
>
> I'd be interested to know if you changed your design from qt's qflags and
> if so why and how that worked out for you.

Not significantly. IIRC it needed some tweaking (I think just addition
of some explicit casts) to work with strongly-typed (i.e. 'enum class')
enums, but otherwise it is essentially the same. Because it is meant to
use strongly typed enums, I was able to drop the overloads to prevent
combining different enum types.

--
Matthew

Thiago Macieira

unread,
Jan 14, 2014, 2:03:47 PM1/14/14
to std-pr...@isocpp.org
On terça-feira, 14 de janeiro de 2014 13:49:46, Matthew Woehlke wrote:
> ...which is both the typedef of FlagType and the operators (and can be
> used in a namespace scope), rather than two separate macros. (Though,
> the first of Qt's macros IIRC is *just* a typedef.)

It's also extracted by moc, which handles them specially.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
signature.asc

Sean Middleditch

unread,
Jan 14, 2014, 2:23:36 PM1/14/14
to std-pr...@isocpp.org
I brought up a topic on flags support a while ago and some very good suggestions came out of it; there just hasn't been a formal proposal yet:

Matthew Woehlke

unread,
Jan 14, 2014, 2:25:13 PM1/14/14
to std-pr...@isocpp.org
On 2014-01-14 14:03, Thiago Macieira wrote:
> On terça-feira, 14 de janeiro de 2014 13:49:46, Matthew Woehlke wrote:
>> ...which is both the typedef of FlagType and the operators (and can be
>> used in a namespace scope), rather than two separate macros. (Though,
>> the first of Qt's macros IIRC is *just* a typedef.)
>
> It's also extracted by moc, which handles them specially.

Well, okay, but that's not likely to apply to an STL implementation :-).
The point was that if STL were to exactly duplicate Qt's implementation
(i.e. with the same two macros doing the same things), the first macro
would be a bit superfluous.

For an STL implementation though I would instead propose my one-macro
form that declares both the typedef and freestanding operators as one
operation. (I'm not sure why Qt does both; probably either for MOC, or
due to some requirement from older C++ versions, neither of which would
apply to a new STL version.)

--
Matthew

Thiago Macieira

unread,
Jan 14, 2014, 3:06:51 PM1/14/14
to std-pr...@isocpp.org
On terça-feira, 14 de janeiro de 2014 14:25:13, Matthew Woehlke wrote:
> For an STL implementation though I would instead propose my one-macro
> form that declares both the typedef and freestanding operators as one
> operation. (I'm not sure why Qt does both; probably either for MOC, or
> due to some requirement from older C++ versions, neither of which would
> apply to a new STL version.)

One reason is for moc, which like I said extracts the declaration and includes
the information in the meta object.

Another reason is for qdoc, which also extracts the declaration and makes a
merged documentation for both the enum and the flags type:
http://qt-project.org/doc/qt-5/qnetworkproxy.html#Capability-enum

From the language point of view, the typedef is not necessary. We could use
QFlags<EnumType> everywhere. We simply choose not to.

If you're interested in creating a Standard Library type, you may want to take
a look at the most recent changes and discussions on QFlags:
https://codereview.qt-project.org/60158
https://codereview.qt-project.org/35324

We're also thinking that we need a separate type for 64-bit enums. One of the
reasons for using QFlags at all, besides the nice operators, was to have a
stable backing type before C++11. Since those enums are still quite common, we
need to ensure someone doesn't accidentally grow an enum from 32- to 64-bit.

Matthew Woehlke

unread,
Jan 14, 2014, 5:43:53 PM1/14/14
to std-pr...@isocpp.org
On 2014-01-14 15:06, Thiago Macieira wrote:
> From the language point of view, the typedef is not necessary. We could use
> QFlags<EnumType> everywhere. We simply choose not to.

Sure. IMHO I'd prefer the named type, but that's into bikeshedding about
the macro (which I'd be okay omitting entirely if it's felt to be
objectionable).

> If you're interested in creating a Standard Library type, you may want to take
> a look at the most recent changes and discussions on QFlags:
> https://codereview.qt-project.org/60158
> https://codereview.qt-project.org/35324
>
> We're also thinking that we need a separate type for 64-bit enums. One of the
> reasons for using QFlags at all, besides the nice operators, was to have a
> stable backing type before C++11. Since those enums are still quite common, we
> need to ensure someone doesn't accidentally grow an enum from 32- to 64-bit.

In C++11 I don't think this is needed? In my implementation, the type of
the internal data member is std::underlying_type<Enum>::type, and so is
automatically the correct size. If we're adding it to STL, we can rely
on std::underlying_type being available and should use it.

Thanks also for the links. The first, I'm not sure if it should be
necessary in light of the above (or at worst, shouldn't need more than
to accept both the signed / unsigned versions of the underlying type).
The second is moot per the above.

--
Matthew

Andrew Tomazos

unread,
Jan 14, 2014, 8:18:11 PM1/14/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
As shown in my previous message in this thread, using the N3815 property queries you can determine efficiently at compile-time the number of distinct values, iterate over the distinct values and/or place the distinct values in a pack or constexpr array.  You can do this as fast as if the compiler did it for you, and with only a few lines of pure library code.

There are many use cases for iterating over the full multiset (not just distinct) of enumerators in declared (or other) order, so no, we are not dropping support for them.  As the distinct set can be derived from the full multiset, but not visa versa, the multiset is exposed as the primitive interface.  This and other issues are explored at length in N3815.

Thiago Macieira

unread,
Jan 14, 2014, 8:37:22 PM1/14/14
to std-pr...@isocpp.org
On terça-feira, 14 de janeiro de 2014 17:43:53, Matthew Woehlke wrote:
> > If you're interested in creating a Standard Library type, you may want to
> > take>
> > a look at the most recent changes and discussions on QFlags:
> > https://codereview.qt-project.org/60158
> > https://codereview.qt-project.org/35324
> >
> > We're also thinking that we need a separate type for 64-bit enums. One of
> > the reasons for using QFlags at all, besides the nice operators, was to
> > have a stable backing type before C++11. Since those enums are still
> > quite common, we need to ensure someone doesn't accidentally grow an enum
> > from 32- to 64-bit.
> In C++11 I don't think this is needed? In my implementation, the type of
> the internal data member is std::underlying_type<Enum>::type, and so is
> automatically the correct size. If we're adding it to STL, we can rely
> on std::underlying_type being available and should use it.
>
> Thanks also for the links. The first, I'm not sure if it should be
> necessary in light of the above (or at worst, shouldn't need more than
> to accept both the signed / unsigned versions of the underlying type).
> The second is moot per the above.

It isn't necessary if the underlying enum type has an explicit size:

enum Flag : int {
Opt1 = 1,
Opt2 = 2
};
// the "class" keyword is not necessary to use this feature

But what happens if the user forgets the explicit type, chooses not to use it,
or is creating flags for a type that he doesn't control?

enum Flag2 {
Opt1 = 1,
Opt2 = 2
};

According to the standard, the enum will be backed by any integer large
enough to hold all values. In the example above, it could be as small as a
char.

In practice, compilers will usually choose int as the smallest type, then grow
to unsigned int, long, unsigned long, long long, unsigned long long. So
there's little danger of types smaller than 32-bit, but there's still the
danger of growing it accidentally to 64-bit.

I mean, the above is most likely
sizeof(Flag2) == sizeof(std::flags<Flag2>) == sizeof(int)

But what happens if in my new version I do change Flag2 to be:

enum Flag2 {
Opt1 = 1,
Opt2 = 2,
Invalid = ~0L // the L looks nice and I don't know the consequences
};

Now sizeof(Flag2) == sizeof(std::flags<Flag2>) == sizeof(long).

The Standard Library does not need to care about this -- after all, binary
compatibility issues are beyond the scope of the standard. But it's something
we care about in Qt and one of the reasons why we introduced QFlags in the
first place.

Matthew Woehlke

unread,
Jan 15, 2014, 12:16:38 PM1/15/14
to std-pr...@isocpp.org
On 2014-01-14 20:37, Thiago Macieira wrote:
> enum Flag2 {
> Opt1 = 1,
> Opt2 = 2
> };
>
> I mean, the above is most likely
> sizeof(Flag2) == sizeof(std::flags<Flag2>) == sizeof(int)
>
> But what happens if in my new version I do change Flag2 to be:
>
> enum Flag2 {
> Opt1 = 1,
> Opt2 = 2,
> Invalid = ~0L // the L looks nice and I don't know the consequences
> };
>
> Now sizeof(Flag2) == sizeof(std::flags<Flag2>) == sizeof(long).

...and so any code that took Flag2 is now (BC) broken, yes?

> The Standard Library does not need to care about this -- after all, binary
> compatibility issues are beyond the scope of the standard. But it's something
> we care about in Qt and one of the reasons why we introduced QFlags in the
> first place.

I guess I'm not convinced that std::flags needs to enforce a stronger BC
guarantee than the underlying type. If you want to ensure BC, specify
the storage type of the underlying enum.

--
Matthew

Tony V E

unread,
Jan 15, 2014, 1:34:19 PM1/15/14
to std-pr...@isocpp.org
On Tue, Jan 14, 2014 at 1:49 PM, Matthew Woehlke <mw_t...@users.sourceforge.net> wrote:


In particular, I would like to get rid of any/all use of macros, for
defining the operators,  etc.

Unfortunately this is "not possible". Because the flags type is not itself the enum type, the desired binary operators taking (Enum, Flag) must be defined separately from the definition of the Flag template. So there is no way (AFAIK at least) to avoid a macro except to have to repeat this code yourself. (Which you could do if you really want to avoid the macro, but...)


This is the basis of what I'm experimenting with:


template <typename Enum>
struct EnumFlags
{
    typedef typename make_unsigned<underlying_type<Enum>::type>::type value_type;
    value_type val;

    EnumFlags(Enum e) : val(e) {}

    operator value_type() { return val; }

    // *most* of the operators
    friend EnumFlags operator|(EnumFlags a, EnumFlags b)
    {
        return EnumFlags(value(a) | value(b));
    }
    friend EnumFlags operator|(EnumFlags a, Enum b)
    {
        return EnumFlags(value(a) | value(b));
    }
    friend EnumFlags operator|(Enum a, EnumFlags b)
    {
        return EnumFlags(value(a) | value(b));
    }
    // but, unfortunately, Enum | Enum must be outside the class. See below...
};

// now here is the scary part:

template <typename Enum>
typename enable_if< is_enum<Enum>::value, EnumFlags<Enum> >::type   operator|(Enum a, Enum b)
{
    return EnumFlags<Enum>(static_cast<Enum>(static_cast<EnumFlags<Enum>::value_type>(a) | b));
}

So that defines operator|() for ALL enums, whether or not you want it.  Thus it is the "scary part".

Note, that:

enum Breakfast { Ham, Eggs, Green };

unsigned int x = Green | Eggs | Ham;

still works, but it converts it temporarily to EnumFlags<Breakfast>, then to unsigned int via the cast operator. Whether you want it to or not.  Not _usually_ a problem, however:

struct ConvertsToInt
{
    ConvertsToInt(unsigned int);
};

void func(ConvertsToInt);

int main()
{
    func(Green | Eggs | Ham); // no longer works - too many conversions :-(
}


So that overly-broad operator|(Enum a, Enum b) is dangerous.  But might be OK for a code-base where you don't want to allow things like that.

Alternatively, you can replace the use of is_enum<> with something like wants_enumflags<>.  And then you specialize to say you want it:

    template <> wants_enumflags<Breakfast> : true_type;

but I'm sure most people would prefer to have that in a macro. MYPROJECT_ENUMFLAGS(Breakfast) or Q_DECLARE_OPERATORS_FOR_FLAGS(Breakfast).
:-(

Tony


 
That said, my implementation is just:

  MYPROJECT_FLAGS(EnumType, FlagType)

...which is both the typedef of FlagType and the operators (and can be used in a namespace scope), rather than two separate macros. (Though, the first of Qt's macros IIRC is *just* a typedef.)


--
Matthew

--

--- You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Matthew Woehlke

unread,
Jan 15, 2014, 2:19:00 PM1/15/14
to std-pr...@isocpp.org
On 2014-01-15 13:34, Tony V E wrote:
> On Tue, Jan 14, 2014 at 1:49 PM, Matthew Woehlke wrote:
>>> In particular, I would like to get rid of any/all use of macros, for
>>> defining the operators, etc.
>>
>> Unfortunately this is "not possible". Because the flags type is not itself
>> the enum type, the desired binary operators taking (Enum, Flag) must be
>> defined separately from the definition of the Flag template. So there is no
>> way (AFAIK at least) to avoid a macro except to have to repeat this code
>> yourself. (Which you could do if you really want to avoid the macro, but...)
>
> This is the basis of what I'm experimenting with:
> [snip implementation]

Er... I'll omit specific comments, and just say that there is a *lot*
missing there.

I attached the version I'm using, with some attempt to rewrite it as an
actual reference implementation (please pardon any style errors). I
didn't change it to use make_unsigned as you did... I'm not sure if
that's desirable, as it can lead to signed/unsigned mismatch because the
internal type of the std::flags != the enum type. (Also, I added an
explicit operator~ for the enum type which should ensure that e.g.
'~enum::value' always has the same type as std::flags<enum>::value_t and
thus won't trigger type conversions using as input to std::flags's
operators.)

> // now here is the scary part:
>
> template <typename Enum>
> typename enable_if< is_enum<Enum>::value, EnumFlags<Enum> >::type
> operator|(Enum a, Enum b)
> {
> return
> EnumFlags<Enum>(static_cast<Enum>(static_cast<EnumFlags<Enum>::value_type>(a)
> | b));
> }

Er... yes. Let's not do that. Globally defining operator| for any enum
(in case someone has done '#include <flags>' anyway) seems dangerous.

--
Matthew
c++1y-std-flags.h

Matthew Woehlke

unread,
Jan 15, 2014, 2:54:36 PM1/15/14
to std-pr...@isocpp.org
Here's a modest example of the previous code in action:

#include <flags>

enum class bit // 'class' is optional, can also use old-style enum
{
A = 1<<0,
B = 1<<1,
C = 1<<2,
};
// STD_OPERATORS_FOR_FLAGS(bit) if you don't want the typedef
STD_FLAGS(bitset, bit) // as above, plus alias bitset = std::flags<bit>

int main()
{
auto x = std::flags<int>{}; // error: 'int' is not an enum type
auto i = bitset{5}; // error: ctor is protected
auto z = bitset{0}; // okay, calls bitset(nullptr_t)

auto b = bitset{bit::A};
auto m = ~bit::B; // okay, use custom operator~(bit)
b &= m; // typeof(m) == std::flags<bit>::value_t

return 0;
}

Unlike QFlags, it works on both 'enum' (old-style enum) and 'enum class'
(new, type-safe enum). There is no mucking about with an 'incompatible
flags' type (but note that this means you can do some 'wrong'/'abusive'
things with a non-type-safe enum, though the resulting integer type
can't be put back into a std::flags).

The use of a ctor taking nullptr_t isn't ideal, but I really want to be
able to zero-initialize a std::flags (i.e. literal "= 0" and variations
thereof), and this seems the best way without a language change.

p.s. I consider having separate types for individual flag values and the
flag collection to be a feature, not a detriment :-).

I'll declare the exact method of declaring the freestanding operators to
be a matter of bikeshedding. I used a macro, but Tony's suggestion re:
conditional templates ('wants_flags') would work as well.

Another option would be if we added a language feature to test if a
template instantiation exists without instantiating it, i.e. at the
point the compiler needs to instantiate e.g. operator|(flag_t, flag_t)
it would test if 'std::flags<flag_t>' has been used prior to that point.
This would make the freestanding operators 'automagic'.

(Note that I'm considering any/all issues regarding more convenient
definition of flag values to be orthogonal.)

--
Matthew

Greg Marr

unread,
Jan 16, 2014, 5:25:43 PM1/16/14
to std-pr...@isocpp.org
On Monday, January 13, 2014 1:13:37 PM UTC-5, Matthew Woehlke wrote:
On 2014-01-11 01:47, fmatth...@gmail.com wrote:
>    static constexpr const bool is_contiguious = /* true if enum values are
> contiguous */
>    static constexpr const bool is_regular = is_contiguous && min ==
> value_type(0);
>    static constexpr const bool has_zero = /* true if zero is an enum value */
>    static constexpr const bool has_postive = /* true if at least one enum
> value is positive */
>    static constexpr const bool all_postive = /* true if all enum values are
> positive */

This feels like 'all_nonnegative' is missing...

>    static constexpr const bool has_negative = /* true if at least one enum
> value is negative */

It's covered in the negative sense:
    all_nonnegative() == !has_negative() 

For that matter, all_positive is already covered too:
    all_positive() == !has_zero() && !has_negative()

I noticed three typos in the quoted section: is_contiguious, has_postive, all_postive

Tony V E

unread,
Jan 16, 2014, 5:51:30 PM1/16/14
to std-pr...@isocpp.org
On Wed, Jan 15, 2014 at 2:19 PM, Matthew Woehlke <mw_t...@users.sourceforge.net> wrote:
On 2014-01-15 13:34, Tony V E wrote:
On Tue, Jan 14, 2014 at 1:49 PM, Matthew Woehlke wrote:
In particular, I would like to get rid of any/all use of macros, for
defining the operators,  etc.

Unfortunately this is "not possible". Because the flags type is not itself
the enum type, the desired binary operators taking (Enum, Flag) must be
defined separately from the definition of the Flag template. So there is no
way (AFAIK at least) to avoid a macro except to have to repeat this code
yourself. (Which you could do if you really want to avoid the macro, but...)

This is the basis of what I'm experimenting with:
[snip implementation]

Er... I'll omit specific comments, and just say that there is a *lot* missing there.

Obviously.  It is just a sketch of some pertinent parts.  I didn't want to paste in the whole thing.
 

I attached the version I'm using, with some attempt to rewrite it as an actual reference implementation (please pardon any style errors). I didn't change it to use make_unsigned as you did... I'm not sure if that's desirable, as it can lead to signed/unsigned mismatch because the internal type of the std::flags != the enum type. (Also, I added an explicit operator~ for the enum type which should ensure that e.g. '~enum::value' always has the same type as std::flags<enum>::value_t and thus won't trigger type conversions using as input to std::flags's operators.)

Yeah, I still need to think about the unsigned part.  In my head operator|() is always unsigned.  Bit-wise operations on signed values is just silly.  So I automatically made it unsigned.
 


// now here is the scary part:

template <typename Enum>
typename enable_if< is_enum<Enum>::value, EnumFlags<Enum> >::type
operator|(Enum a, Enum b)
{
     return
EnumFlags<Enum>(static_cast<Enum>(static_cast<EnumFlags<Enum>::value_type>(a)
| b));
}

Er... yes. Let's not do that. Globally defining operator| for any enum (in case someone has done '#include <flags>' anyway) seems dangerous.


Yeah, that's my feeling.  But I'm trying to convince myself that bitwise operators on enums *should* promote to a different type (a type "more" than the enum, but "less" than int). I'm at least wondering if it would be viable for a code-base that wanted to prevent wild-west enum operations.  (ie you can also define the mixed operator|(EnumA a, EnumB b) and have it static_assert, for example).  Obviously not OK for the standard, but might work for some code bases.

Also, I just hate MACROS that much.

Tony

Nevin Liber

unread,
Jan 16, 2014, 6:42:03 PM1/16/14
to std-pr...@isocpp.org
On 16 January 2014 16:51, Tony V E <tvan...@gmail.com> wrote:

Yeah, that's my feeling.  But I'm trying to convince myself that bitwise operators on enums *should* promote to a different type (a type "more" than the enum, but "less" than int).

I think it gets to be a bit unwieldy.  Too many types and you get useability problems.

Then we get weird stuff like:

enum class Bits { /* ... */ };

auto b1 = Bits::Alpha;
auto b2 = Bits::Bravo | Bits::Charlie; // b2 is NOT the same type as b1


Also, what if the labels aren't distinct bits, as in:

enum class Bits
{
    Alpha = 1,
    Bravo = 2,
    Charlie = 4,

    Mask = 1 | 2 | 4,
};

If you have a "middle" type, Mask clearly belongs in it.  How do you put it there?


In my experience, I've never seen people have a problem with the result of combining bits from an enum back into that enum type.  My current set of macros is:

#define ENUM(E)\
    inline constexpr std::underlying_type<E>::type operator+(E e) noexcept\
    { return static_cast<std::underlying_type<E>::type>(e); }\
    inline constexpr bool operator!(E e) noexcept\
    { return !+e; }\

#define ENUM_BITFIELD_DETAIL_BINOP(E, OP)\
    inline constexpr E operator OP (E l, E r) noexcept\
    { return static_cast<E>(+l OP +r); }\
    \
    inline E& operator OP##= (E& l, E r) noexcept\
    { return l = l OP r; }\

#define ENUM_BITFIELD(E)\
    ENUM(E)\
    ENUM_BITFIELD_DETAIL_BINOP(E, &)\
    ENUM_BITFIELD_DETAIL_BINOP(E, ^)\
    ENUM_BITFIELD_DETAIL_BINOP(E, |)\
    inline constexpr E operator~(E e) noexcept\
    { return static_cast<E>(~+e); }


--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Matthew Woehlke

unread,
Jan 17, 2014, 12:27:17 PM1/17/14
to std-pr...@isocpp.org
On 2014-01-16 18:42, Nevin Liber wrote:
> On 16 January 2014 16:51, Tony V E wrote:
>> Yeah, that's my feeling. But I'm trying to convince myself that bitwise
>> operators on enums *should* promote to a different type (a type "more" than
>> the enum, but "less" than int).
>
> I think it gets to be a bit unwieldy. Too many types and you get
> useability problems.
>
> Then we get weird stuff like:
>
> enum class Bits { /* ... */ };
>
> auto b1 = Bits::Alpha;
> auto b2 = Bits::Bravo | Bits::Charlie; // b2 is NOT the same type as b1

I don't consider this a bad thing. On the contrary, this allows
differentiating between e.g. functions that expect a single flag, and
functions that accept a set of flags.

> Also, what if the labels aren't distinct bits, as in:
>
> enum class Bits
> {
> Alpha = 1,
> Bravo = 2,
> Charlie = 4,
>
> Mask = 1 | 2 | 4,
> };
>
> If you have a "middle" type, Mask clearly belongs in it. How do you put it
> there?

'enum class BitMasks : Bits', obviously, and then tweak the definition
of flags to also accept a 'Mask' template parameter (likely requires
that Bits automatically promotes to Bits, or else we need to definitions
of std::flags, one for Mask != Enum, and one for ==).

Since we don't have enum inheritance currently, I wasn't trying to
address that.

> In my experience, I've never seen people have a problem with the result of
> combining bits from an enum back into that enum type. My current set of
> macros is:

...and I've never seen a problem with 'flags' being a separate type.
Obviously it isn't for Qt, and that's a fairly substantial existing
implementation.

Conversely, the idea of making a standard use case out of coercing a
value of type 'enum' (expecially 'enum class') to equal something not
actually in the enumeration just doesn't feel right to me.

--
Matthew

Matthew Woehlke

unread,
Jan 17, 2014, 4:19:54 PM1/17/14
to std-pr...@isocpp.org
On 2014-01-15 14:19, Matthew Woehlke wrote:
> I attached the version I'm using, with some attempt to rewrite it as an
> actual reference implementation (please pardon any style errors).

Improved version, now with friend binary operators (and more overloads
for improved commutative-ness) and fix for '=0', '{0}' not working.

Unfortunately this still violates -Wzero-as-null-pointer-constant for
'expected' use. Anyone happen to know of a way to write an overload that
*only* takes a literal '0' besides nullptr_t?

--
Matthew
c++1y-std-flags.h

Matthew Woehlke

unread,
Jan 20, 2014, 12:45:25 PM1/20/14
to std-pr...@isocpp.org
On 2014-01-16 17:51, Tony V E wrote:
> On Wed, Jan 15, 2014 at 2:19 PM, Matthew Woehlke wrote:
>> On 2014-01-15 13:34, Tony V E wrote:
>>> This is the basis of what I'm experimenting with:
>>> [snip implementation]
>>>
>>
>> Er... I'll omit specific comments, and just say that there is a *lot*
>> missing there.
>
> Obviously. It is just a sketch of some pertinent parts. I didn't want to
> paste in the whole thing.

'constexpr'?

>> I attached the version I'm using, with some attempt to rewrite it as an
>> actual reference implementation (please pardon any style errors). I didn't
>> change it to use make_unsigned as you did... I'm not sure if that's
>> desirable, as it can lead to signed/unsigned mismatch because the internal
>> type of the std::flags != the enum type. (Also, I added an explicit
>> operator~ for the enum type which should ensure that e.g. '~enum::value'
>> always has the same type as std::flags<enum>::value_t and thus won't
>> trigger type conversions using as input to std::flags's operators.)
>
>
> Yeah, I still need to think about the unsigned part. In my head
> operator|() is always unsigned. Bit-wise operations on signed values is
> just silly.

Why? :-)

>>> // now here is the scary part:
>>>
>>> template <typename Enum>
>>> typename enable_if< is_enum<Enum>::value, EnumFlags<Enum> >::type
>>> operator|(Enum a, Enum b)
>>> {
>>> return
>>> EnumFlags<Enum>(static_cast<Enum>(static_cast<EnumFlags<
>>> Enum>::value_type>(a)
>>> | b));
>>> }
>>
>> Er... yes. Let's not do that. Globally defining operator| for any enum (in
>> case someone has done '#include <flags>' anyway) seems dangerous.
>
> Yeah, that's my feeling. But I'm trying to convince myself that bitwise
> operators on enums *should* promote to a different type (a type "more" than
> the enum, but "less" than int).

Well, your usual use case is going to be e.g.:

// declarations
STD_FLAGS(options, option)
foo(options);

// code
foo(option::A | option::B)

There's a reasonable expectation to write code like that, as opposed to:

foo(options{option::A} | option::B)

> I'm at least wondering if it would be
> viable for a code-base that wanted to prevent wild-west enum operations.
> (ie you can also define the mixed operator|(EnumA a, EnumB b) and have it
> static_assert, for example). Obviously not OK for the standard, but might
> work for some code bases.
>
> Also, I just hate MACROS that much.

Well, sure :-). Really, though, they're not Pure Evil; sometimes they're
the best solution. I'm open to others though, as long they neither
involve making the operators available for *any* enum nor require users
to entirely define the operators themselves.

--
Matthew

Thiago Macieira

unread,
Jan 20, 2014, 12:59:54 PM1/20/14
to std-pr...@isocpp.org
On segunda-feira, 20 de janeiro de 2014 12:45:25, Matthew Woehlke wrote:
> > Yeah, I still need to think about the unsigned part. In my head
> > operator|() is always unsigned. Bit-wise operations on signed values is
> > just silly.
>
> Why?

I'm wondering the same.

The only signed operations that might even be silly on bits (or, at least,
surprising) are right shift and sign-extension.

When talking about flags, these operations are rare, Right shift would happen
in real bitfeilds, but not in flags. Sign-extension would be problematic when
converting the full integer holding the enum / flags into a larger type, but
that can be solved by returning 32- or 64-bit integers as necessary. That way,
no sign-extension happens.

But we need to keep the signedness of the original enum, otherwise compilers
complain. See the specific issue we're trying to solve with QFlags:

https://codereview.qt-project.org/60158
signature.asc

Thiago Macieira

unread,
Jan 20, 2014, 7:20:55 PM1/20/14
to std-pr...@isocpp.org
On segunda-feira, 20 de janeiro de 2014 09:59:54, Thiago Macieira wrote:
> When talking about flags, these operations are rare, Right shift would
> happen in real bitfeilds, but not in flags. Sign-extension would be
> problematic when converting the full integer holding the enum / flags into
> a larger type, but that can be solved by returning 32- or 64-bit integers
> as necessary. That way, no sign-extension happens.
>
> But we need to keep the signedness of the original enum, otherwise
> compilers complain. See the specific issue we're trying to solve with
> QFlags:
>
> https://codereview.qt-project.org/60158

Uh... my code had the very sign-extension problem I was talking about. Fixed.
:-)

Tony V E

unread,
Jan 27, 2014, 2:58:05 PM1/27/14
to std-pr...@isocpp.org
On Mon, Jan 20, 2014 at 12:59 PM, Thiago Macieira <thi...@macieira.org> wrote:
On segunda-feira, 20 de janeiro de 2014 12:45:25, Matthew Woehlke wrote:
> > Yeah, I still need to think about the unsigned part.  In my head
> > operator|() is always unsigned.  Bit-wise operations on signed values is
> > just silly.
>
> Why?

I'm wondering the same.


I don't assume 2's complement representation of signed integers.  In general, I've learned to avoid bit-twiddling signed values.  Typically it is fine, but in my head, signed values are numbers, and unsigned values are for bit-twiddling.  Don't mix the two.  Which also means unsigned values are NOT for numbers.

But that's just my opinion.

Tony


Thiago Macieira

unread,
Jan 27, 2014, 6:53:27 PM1/27/14
to std-pr...@isocpp.org
I'd do what you suggested then:

template <typename Enum>
struct EnumFlags
{
typedef typename make_unsigned<underlying_type<Enum>::type>::type
value_type;
value_type val;
[...]

As long as only one method exists that takes an integer, the compiler will
automatically promote signed to unsigned. Probably with a warning, though.

We could advise people who do flags that they should always explicitly choose
an unsigned underlying type.
signature.asc

Peter Bigot

unread,
Jan 27, 2014, 9:41:05 PM1/27/14
to std-pr...@isocpp.org

An unsigned underlying type that has the same or higher rank than int, though, right?  Using unsigned char as the enum base could result in integral promotion to int, reintroducing the problem of operating on signed values.  In the general case I think you need something like this:

   using promoted_type = typename std::common_type<int, std::underlying_type<Enum>::type>::type;
   using value_type = typename std::make_unsigned<promoted_type>::type;

Peter
Reply all
Reply to author
Forward
0 new messages