7

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.

Community
  • 1
  • 1
Excelcius
  • 1,680
  • 1
  • 14
  • 31
  • Related: [How does one use an enum class as a set of flags?](http://stackoverflow.com/q/18553843/420683) – dyp Jan 10 '14 at 14:52
  • You could use a `constexpr` function and ADL instead of traits. It's probably ugly, but would allow defining a `friend` function inside `struct X`. – dyp Jan 10 '14 at 14:54
  • @dyp Sounds nice but unfortunately I'm using VS2013, which doesn't support `constexpr` yet. I added the compiler to the question. – Excelcius Jan 10 '14 at 14:58
  • 1
    Then you can still use `std::true_type` and `std::false_type` (or better yet: a unique type) and a `decltype` to invoke ADL. – dyp Jan 10 '14 at 15:00
  • Also related: [How to make enum class to work with the 'bit-or' feature?](http://stackoverflow.com/q/18803940) – Konrad Rudolph Jan 10 '14 at 15:19
  • It might also be possible to use a C++03-fake enum class (a custom struct) that defines these operators. Something like `struct 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); };` – dyp Jan 10 '14 at 15:24
  • @dyp Yes, but I wanted to stick to the enums because of the way they allow enumerators to be defined. Maybe I focused the question too much on enums, basically I want to find a easy-to-use mechanism for defining an attribute for any type, scoped in a class or not, to be used for type_trait-checking (or some other lookup-mechanism suitable). However, most types provide suitable workarounds, that's why I chose to explain the problem using enums. The issue with enums is that they themselve cannot contain any information other than the values (and underlying type). – Excelcius Jan 10 '14 at 15:34

2 Answers2

2

Here's an ugly solution using ADL instead of traits (of course you can hide the ADL inside the trait):

New operator template:

struct my_unique_enum_flag_type;

// Example operator to use enum class as flags
template <typename T>
enable_if_t<std::is_enum<T>::value
            && std::is_same<decltype(is_flags(std::declval<T>())),
                            my_unique_enum_flag_type>::value, T&>
operator|=(T &t1, const T t2)
{
    return t1 = static_cast<T>(static_cast<underlying_type_t<T>>(t1) | 
                               static_cast<underlying_type_t<T>>(t2));
};

Definition of is_flags for Bar:

struct X
{
    enum class Bar { A = 1, B = 2 };

    friend my_unique_enum_flag_type is_flags(Bar);
};

int main()
{
    X::Bar a = X::Bar::A;
    a |= X::Bar::B;
}

(preferably, use a more unique name than is_flags for ADL)

dyp
  • 38,334
  • 13
  • 112
  • 177
  • Hmm that still requires two different macros, since `friend` must be used inside classes. – dyp Jan 10 '14 at 15:08
  • I agree, it would be nice to find a way to have 1 macro only to serve as an "attribute" of sorts. But I find this solution already better than my initial approach since the enum and the fact that it can be used as flags is in the same place. – Excelcius Jan 10 '14 at 15:15
  • This might be a VS2013-bug but when I try this it doesn't matter if I define the friend or not, it works in any case. It looks like ths std::is_same always returns true although I can't figure out why. – Excelcius Jan 10 '14 at 15:44
  • 1
    Thanks again for your input, I learned some interesting things about ADL, but Dieter Lückings answer is far more flexible and provides better readability without even using macros. – Excelcius Jan 10 '14 at 21:10
2

You could tag the enumeration itself:

#include <type_traits>

template<typename T>
struct is_flags {
    private:
    typedef typename std::underlying_type<T>::type integral;
    template<integral> struct Wrap {};

    template<typename U>
    static constexpr std::true_type check(Wrap<integral(U::EnumFlags)>*);

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<T>(0)) result;

    public:
    static constexpr bool value = std::is_enum<T>::value && result::value;
};

namespace Detail {
    template <bool>
    struct Evaluate;

    template <>
    struct Evaluate<true> {
        template <typename T>
        static T apply(T a, T b) { return T(); }
    };
}

template <typename T>
T evalueate(T a, T b)
{
    return Detail::Evaluate<is_flags<T>::value>::apply(a, b);
}

enum class E{ A = 1, B, C };
struct X {
    enum class F{ EnumFlags, A = 1, B, C };
};

int main ()
{
    // error: incomplete type ‘Detail::Evaluate<false>’ used in nested name specifier
    // evalueate(E::A, E::B);
    evalueate(X::F::A, X::F::B);
}
  • This looks promising, I'll give it a try as soon as possible. My compiler doesn't support `constexpr` yet, but I guess it won't matter. However when I tried dyps code I ran into some compiler issues with decltype, I hope it works in this case. – Excelcius Jan 10 '14 at 18:20
  • Thanks, this works really well, see my updated question with my solution. I had one issue though, the `Wrap` struct caused problems but that may be due to VS2013 not handling decltype very well (had several compiler crashes while trying other solutions). But in the link I provided in the final solution someone suggested a similar approach without using `Wrap` at all and this seems to work and is even shorter. – Excelcius Jan 10 '14 at 21:08