Search code examples
c++templatesc++-concepts

Opt-in / Customization point for template enum class operators


I often use enum class for flags like this:

enum class MyFlags : uint32_t {
  None  = 0,
  Flag1 = (1 << 0),
  Flag2 = (1 << 1),
  ...
}

Now I often want to do operations for combining the flags like this: MyFlags::Flag1 | MyFlags::MyFlags2

So i need an operator overload for this:

MyFlags operator|(MyFlags f1, MyFlags f2)
{
   return static_cast<MyFlags>(uint32_t(f1) | uint32_t(f2));
}

// other operators like =|, &, also defined.

Since I find myself doing this for various enum classes I would like to write a template for this, but only activate the enum classes that are actually using flags (opt-in) so it's not used for all enums.

How can I enable the operator for some selected enums? The following is how far I got:

// What other code here to only select this for selected types?
template <typename T> // Concept instead of typename?
T operator|(T f1, T f2)
{
   return static_cast<T>(uint32_t(f1) | uint32_t(f2));
}

// Other .cpp file
// Here I'd like to do opt-in. Is this done with template specialization? std::enable_if? Other?
// What code here to use the above template for MyFlags?

As indicated in the comments, I'd like to use concepts if possible. If there are other ways, it would be ok too.


Solution

  • So you would make your concept:

    template<typename T>
    concept enable_bitops_for = std::is_enum_v<T> && /* ... */;
    
    template <enable_bitops_for T>
    constexpr T operator|(T f1, T f2)
    {
       return static_cast<T>(std::to_underlying(f1) | std::to_underlying(f2));
    }
    

    Now the question is how you define your concept so that it's only satisfied for enums you specifically enable it for.

    A classic way is template specialization:

    template<typename T>
    inline constexpr bool do_enable_bitops_for = false;
    
    template<typename T>
    concept enable_bitops_for = std::is_enum_v<T> && do_enable_bitops_for<std::remove_cv_t<T>>;
    
    enum class MyFlags : uint32_t {
      None  = 0,
      Flag1 = (1 << 0),
      Flag2 = (1 << 1),
      ...
    };
    
    template<>
    inline constexpr bool do_enable_bitops_for<MyFlags> = true;
    

    This has some problems if your enum class is defined inside a namespace or a class since you have to leave the namespace or class to specialize.

    Another is some sort of tag:

    template<typename T>
    concept enable_bitops_for = std::is_enum_v<T> && (T::_enable_bitops == T{});
    
    enum class MyFlags : uint32_t {
      None  = 0,
      Flag1 = (1 << 0),
      Flag2 = (1 << 1),
      ...,
      _enable_bitops = 0
    };
    
    template<typename T>
    concept enable_bitops_for = std::is_enum_v<T> && requires {
        { _enable_bitops(T{}) } -> std::same_as<std::true_type>;
    };
    
    enum class MyFlags : uint32_t {
      None  = 0,
      Flag1 = (1 << 0),
      Flag2 = (1 << 1),
      ...
    }
    std::true_type _enable_bitops(MyFlags);