23

Is there a type trait, or is it possible to write a type trait is_scoped_enum<T> such that:

  • if T is a scoped enumeration, is_scoped_enum<T>::value is true and
  • if T is any other type, is_scoped_enum<T>::value is false
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • By *scoped enum*, you meant C++11's enum? – Nawaz May 23 '12 at 17:17
  • 3
    @Nawaz: A scoped enum is what an `enum class` is called in C++11, yes. – James McNellis May 23 '12 at 17:18
  • Just out of curiosity, what practical applications are there for this one? – Xeo May 24 '12 at 18:43
  • 2
    @Xeo: I am overloading the bitwise operators for a set of scoped enumeration types. – James McNellis May 24 '12 at 21:08
  • https://bitbucket.org/martinhofernandes/wheels/src/353fc67489dc/include/wheels/enums.h%2B%2B – R. Martinho Fernandes May 24 '12 at 21:15
  • @R.MartinhoFernandes: Ha; that looks familiar. :-) http://cxxreflect.codeplex.com/SourceControl/changeset/view/6963d00d7608#CxxReflect%2fFundamentalUtilities.hpp (search for `CXXREFLECT_GENERATE_SCOPED_ENUM_OPERATORS`) Unfortunately, Visual C++ does not support `constexpr`, which makes scoped enums quite painful to use for flags, but at least with the operators it's _better_. I like your traits usage, though. For flags, I've been using a `flags` class type to wrap the enumeration. – James McNellis May 24 '12 at 21:49
  • With c++23 this will be in the standard. If you can use c++17 or c++20 you can implement it in a shorter way than in R. Martinho Fernandes answer. cppreference has an example implementation for c++20: https://en.cppreference.com/w/cpp/types/is_scoped_enum – Janek Beck Jan 07 '21 at 15:52

2 Answers2

35

I think testing if it is an enum and not implicitly convertible to the underlying type should do the trick.

template <typename T, bool B = std::is_enum<T>::value>
struct is_scoped_enum : std::false_type {};

template <typename T>
struct is_scoped_enum<T, true>
: std::integral_constant<bool,
    !std::is_convertible<T, typename std::underlying_type<T>::type>::value> {};
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • 2
    Better use `std::underlying_type` instead of `int`. An `enum class` in C++11 can base on something not convertible to `int`. – kennytm May 23 '12 at 17:21
  • @KennyTM: What type? C++11 §7.2/2 states "The _enum-base_ shall name an integral type;" is there an integral type not convertible to `int`? – James McNellis May 23 '12 at 17:33
  • @JamesMcNellis: You're correct. Sorry for confusion. (I was thinking of `is_convertible` doing implicit conversion.) – kennytm May 23 '12 at 17:42
  • This is a good solution, except that with the latest edit, the logic is now backwards: `::value` is true if `T` is an unscoped enumeration. :-) – James McNellis May 23 '12 at 17:51
  • and one more correction, it should be !std::is_convertible<...>::value – Gene Bushuyev May 23 '12 at 18:04
  • I would like to add (because I've had this issue): This trait could also be implemented as one `using` statement (template alias) as suggested [here](http://stackoverflow.com/questions/15586163/c11-type-trait-to-differentiate-between-enum-class-and-regular-enum), but then `std::underlying_type` will be evaluated for all types, and some compilers generate an error when using `underlying_type` with non-enum types (no SFINAE). So this solution is probably the best one. – Excelcius Jan 14 '14 at 10:47
  • 3
    @Excelcius It's worse than the possibility of getting an error or not: using `std::underlying_type` with a non-enum type is undefined behaviour, which requires no diagnostic, so the compiler need not report the misuse, which then gives your program UB. I didn't look at common implementations but presume the Standard allows UB to facilitate easier implementation in valid use cases. – underscore_d Jun 10 '18 at 10:09
0

C++23 will be providing is_scoped_enum which can be used once it gets implemented. See this link for documentation: is_scoped_enum. I don't think clang supports this yet (see clang status for the latest info of what features are supported).

For now I am using a slightly simplified version of the answer above (using _v and _t) as well as an implementation of is_underlying:

// C++ 23 should include 'is_scoped_enum' and 'to_underlying' so the following
// code can be removed:

template<typename T, bool B = std::is_enum_v<T>>
struct is_scoped_enum : std::false_type {};

template<typename T>
struct is_scoped_enum<T, true>
  : std::integral_constant<
      bool, !std::is_convertible_v<T, std::underlying_type_t<T>>> {};

template<typename T>
inline constexpr bool is_scoped_enum_v = is_scoped_enum<T>::value;

template<typename T, std::enable_if_t<is_scoped_enum_v<T>, int> = 0>
[[nodiscard]] constexpr auto to_underlying(T x) noexcept {
  return static_cast<std::underlying_type_t<T>>(x);
}
Dan H
  • 31
  • 1
  • 2
  • 7