【问题标题】:How do I define / specialize a type_trait in class scope?如何在类范围内定义/专门化 type_trait?
【发布时间】:2014-01-10 14:06:04
【问题描述】:

我有以下情况: 我的问题围绕着使用强类型枚举类作为标志(就像在带有标志属性的 C# 中一样)。我知道这不是 enum 类最初的使用方式,但这不是这个问题的重点。

我已经定义了几个用于这些枚举类的运算符和函数,以及一个自定义类型特征来区分普通枚举和标志枚举。这是一个例子:

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

现在,如果我定义任何 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
}

上面的代码工作正常,并且使用宏来进行模板特化,它看起来就像非常方便的 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 {};
}

类型特征不能在这里特化。我需要在 X 之外定义特征,这是可能的,但将“标志属性”与枚举声明分开。在我们的代码中使用它会非常好,因为标志在所有地方都使用但以一种相当老式的方式(int + #define)。到目前为止,我发现这个问题的所有解决方案都集中在类而不是枚举上,其中解决方案要简单得多,因为我可以将 trait 定义为类本身的成员。然而,枚举不能继承、包含 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;
};

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

enum class Foo { Flags, A = 0x0001, B = 0x0002 };

有关类似问题和解决方案,另请参阅 here

更新 2: 自 Visual Studio 2013 更新 2 起,当 is_flags 特征应用于 ios-base 标头时,此解决方案将导致编译器崩溃。因此,我们现在使用一种不同且更简洁的方法,我们使用一个模板类作为enum class 的存储,并在其自身上定义所有运算符,而无需任何类型特征魔法。模板类可以用底层enum class 隐式创建,也可以用底层类型显式创建。很有魅力,不像enable_if-mess。

【问题讨论】:

标签: c++ enums flags typetraits


【解决方案1】:

您可以标记枚举本身:

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

【讨论】:

  • 这看起来很有希望,我会尽快尝试一下。我的编译器还不支持constexpr,但我想这并不重要。但是,当我尝试使用 dyps 代码时,我遇到了 decltype 的一些编译器问题,我希望它适用于这种情况。
  • 谢谢,这很好用,请参阅我的更新问题和我的解决方案。不过我遇到了一个问题,Wrap 结构引起了问题,但这可能是由于 VS2013 没有很好地处理 decltype(在尝试其他解决方案时出现了几次编译器崩溃)。但是在我在最终解决方案中提供的链接中,有人提出了一种类似的方法,根本不使用Wrap,这似乎可行,而且更短。
【解决方案2】:

这是一个使用 ADL 而不是 trait 的丑陋解决方案(当然你可以将 ADL 隐藏在 trait 中):

新的运算符模板:

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

is_flagsBar 的定义:

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

(最好为 ADL 使用比 is_flags 更独特的名称)

【讨论】:

  • 嗯,这仍然需要两个不同的宏,因为 friend 必须在类中使用。
  • 我同意,如果能找到一种方法让 1 个宏仅用作某种“属性”,那就太好了。但我发现这个解决方案已经比我最初的方法更好,因为枚举和它可以用作标志的事实在同一个地方。
  • 这可能是一个 VS2013 错误,但是当我尝试这个时,我是否定义朋友并不重要,它在任何情况下都有效。看起来 std::is_same 总是返回 true,尽管我不知道为什么。
  • 再次感谢您的意见,我学到了一些关于 ADL 的有趣的东西,但 Dieter Lückings 的回答要灵活得多,甚至无需使用宏即可提供更好的可读性。
猜你喜欢
  • 2013-09-25
  • 2014-08-13
  • 1970-01-01
  • 2015-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-31
相关资源
最近更新 更多