如何在类范围中定义/特化type_trait?

Exc*_*ius 7 c++ enums flags type-traits

我有以下情况:我的问题围绕使用强类型枚举类作为标志(就像在C#中使用Flags-Attribute一样).我知道这不是枚举类首先使用的方式,但这不是这个问题的重点.

我已经定义了几个要在这些枚举类上使用的运算符和函数,以及一个自定义类型特征,用于区分常规枚举和Flag-enums.这是一个例子:

// 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));
};
Run Code Online (Sandbox Code Playgroud)

现在如果我定义任何enum class我可以做以下事情:

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
}
Run Code Online (Sandbox Code Playgroud)

上面的代码工作正常,并使用宏进行模板专业化,它几乎看起来像非常方便的C#Flags-Attribute.

但是,当enum class在命名空间范围中未定义时,我遇到了一个问题:

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 {};
}
Run Code Online (Sandbox Code Playgroud)

类型特征不能在这里专门化.我需要在X之外定义特征,这是可能的,但是将"Flag-Attribute"与枚举声明分开.在我们的代码中使用它会很好,因为标志在整个地方使用,但是以一种相当老式的方式(int+ #define).到目前为止,我发现这个问题的所有解决方案都集中在类而不是枚举上,其中解决方案更简单,因为我可以将特征定义为类本身的成员.但是,枚举不能继承,包含typedef或者将某个枚举类与另一个枚举类区分开来可能需要的任何东西.

那么是否有可能在类范围中定义某种特征,可以在全局命名空间范围内使用它来识别特殊的枚举类类型?

编辑:我应该补充说我正在使用Visual Studio 2013.

更新:感谢您的答案,标签解决方案非常有效,尽管我必须做出微妙的改变(在此过程中使其变得更加简单).我现在正在使用这种自定义类型特征:

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;
};
Run Code Online (Sandbox Code Playgroud)

现在,我需要做的就是添加Flags到枚举类中,无论它在什么范围内:

enum class Foo { Flags, A = 0x0001, B = 0x0002 };
Run Code Online (Sandbox Code Playgroud)

另请参阅此处了解类似的问题和解决方案.

更新2:从Visual Studio 2013 Update 2开始,当is_flags特征应用于ios-base标头时,此解决方案将导致编译器崩溃.因此,我们现在使用一种不同的,更清晰的方法,我们使用一个模板类作为a的存储,enum class并定义所有运算符本身,没有任何类型特征魔术.可以enum class使用底层类型隐式创建模板类,使用底层类型创建模板类.有魅力,而且更enable_if不用了.

小智 2

您可以标记枚举本身:

\n\n
#include <type_traits>\n\ntemplate<typename T>\nstruct is_flags {\n    private:\n    typedef typename std::underlying_type<T>::type integral;\n    template<integral> struct Wrap {};\n\n    template<typename U>\n    static constexpr std::true_type check(Wrap<integral(U::EnumFlags)>*);\n\n    template<typename>\n    static constexpr std::false_type check(...);\n\n    typedef decltype(check<T>(0)) result;\n\n    public:\n    static constexpr bool value = std::is_enum<T>::value && result::value;\n};\n\nnamespace Detail {\n    template <bool>\n    struct Evaluate;\n\n    template <>\n    struct Evaluate<true> {\n        template <typename T>\n        static T apply(T a, T b) { return T(); }\n    };\n}\n\ntemplate <typename T>\nT evalueate(T a, T b)\n{\n    return Detail::Evaluate<is_flags<T>::value>::apply(a, b);\n}\n\nenum class E{ A = 1, B, C };\nstruct X {\n    enum class F{ EnumFlags, A = 1, B, C };\n};\n\nint main ()\n{\n    // error: incomplete type \xe2\x80\x98Detail::Evaluate<false>\xe2\x80\x99 used in nested name specifier\n    // evalueate(E::A, E::B);\n    evalueate(X::F::A, X::F::B);\n}\n
Run Code Online (Sandbox Code Playgroud)\n