【问题标题】:Template for type safe c++11 enum class flags类型安全 C++11 枚举类标志的模板
【发布时间】:2015-03-06 08:22:49
【问题描述】:

我正在尝试使用模板创建类型安全的 C++ 标志。我还想区分 a 标志和标志s(零个、一个或多个标志)。

下面的解决方案效果很好,除了EnumFlag<T> operator | (T, T),它会导致枚举上的所有|-操作返回类型EnumFlag。这破坏了很多代码。有什么技巧可以解决这个问题吗?在我的代码中,我执行以下操作,但是在这里硬编码Option 不是一种选择。如何使这个通用?

EnumFlag<typename std::enable_if<std::is_same<T, Option>::value, T>::type> operator | (T l, T r)

将其更改为...

EnumFlag<T> operator | (T l, T r)

...原因破坏了一切。我想要这样的东西(不是 compilabel 代码)。或者任何其他更好的主意!

EnumFlag<typename std::enable_if<std::already_expanded<EnumFlag<T>>::value, T>::type> operator | (T l, T r)

完整的可编译代码:

EnumFlag.h

#ifndef __classwith_flags_h_
#define __classwith_flags_h_

#include <type_traits>

enum class Option
{
    PrintHi = 1 << 0,
    PrintYo = 1 << 1,
    PrintAlot = 1 << 2
};

template <typename T>
class EnumFlag
{
public:
    using UnderlayingType = typename std::underlying_type<T>::type;

    EnumFlag(const T& flags)
        : m_flags(static_cast<UnderlayingType>(flags))
    {}

    bool operator & (T r) const
    {
        return 0 != (m_flags & static_cast<UnderlayingType>(r));
    }

    static const T NoFlag = static_cast<T>(0);

private:
    UnderlayingType  m_flags;
};
template<typename T>
EnumFlag<typename std::enable_if<std::is_same<T, Option>::value, T>::type> operator | (T l, T r)
{
    return static_cast<T>(static_cast<typename EnumFlag<T>::UnderlayingType>(l) | static_cast<typename EnumFlag<T>::UnderlayingType>(r));
}

class ClassWithFlags
{
public:
    using Options = EnumFlag < Option >;

    void doIt(const Options &options);
};

#endif

EnumFlag.cpp

#include "EnumFlag.h"

#include <iostream>

void ClassWithFlags::doIt(const Options &options)
{
    if (options & Option::PrintHi)
    {
        std::cout << "Hi" << std::endl;
    }
    if (options & Option::PrintYo)
    {
        std::cout << "Yo!" << std::endl;
    }
}

int main()
{
    ClassWithFlags classWithFlags;
    classWithFlags.doIt(Option::PrintHi | Option::PrintAlot);
}

> DEMO

实际代码会包含更多的运算符,但这足以说明问题。

一个不那么侵入性的解决方案是这样的(但仍然太侵入性)

template<typename T>
typename std::underlying_type<T>::type operator | (T l, T r)
{
    return (static_cast<typename std::underlying_type<T>::type>(l) | static_cast<typename std::underlying_type<T>::type>(r));
}

不够神,那么EnumFlag(const std::underlying_type&lt;T&gt; &amp;flags) 必须存在,我失去了安全性。另外,我希望只为实际需要的类型创建全局运算符重载。宏也不是神,因为我希望允许在类中声明 EnumFlags。全局重载不能存在,因此我需要在不同位置调用两个宏以在 EnumFlag 上创建。

解决方案必须是纯C++11/stl。

【问题讨论】:

  • 不会添加从EnumFlag&lt;T&gt;T 的非显式转换来解决由EnumFlag&lt;T&gt; operator | (T l, T r) 引起的问题吗?
  • 我认为这是一种解决方法,在不需要时创建 EnumFlag 实例,只是为了转换回来。这也将允许使用Options 的实例调用foo(const Option &amp;o)。因此,丢失类型安全。在foo 内部,我们可能有一个处理所有情况的开关,但仍然不会发生任何事情......
  • std::bitset 拿着你的旗帜怎么样?

标签: c++ templates c++11 enums type-safety


【解决方案1】:

Anthony Williams 有一个很好的文章和现成的代码:"Using Enum Classes as Bitfields"

也许这正是您所寻找的。与我看到的其他解决方案不同,这里没有使用宏 - 只是纯模板。

演示他的解决方案的简单示例:

#include "bitmask_operators.hpp"

enum class A{
    x=1,y=2
       };

enum class B:unsigned long {
    x=0x80000000,y=0x40000000
        };

template<>
struct enable_bitmask_operators<A>{
    static const bool enable=true;
};

template<>
struct enable_bitmask_operators<B>{
    static const bool enable=true;
};

enum class C{x,y};


int main(){
    A a1=A::x | A::y;
    A a2=a1&A::y;
    a2^=A::x;
    A a3=~a1;

    B b1=B::x | B::y;
    B b2=b1&B::y;
    b2^=B::x;
    B b3=~b1;

    // C c1=C::x | C::y;
    // C c2=c1&C::y;
    // c2^=C::x;
    // C c3=~c1;
}

bitmask_operators.hpp source code.

【讨论】:

  • 这是一个很好的链接,但我对必须为每个标志编写新的模板规范并不完全满意。但它给了我一些想法(但他们失败了)。像这样: ... class EnumFlag : is_enum_flag ... EnumFlag::value, T>::type> operator | (T l, T r) ...
【解决方案2】:

这是我的做法,希望对你有用。

主要问题是如何区分枚举标志和所有其他类型。我会使用额外的模板类型,例如flag_type,这将有一个成员 value 仅用于我们的枚举标志:

template<typename T>
typename flag_type<T>::value operator | (T, T)
{
    /* implementation */
}

默认flag_type 为空

template<typename T>
struct flag_type {};

而对于枚举标志,它将包含从operator| 返回的EnumFlag 类型。这是一个宏:

#define DECLARE_FLAG_TYPE(__type)                             \
template<>                                                    \
struct flag_type<__type> { using value = EnumFlag<__type>; }

然后,我们可以使用它在外部范围和类中定义枚举标志:

enum class Option
{
    One   = 1 << 0,
    Two   = 1 << 1,
    Three = 1 << 2
};
DECLARE_FLAG_TYPE(Option);

class Class
{
public:
    enum class Option
    {
        Four = 1 << 0,
        Five = 1 << 1
    };
};
DECLARE_FLAG_TYPE(Class::Option);

【讨论】:

  • 谢谢,这是对我编写的宏的一个很好的改进。有用! :D 但是,我仍然不喜欢在课堂外宣布国旗。我认为需要一个纯模板解决方案。
【解决方案3】:

此解决方案根本不需要任何额外的宏\使用枚举类声明:

enum class MyEnum { Value1 = 1 << 0, Value2 = 1 << 1 };
using MyEnums = flags<MyEnum>; // actually this line is not necessary

auto mask = Value1 | Value2; // set flags Value1 and Value 2
if (mask & Value2) { // if Value2 flag is set
    doSomething();
}

https://github.com/grisumbras/enum-flags

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多