[PATCH 1/8] gdbsupport: Provide global operators |=, &=, and ^= for enum bit flags

Andrew Burgess
2020-08-13T12:58:53Z

The current enum bit flags mechanism provides global operators &, |,
^, ~, but does not provide &=, |=, ^=.

The implementation for one of these, |=, would look like this:

template <typename enum_type>
typename enum_flags_type<enum_type>::type &
operator|= (enum_type &e1, enum_type e2)
{
e1 = enum_flags<enum_type> (e1) | e2;
return e1;
}

Then if we create a test within GDB like this:

enum some_flag
{
flag_val1 = 1 << 1,
flag_val2 = 1 << 2,
flag_val3 = 1 << 3,
flag_val4 = 1 << 4,
};
DEF_ENUM_FLAGS_TYPE(enum some_flag, some_flags);

...

enum some_flag f = flag_val1 | flag_val2;
f |= flag_val3;

Initially this didn't compile, with an error like:

..../gdbsupport/enum-flags.h: In instantiation of ‘typename enum_flags_type<T>::type& operator|=(enum_type&, enum_type) [with enum_type = some_flag; typename enum_flags_type<T>::type = enum_flags<some_flag>]’:
..../gdb/main.c:168:8: required from here
..../gdbsupport/enum-flags.h:217:10: error: cannot bind non-const lvalue reference of type ‘enum_flags_type<some_flag>::type&’ {aka ‘enum_flags<some_flag>&’} to an rvalue of type ‘enum_flags_type<some_flag>::type’ {aka ‘enum_flags<some_flag>’}
217 | return e1;
| ^~
..../gdbsupport/enum-flags.h:125:3: note: after user-defined conversion: ‘enum_flags<E>::enum_flags(enum_flags<E>::enum_type) [with E = some_flag; enum_flags<E>::enum_type = some_flag]’
125 | enum_flags (enum_type e)
| ^~~~~~~~~~

If we look at enum_flags_type, which is used in the return type of our
operator, it looks like this:

template<>
struct enum_flags_type<enum_type>
{
typedef enum_flags<enum_type> type;
}

So, with `typename' and `typedef' resolved, our new operator actually
compiled like this:

template <typename enum_type>
enum_flags<enum_type> &
operator|= (enum_type &e1, enum_type e2)
{
e1 = enum_flags<enum_type> (e1) | e2;
return e1;
}

And I think this makes the problem clearer (maybe), the return type
reference is different to the argument types, so the compiler is
forced to try and create a temporary (the result of converting `e1' to
type `enum_flags<enum_type>') and return a reference to that temporary.

What I'd like is for the return type to match the arguments, and this
can be achieved I think by changing the enum_flags_type definition to
this:

template<>
struct enum_flags_type<enum_type>
{
typedef enum_type type;
}

Now, once the `typename' and `typedef' are resolved, our new operator
compiles as this:

template <typename enum_type>
enum_type &
operator|= (enum_type &e1, enum_type e2)
{
e1 = enum_flags<enum_type> (e1) | e2;
return e1;
}

And indeed our original example now compiles fine.

The final thing we must take care to check is that the global
operators are not now picking up _all_ enums, after all, this is part
of what the enum-flags.h file does for us, bitwise operators where we
ask for it, and not otherwise.

A manual test on my demo above (with some_flags) confirms that
removing the DEF_ENUM_FLAGS_TYPE line still prevents the code from
compiling as we'd hope, this is because, just like before
`enum_flags_type<enum_type>::type' is only defined for the enums that
have DEF_ENUM_FLAGS_TYPE applied to them.

gdbsupport/ChangeLog:

* enum-flags.h (DEF_ENUM_FLAGS_TYPE): Update header comment, and
define enum_flags_type<enum_type>::type differently.
(operator|=): New.
(operator&=): New.
(operator^=): New.
---
gdbsupport/ChangeLog | 8 ++++++++
gdbsupport/enum-flags.h | 30 +++++++++++++++++++++++++++---
2 files changed, 35 insertions(+), 3 deletions(-)

diff --git a/gdbsupport/enum-flags.h b/gdbsupport/enum-flags.h
index 825ff4faf2c..78db3c7d88e 100644
--- a/gdbsupport/enum-flags.h
+++ b/gdbsupport/enum-flags.h
@@ -55,15 +55,15 @@
instantiating for non-flag enums. */
template<typename T> struct enum_flags_type {};

-/* Use this to mark an enum as flags enum. It defines FLAGS as
+/* Use this to mark an enum as flags enum. It defines FLAGS_TYPE as
enum_flags wrapper class for ENUM, and enables the global operator
- overloads for ENUM. */
+ overloads for ENUM_TYPE. */
#define DEF_ENUM_FLAGS_TYPE(enum_type, flags_type) \
typedef enum_flags<enum_type> flags_type; \
template<> \
struct enum_flags_type<enum_type> \
{ \
- typedef enum_flags<enum_type> type; \
+ typedef enum_type type; \
}

/* Until we can rely on std::underlying type being universally
@@ -209,6 +209,30 @@ operator~ (enum_type e)
return ~enum_flags<enum_type> (e);
}

+template <typename enum_type>
+typename enum_flags_type<enum_type>::type
+operator|= (enum_type &e1, const enum_type &e2)
+{
+ e1 = enum_flags<enum_type> (e1) | e2;
+ return e1;
+}
+
+template <typename enum_type>
+typename enum_flags_type<enum_type>::type
+operator&= (enum_type &e1, const enum_type &e2)
+{
+ e1 = enum_flags<enum_type> (e1) & e2;
+ return e1;
+}
+
+template <typename enum_type>
+typename enum_flags_type<enum_type>::type
+operator^= (enum_type &e1, const enum_type &e2)
+{
+ e1 = enum_flags<enum_type> (e1) ^ e2;
+ return e1;
+}
+
#else /* __cplusplus */

/* In C, the flags type is just a typedef for the enum type. */
--
2.25.4