Templatize enum class operators

2019-05-14 01:13发布

问题:

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.

回答1:

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

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.