csbako
csbako

Reputation: 201

How to create a template operator for enums

I need a general template class without macro magic that I can use like this:

template<typename E>
class enum_operators
{
    E& operator++( E& orig )
    {
        orig = static_cast< E >( orig + 1 );
        return orig;
    }
};

enum colors : public enum_operators< colors >
{
    white,
    red,
    green,
    blue
};

enum corners : public enum_operators< corners >
{
    topleft,
    topright,
    bottomleft,
    bottomright
};

Is it possible with variadic template or something else? How can I do that?

Upvotes: 4

Views: 2112

Answers (6)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

namespace operator_support {
  template<class T> struct tag_t{using type=T; constexpr tag_t(){}};
  template<class T> constexpr tag_t<T> tag = {};

  template<class T>
  std::false_type operator_support::supports_plus_plus( tag_t<T> );

  template<class E>
  decltype(supports_plus_plus( tag<E> )) supports_plus_plus_v = {};
}

In order to make your enum supports_plus_plus_t<E> be true_type, simply write std::true_type supports_plus_plus< tag_t<E> >(); in the same namespace as your enum E. ADL does the rest.

namespace plus_plus {
  template <class E,
    std::enable_if_t<operator_support::supports_plus_plus_v<E>>* =nullptr
  >
  E &operator ++ (E &e) {
    return e = static_cast<E>(
      static_cast<std::underlying_type_t<E>>(e) + 1
    );
  }
}

in order to use ++ you must:

  • Type std::true_type supports_plus_plus< tag_t<E> >(); in the namespace of the enum E.

  • using namespace ::plus_plus; where you want to call ++ on an enum. This restricts the use of ++ to enums that support it and deals with operator lookup issues. (body of ++ from @csbako).

You can make a macro:

#define PLUS_PLUS_POWERS_ACTIVATE(...) \
  std::true_type supports_plus_plus< tag_t<__VA_ARGS__> >(); \
  using namespace ::plus_plus

which takes an enum name (I use va args as C++ enum names may have embedded ,s between <>s, which macros don't like).

enum whatever {
  a,b,c
};
PLUS_PLUS_POWERS_ACTIVATE(whatever);
int main() {
  whatever x = a;
  std::cout << x;
  ++x;
  std::cout << x;
  ++x;
  std::cout << x;
}

Upvotes: 0

csbako
csbako

Reputation: 201

namespace enum_operator
{
    template<typename E>
    E& operator++(E& e)
    {
        e = static_cast< E >(static_cast< int >( e ) + 1);
        return e;
    }
};

namespace
{
    enum colors
    {
        white,
        red,
        green,
        blue
    };

    enum corners
    {
        topleft,
        topright,
        bottomleft,
        bottomright
    };

    using namespace enum_operator
};

Upvotes: -1

PaperBirdMaster
PaperBirdMaster

Reputation: 13320

Don't use operator ++! What are you supposed to do if you have an enum like this?:

enum class other_enum : int
{
    low = -3000,
    fabada_asturiana = 0xfabada,
    answer_to_life_universe_everything = 0b101010,
    high = -low
};

As you can see, the values are not reachable increasing by one the previous one and don't even have a pattern; use iterators instead. Based on this answer:

// Shortcut to the enum map.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;

// The enum map.
template <typename ENUM>
enum_map<ENUM> enum_values{};

// Empty function to end the recursion.
void initialize() {}

// Initialize the enum map.
template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
    enum_values<ENUM>.emplace(value, name);
    initialize(tail ...);
}

// Obtain the begin iterator to the enum
template <class ENUM, class = std::enable_if_t<std::is_enum<ENUM>{}>>
auto begin(ENUM &)
{
    return enum_values<ENUM>.begin();
}

// Obtain the end iterator to the enum
template <class ENUM, class = std::enable_if_t<std::is_enum<ENUM>{}>>
auto end(ENUM &)
{
    return enum_values<ENUM>.end();
}

The code above allows the user to create a map with enum values and a description string, it can be used like this:

int main()
{
    initialize
    (
        white, "White",
        red,   "Red",
        green, "Green",
        blue,  "Blue",

        topleft,     "Top Left",
        topright,    "Top Right",
        bottomleft,  "Bottom Left",
        bottomright, "Bottom Right",

        other_enum::low, "Lowest value",
        other_enum::fabada_asturiana, "Typical Spanish",
        other_enum::answer_to_life_universe_everything, "42",
        other_enum::high, "Higher value"
    );

    ...
    return 0;
}

But the initialize call is mandatory in order to make the whole thing work; isn't needed to put all the enums in one call though.

With all the code above, we can iterate the enums this way (Live demo):

for (const auto &v : colors{})
    std::cout << v.first << '\n'; // Print values
for (const auto &v : corners{})
    std::cout << v.second << '\n'; // Print names
for (const auto &v : other_enum{})
    std::cout << (int)v.first << " = " << v.second << '\n'; // Print values & names

With this approach you can avoid the inheritance and work with the types directly. If you need to preserve the enum members order you can change the enum_map to a container which preserves order or if you don't need to associate the value to a string the underlying container can be changed to a vector or a list.

Upvotes: 0

Quentin
Quentin

Reputation: 63124

Building on 101010's and wowofbob's answers :

template <class E, class = std::enable_if_t<std::is_enum<E>{}>>
E &operator ++ (E &e) {
    return e = static_cast<E>(
        static_cast<std::underlying_type_t<E>>(e) + 1
    );
}

I've SFINAE'd away the operator for everything that is not an enum, and added proper static_casting so that it works on enum classes too.

Live on Coliru

In case you don't want to grant all enums under the sun the ability to be incremented, you can nest this operator in a namespace (let's say ::incr_enum), and then you can either :

  • Declare your enums in that namespace as 101010 recommends, in which case the operator is found via ADL;
  • using namespace incr_enum; when you want to import and use that operator in a local scope.

Upvotes: 5

Dimitrios Bouzas
Dimitrios Bouzas

Reputation: 42899

You can't inherit a class from an enum or enum class. In my humble opinion the best you can do is define your template overloaded operator as a free function and put it and all of the enums that you want to work with it in namespace (e.g., fancy) and let name lookup do the rest:

namespace fancy {

enum colors  { white, red, green, blue };
enum corners { topleft, topright, bottomleft, bottomright };

template<typename E>
E& operator++(E &e) {
  e = static_cast<E>(static_cast<int>(e) + 1);
  return e;
}

} // end of namespace fancy

This way you'd restrict your operator for working only with the stuff you have in your namespace.

Live Demo

Upvotes: 1

user2556165
user2556165

Reputation:

You don't really need a classes here.

This works fine:

#include <assert.h>

enum colors
{
    white,
    red,
    green,
    blue
};

enum corners
{
    topleft,
    topright,
    bottomleft,
    bottomright
};


template<class Enum>
Enum next(Enum e) {
    // To cast back to Enum from `int`
    return static_cast<Enum>(e + 1);
}

int main() {

    colors c = white;
    colors d = next(c);
    assert(c == white);
    assert(d == red);

}

Upvotes: 0

Related Questions