I'm trying to create a flags bitfield using C++11 enum classes. I'm looking for a way to templatize the operators' return types so they can be used as in code below:
#include <iostream>
enum class Flags {
one = 1,
two = 1 << 1,
three = 1 << 2,
four = 1 << 3
};
#define _CONVERT(_operator) \
static_cast<T>(static_cast<int>(lhs) _operator static_cast<int>(rhs))
template <typename T>
T operator & (const Flags& lhs, const Flags& rhs) {
return _CONVERT(&);
}
template <typename T>
T operator | (const Flags& lhs, const Flags& rhs) {
return _CONVERT(|);
}
#undef _convert
int main()
{
Flags flag = Flags::one | Flags::two | Flags::three;
if (flag & Flags::two)
std::cout << "Flag has two" << std::endl;
if (flag & Flags::four)
std::cout << "Flag has four" << std::endl;
std::cout << static_cast<int>(flag) << std::endl;
}
However, there are several problems:
Flags flag = Flags::one | Flags::two | Flags::three;
can't deduce type to be Flags
if (flag & Flags::four)
can't deduce type to be bool
I'm new to templates and am kinda lost when it comes to template deduction mechanisms. Also, i tried to create create conversion operator
operator bool(const Flags& flag)
but with no result.
First create a helper template:
template<class E>
struct bool_or_enum{
E e;
explicit operator bool()const{return static_cast<bool>(e); }
operator E() const {return e;}
};
Next, create a trait function and type:
namespace magic_operators {
template<class E>
constexpr std::false_type algebraic_enum(E const volatile&) {return {};}
template<class E>
using use_algebra=decltype( algebraic_enum( std::declval<E const volatile&>() ) );
}
Now magic_operators::use_algebra<E>
searches using ADL for algebraic_enum
overload returning std::true_type
on E
. This permits enabling the magic anywhere. MSVC 2015 lacks sufficient C++11 support to use the above; replace with traits class.
The meat: our operators. Stick them into a namespace and bring them in with using namespace
:
template<class E, std::enable_if_t<magic_operators::use_algebra<E>{}, int> = 0>
bool_or_enum<E> operator&(E const& lhs, E const& rhs){
using U = std::underlying_type_t<E>;
return { E( static_cast<U>(lhs) | static_cast<U>(rhs) ) };
}
And similar for |
.
For ~
and ^
you need a bit mask to remain defined behaviour. Have a traits class enum_mask<E>
that defaults to E::bit_mask
or somesuch to get it.
template<class E, std::enable_if_t<magic_operators::use_algebra<E>{}, int> = 0>
bool_or_enum<E> operator^(E const& lhs, E const& rhs){
using U = std::underlying_type_t<E>;
return { E( enum_mask<E>{} & (static_cast<U>(lhs) ^ static_cast<U>(rhs) ) ) };
}
template<class E, std::enable_if_t<magic_operators::use_algebra<E>{}, int> = 0>
bool_or_enum<E> operator~(E const& e){
using U = std::underlying_type_t<E>;
return { E( enum_mask<E>{} & (~static_cast<U>(e)) ) };
}
This is tricky due to standards requirements on out of gamut enums.
|=
and &=
isn't hard, but does need to be coded. =
and |=
and &=
etc that support both assignment chaining and implicit bool requires yet more work. I say do not support it.
Oh and mark everything constexpr
and add bool_or_enum<E>
overloads to the operator
s.
The above code is not tested or compiled, but the design works.
The end result is:
enum class Bob { a=2, b=7, bit_mask = 0x00ff };
constexpr std::true_type algebraic_enum( Bob const& ){ return {}; }
using namespace algebraic_ops;
int main(){
Bob x=Bob::a;
x = x | Bob::b;
if( x &~ Bob::b ){
std::cout << "cast to bool bitmasking!\n";
}
}
Or somesuch.