用户不得为枚举成员定义任何显式值。
下面是“库文件”(1个标头),后面是提供用法示例的测试程序。
#include <iostream>
#include <bitset>
#include <cassert>
/**
* FlagSet implements a bitset usable with `enum` and `enum class`.
*
* It provide a typesafe interface for manipulating the bitset. This helps
* prevents mistake as the various operator and function will refuse a
* parameter that doesn't match the expected enum type.
*
* A flagset supports one user-defined enumeration. The number of flags
* (ie the member of the user enumeration) is not limited, as the underlying
* bitset (std::bitset) can have an arbitrary large size.
*
* REQUIREMENTS:
* * This code source required C++14 to compile.
* * The user enumeration shall not explicitely set any value.
* * The last enumeration member shall be: "__SENTINEL__"
*
*/
template<typename T>
struct FlagSet
{
FlagSet() = default;
FlagSet(const FlagSet &o) : bitset(o.bitset) {}
FlagSet &operator|=(const T &val)
{
set_true(val);
return *this;
}
FlagSet &operator&=(const T &val)
{
bool tmp = bitset[static_cast<utype>(val)] ? true : false;
bitset.reset();
bitset[static_cast<utype>(val)] = tmp;
return *this;
}
FlagSet &operator|=(const FlagSet &o)
{
bitset |= o.bitset;
return *this;
}
FlagSet &operator&=(const FlagSet &o)
{
bitset &= o.bitset;
return *this;
}
FlagSet &operator=(const FlagSet &o)
{
bitset = o.bitset;
}
/**
* Return a bitset containing the result of the
* bitwise AND between *this and val.
*
* The resulting bitset can contain at most 1 bit.
*/
FlagSet operator&(const T&val)
{
FlagSet ret(*this);
ret &= val;
assert(ret.bitset.count() <= 1);
return ret;
}
/**
* Perform a AND binary operation between *this and
* `val` and return the result as a copy.
*/
FlagSet operator&(const FlagSet &val)
{
FlagSet ret(*this);
ret.bitset &= val.bitset;
return ret;
}
/**
* Return a bitset containing the result of the
* bitwise OR between *this and val.
*
* The resulting bitset contains at least 1 bit.
*/
FlagSet operator|(const T&val)
{
FlagSet ret(*this);
ret |= val;
assert(ret.bitset.count() >= 1);
return ret;
}
/**
* Perform a OR binary operation between *this and
* `val` and return the result as a copy.
*/
FlagSet operator|(const FlagSet &val)
{
FlagSet ret(*this);
ret.bitset |= val.bitset;
return ret;
}
FlagSet operator~()
{
FlagSet cp(*this);
cp.bitset.flip();
return cp;
}
/**
* The bitset evaluates to true if any bit is set.
*/
operator bool() const
{
return bitset.any();
}
/**
* Below are the method from std::bitset that we expose.
*/
bool operator==(const FlagSet &o) const
{
return bitset == o.bitset;
}
std::size_t size() const
{
return bitset.size();
}
std::size_t count() const
{
return bitset.count();
}
FlagSet &set()
{
bitset.set();
return *this;
}
FlagSet &reset()
{
bitset.reset();
return *this;
}
FlagSet &flip()
{
bitset.flip();
return *this;
}
FlagSet &set(const T &val, bool value = true)
{
bitset.set(static_cast<utype>(val), value);
return *this;
}
FlagSet &reset(const T&val)
{
bitset.reset(static_cast<utype>(val));
return *this;
}
FlagSet &flip(const T &val)
{
bitset.flip(static_cast<utype>(val));
return *this;
}
bool operator[](const T&val)
{
return bitset[static_cast<utype>(val)];
}
/**
* Overload for std::ostream
*/
friend std::ostream& operator<<(std::ostream& stream, const FlagSet& me)
{
return stream << me.bitset;
}
private:
using utype = std::underlying_type_t<T>;
std::bitset<static_cast<utype>(T::__SENTINEL__)> bitset;
void set_true(const T&val)
{
bitset[static_cast<utype>(val)] = 1;
}
};
/**
* Provide a free operator allowing to combine two enumeration
* member into a FlagSet.
*/
template<typename T>
std::enable_if_t<std::is_enum<T>::value, FlagSet<T>>
operator|(const T &lhs, const T &rhs)
{
FlagSet<T> bs;
bs |= lhs;
bs |= rhs;
return bs;
}
示例文件:
#include "flagset.hpp"
/**
* Some random enum to use in tests.
*/
enum class Options : uint64_t
{
FULLSCREEN,
INVERT_MOUSE,
BLA,
RED_BACKGROUND,
RED_FOREGROUND,
__SENTINEL__
};
int test_AND()
{
FlagSet<Options> red(Options::RED_FOREGROUND | Options::RED_BACKGROUND);
auto ret = red & Options::RED_BACKGROUND;
assert(ret);
assert(ret.count() == 1);
ret = red & Options::RED_FOREGROUND;
assert(ret);
assert(ret.count() == 1);
ret = red & (Options::RED_FOREGROUND | Options::RED_BACKGROUND);
assert(ret);
assert(ret.count() == 2);
ret = ~red & Options::RED_BACKGROUND;
assert(ret == false);
assert(ret.count() == 0);
}
int test_OR()
{
FlagSet<Options> red;
red |= Options::RED_FOREGROUND | Options::RED_BACKGROUND;
assert(red.count() == 2);
FlagSet<Options> opt;
opt |= (Options::FULLSCREEN | Options::BLA);
// FULLSCREEN and BLA match, so this evaluates to true.
assert(opt & (Options::FULLSCREEN | Options::BLA | Options::RED_FOREGROUND | Options::RED_BACKGROUND));
// Ensure that a group of flag is set
FlagSet<Options> expected;
expected |= (Options::FULLSCREEN);
assert((opt & expected) == expected);
assert((opt & (Options::RED_FOREGROUND | Options::RED_BACKGROUND)) == false);
assert((opt & red) == false);
assert(!(opt & Options::INVERT_MOUSE));
opt |= ~red;
assert(opt & Options::INVERT_MOUSE);
}
int test_set_reset()
{
FlagSet<Options> opt;
assert(opt.count() == 0);
opt.set();
assert(opt.count() == opt.size() && opt.size() == 5);
opt.reset();
assert(opt.count() == 0);
opt.set(Options::BLA);
assert(opt.count() == 1 && opt[Options::BLA]);
opt.set(Options::BLA, false);
assert(opt.count() == 0);
}
int test_type_safety()
{
// The following will not compile.
FlagSet<Options> bs;
// bs & 42;
// bs &= 42;
// bs |= 42;
// bs | 42;
}
int main()
{
auto t = std::bitset<4>();
assert((int)Options::FULLSCREEN == 0);
test_AND();
test_OR();
test_set_reset();
}
我对几乎所有反馈都感兴趣:从实现细节到API以及命名(FlagSet是我能想到的最好的名字)。 br />
#1 楼
这看起来不错。我会提出以下建议。首先,您仅在一个地方使用
set_value
。这是一个非常不必要的功能,只需满足以下条件即可:FlagSet &operator|=(const T &val)
{
bitset.set(static_cast<utype>(val));
return *this;
}
其次,对于
&=
来说,表达式expr ? true : false
是一个反模式,可以仅用expr
代替。在这种情况下,您甚至不需要它,因为bitset
提供了test
。整个事情可能变成:FlagSet &operator&=(const T &val)
{
bool tmp = bitset.test(static_cast<utype>(val));
bitset.reset();
bitset.set(static_cast<utype>(val), tmp);
return *this;
}
第三,您缺少
return
:真的,因为这些都是琐碎的,所以很漂亮:FlagSet &operator=(const FlagSet &o)
{
bitset = o.bitset;
return *this; // <==
}
甚至不指定它们。
第四,更喜欢使
default
显式显示:FlagSet(const FlagSet& ) = default;
FlagSet& operator=(const FlagSet& ) = default;
这将防止编译诸如
operator bool
之类的怪异表达式。 >最后,FlagSet{} + 4
是C ++标准中的保留字。您可以改为要求将其命名为其他名称,例如__SENTINEL__
或LAST_VALUE
等。 评论
\ $ \ begingroup \ $
我还没有测试过,但是可以通过使用sizeof(std :: underlying_type_t
\ $ \ endgroup \ $
–马克·英格拉姆
18/12/18在11:29
\ $ \ begingroup \ $
@MarkIngram或sizeof(std :: underlying_type_t
\ $ \ endgroup \ $
–轨道轻赛
18/12/18在11:30
\ $ \ begingroup \ $
它也缺少一些const(例如operator |)
\ $ \ endgroup \ $
–轨道轻赛
18/12/18在11:51
\ $ \ begingroup \ $
最后,我建议添加从T到FlagSet
\ $ \ endgroup \ $
–轨道轻赛
18/12/18在12:31
\ $ \ begingroup \ $
如此处所建议,使用std :: bitset
\ $ \ endgroup \ $
– mrts
19-10-4在21:03
#2 楼
谢谢@Xaqq,这真的很棒! > #include "flag_set.hpp"
enum class Options : uint8_t {
FULLSCREEN,
INVERT_MOUSE,
FLASH,
RED_BACKGROUND,
RED_FOREGROUND,
_
};
int main()
{
flag_set<Options> red(Options::RED_FOREGROUND | Options::RED_BACKGROUND);
if (red & Options::RED_BACKGROUND)
cout << "Red background activated";
}
请注意,我选择
_
作为前哨,因为它很明显并且不太可能与实际枚举值。另外,我觉得遵循ISO标准库下划线样式命名是很自然的,因为这是类似于标准库类的通用实用程序类。资料库。 @Xaqq,如果您愿意,我可以为您提供资源库,因为您是合法的作者。 #3 楼
请注意, // Operator that combines two enumeration values into a flag_set.
template <typename T>
std::enable_if_t<std::is_enum<T>::value, flag_set<T>> operator|(const T& lhs, const T& rhs)
将
operator|
添加到所有枚举,包括那些没有预期的特殊哨兵。例如Qt的
Q_DECLARE_METATYPE()
宏。可以用
template <typename T, typename = void>
struct is_enum_that_contains_sentinel :
std::false_type {};
template <typename T>
struct is_enum_that_contains_sentinel<T, decltype(static_cast<void>(T::_))> :
std::is_enum<T> {};
// Operator that combines two enumeration values into a flag_set only if the
// enumeration contains the sentinel `_`.
template <typename T>
std::enable_if_t<is_enum_that_contains_sentinel<T>::value, flag_set<T>>
operator|(const T& lhs, const T& rhs)
{
...
修复。 >(基于JonathanMüller的代码,可在GitHub上找到。)
评论
很好,但是当我第一次上课时,那个__SENTINEL__让我失望了。在我看到用法示例之前,我以为您依赖于编译器的一些晦涩之处。使用双下划线的名称仅用于编译器/标准库名称:stackoverflow.com/a/228797/1198654