如何为枚举创建模板运算符

How to create a template operator for enums

我需要一个没有宏魔法的通用模板 class 我可以像这样使用它:

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
};

是否可以使用可变参数模板或其他方式?我怎样才能做到这一点?

这里你真的不需要 类。

这很好用:

#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);

}

您不能从 enumenum class 继承 class。在我看来,你能做的最好的事情就是将你的模板重载运算符定义为一个自由函数,并将它和你想要使用它的所有 enum 放在命名空间中(例如,fancy)让名称查找完成剩下的工作:

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

这样你就可以限制你的操作员只使用你的命名空间中的东西。

Live Demo

基于 's and 的回答:

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
    );
}

我已经将 SFINAE 去掉了所有非枚举的运算符,并添加了适当的 static_casting 以便它也适用于 enum classes。

Live on Coliru

如果您不想授予所有 enum s under the sun 递增的能力,您可以将此运算符嵌套在命名空间中(比方说 ::incr_enum),然后你可以:

  • 按照 101010 的建议在该命名空间中声明您的枚举,在这种情况下,可以通过 ADL 找到运算符;
  • using namespace incr_enum; 当您想在本地范围内导入和使用该运算符时。

不要使用operator ++!如果你有这样的枚举,你应该怎么做?:

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

如您所见,这些值无法从前一个增加一个,甚至没有模式;改用迭代器。基于 :

// 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();
}

上面的代码允许用户创建带有枚举值和描述字符串的地图,可以这样使用:

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;
}

但是 initialize 调用是强制性的,以便使整个事情正常进行;不过,不需要将所有枚举放在一次调用中。

使用上面的所有代码,我们可以这样迭代枚举 (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

通过这种方法,您可以避免继承并直接使用类型。如果您需要保留枚举成员顺序,您可以将 enum_map 更改为保留顺序的容器,或者如果您不需要将值关联到字符串,则底层容器可以更改为向量或列表.

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
};
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 = {};
}

为了使您的枚举 supports_plus_plus_t<E> 成为 true_type,只需将 std::true_type supports_plus_plus< tag_t<E> >(); 写入与您的枚举 E 相同的名称空间中。 ADL 完成剩下的工作。

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
    );
  }
}

要使用 ++,您必须:

  • 在枚举 E.

  • 的命名空间中键入 std::true_type supports_plus_plus< tag_t<E> >();
  • using namespace ::plus_plus; 您要在 enum 上调用 ++ 的位置。 这将 ++ 的使用限制为支持它的枚举并处理运算符查找问题。 (来自@csbako 的 ++ 正文)。

你可以做一个宏:

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

它采用枚举名称(我使用 va args,因为 C++ 枚举名称可能在 <> 之间嵌入了 ,,这是宏不喜欢的)。

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;
}