I have the following situation: My problem revolves around using strongly typed enum classes as flags (just as in C# with the Flags-Attribute). I know this is not the way enum classes were meant to be used in the first place, but that it not the point of this question.
I have defined several operators and functions to use on these enum classes, and a custom type trait to distinguish normal enums from Flag-enums. Here's an example:
// Default type_trait which disables the following operators
template <typename T> struct is_flags : std::false_type {};
// Example operator to use enum class as flags
template <typename T>
std::enable_if_t<std::is_enum<T>::value && is_flags<T>::value, T&>
operator|=(T &t1, const T t2)
{
return t1 = static_cast<T>(static_cast<std::underlying_type_t<T>>(t1) |
static_cast<std::underlying_type_t<T>>(t2));
};
Now if I define any enum class
i can do the following:
enum class Foo { A = 1, B = 2 };
enum class Bar { A = 1, B = 2 };
// Declare "Bar" to be useable like Flags
template <> struct is_flags<Bar> : std::true_type {};
void test()
{
Foo f;
Bar b;
f |= Foo::A; // Doesn't compile, no operator |=
b |= Bar::A; // Compiles, type_trait enables the operator
}
The above code works fine and using a macro for the template specialization it almost looks like the very convenient C# Flags-Attribute.
However, when the enum class
is not defined in namespace scope, I run into an issue:
struct X
{
enum class Bar { A = 1, B = 2 };
// Following line gives: C3412: Cannot specialize template in current scope
template <> struct is_flags<Bar> : std::true_type {};
}
The type trait cannot be specialized here. I would need to define the trait outside of X, which is possible, but separates the "Flag-Attribute" from the enum declaration. It would be so nice to use this in our code since flags are used all over the place but in a rather old-fashioned manner (int
+ #define
). All solutions to this problem I have found so far focus on classes instead of enums, where the solution is much simpler, since I can define the trait as a member of the class itself. Enums, however, cannot inherit, contain typedefs or whatever might be needed to differentiate a certain enum class from another.
So is there any possibility to define some kind of trait in a class-scope which can be used in global namespace scope to recognize special enum class-types?
EDIT: I should add that I'm using Visual Studio 2013.
UPDATE: Thanks for the answers, the tag-solution worked really well, although I had to make a subtle change (making it even more simple in the process). I'm now using this custom type trait:
template <typename T>
struct is_flags
{
private:
template <typename U> static std::true_type check(decltype(U::Flags)*);
template <typename> static std::false_type check(...);
typedef decltype(check<T>(0)) result;
public:
static const bool value = std::is_enum<T>::value && result::value;
};
Now, all I need to do is add Flags
to the enum class, no matter what scope it's in:
enum class Foo { Flags, A = 0x0001, B = 0x0002 };
See also here for a similar problem and solution.
UPDATE 2: Since Visual Studio 2013 Update 2 this solution will cause compiler crashes when the is_flags
trait is applied to ios-base headers. Therefore we are now using a different and cleaner approach, we use a template class which acts as the storage for an enum class
and defines all operators on itself without any type-trait magic. The template class can be created implicit with the underlying enum class
and explicit with the underlying type. Works a charm and is much less of an enable_if
-mess.
constexpr
function and ADL instead of traits. It's probably ugly, but would allow defining afriend
function insidestruct X
. – Authoritarianconstexpr
yet. I added the compiler to the question. – Gavstd::true_type
andstd::false_type
(or better yet: a unique type) and adecltype
to invoke ADL. – Authoritarianstruct my_enum{ struct enumerator{ constexpr enumerator(int p) : m(p) {} int m; }; constexpr static enumerator A = 1, B = 2; my_enum(enumerator); my_enum& operator |=(my_enum); };
– Authoritarian