使用以下模板尝试使C ++ 11/14的新类枚举按需工作,我发现以下代码甚至不会尝试调用隐式ctor来使用适合VS2013的非成员模板更新1:
BitTest
定义为:
template <typename enumT>
bool BitTest(const EnumeratedFlags<enumT>& lhs, const EnumeratedFlags<enumT>& rhs)
{
return (lhs & rhs);
}
测试带有异常的代码:
enum class Flags { None = 0x00, CanChangeDataSources = 0x01, RequiresExclusiveAccess = 0x02 };
EnumeratedFlags<Flags> lhs(Flags::CanChangeDataSources);
// ... the following fails to even attempt to convert the Foo::Flags
if (BitTest(lhs, Flags::CanChangeDataSources)) { DoSomething(); }
// this compiles, but I don't know why it's necessary (and this is annoying and ugly)...
if (BitTest(lhs, EnumeratedFlags<Flags>(Flags::CanChangeDataSources))) { DoSomething(); }
以下是我目前正在尝试使用的模板定义:
template <typename enumT>
class EnumeratedFlags
{
public:
typedef enumT enum_type;
typedef typename std::underlying_type<enumT>::type store_type;
// constructors
EnumeratedFlags()
: m_bits(0)
{
}
EnumeratedFlags(enum_type flag)
: m_bits(static_cast<store_type>(flag))
{
}
explicit EnumeratedFlags(store_type value)
: m_bits(value)
{
}
EnumeratedFlags(const std::initializer_list<enum_type> & initializers)
: m_bits(0)
{
for (auto flag : initializers)
m_bits |= static_cast<store_type>(flag);
}
// operators
operator std::string () const
{
return to_string();
}
bool operator [] (enum_type flag) const
{
return test(flag);
}
store_type operator * () const
{
return m_bits;
}
operator bool () const
{
return m_bits != store_type(0);
}
// explicit accessors
store_type bits() const
{
return m_bits;
}
std::string to_string() const
{
std::string str(size(), '0');
for (size_t x = 0; x < size(); ++x)
str[size() - x - 1] = (m_bits & (1 << x) ? '1' : '0');
return str;
}
EnumeratedFlags & set(enum_type flag)
{
BitSet(m_bits, static_cast<store_type>(flag));
return *this;
}
EnumeratedFlags & set_if(enum_type flag, bool set_or_clear)
{
BitSetIf(m_bits, static_cast<store_type>(flag), set_or_clear);
return *this;
}
EnumeratedFlags & clear()
{
m_bits = store_type(0);
return *this;
}
EnumeratedFlags & flip()
{
m_bits = ~m_bits;
return *this;
}
EnumeratedFlags & flip(enum_type flag)
{
m_bits ^= static_cast<store_type>(flag);
return *this;
}
size_t count() const
{
// http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
store_type bits = m_bits;
size_t total = 0;
for (; bits != 0; ++total)
{
bits &= bits - 1; // clear the least significant bit set
}
return total;
}
size_t size() const
{
// one character per possible bit
return sizeof(enum_type) * 8;
}
bool test(enum_type flag) const
{
return BitTest(m_bits, static_cast<store_type>(flag));
}
bool any() const
{
return m_bits != 0;
}
bool none() const
{
return m_bits == 0;
}
private:
store_type m_bits;
};
template <class charT, class traits, typename enumT>
std::basic_ostream<charT, traits> & operator << (std::basic_ostream<charT, traits> & os, const EnumeratedFlags<enumT> & flags)
{
return os << flags.to_string();
}
template <typename enumT>
EnumeratedFlags<enumT> operator & (const EnumeratedFlags<enumT>& lhs, const EnumeratedFlags<enumT>& rhs)
{
return EnumeratedFlags<enumT>(lhs.bits() & rhs.bits());
}
template <typename enumT>
EnumeratedFlags<enumT> operator | (const EnumeratedFlags<enumT>& lhs, const EnumeratedFlags<enumT>& rhs)
{
return EnumeratedFlags<enumT>(lhs.bits() | rhs.bits());
}
template <typename enumT>
EnumeratedFlags<enumT> operator ^ (const EnumeratedFlags<enumT>& lhs, const EnumeratedFlags<enumT>& rhs)
{
return EnumeratedFlags<enumT>(lhs.bits() ^ rhs.bits());
}
template <typename enumT>
bool BitTest(const EnumeratedFlags<enumT>& lhs, const EnumeratedFlags<enumT>& rhs)
{
return (lhs & rhs);
}
基本上,我认为X (const T & lhs, const T & rhs)
形式的任何免费函数都会使用最多一个用户定义的转换来查找上面BitTest<>()
的有效调用。相反,我必须在上面的代码中明确说明转换,以使编译器使用此函数,这大大降低了template class EnumeratedFlags<>
的表达能力。
一般来说,C ++让我感到疯狂,因为没有一种好方法可以使用结合了使用范围枚举(enum class Foo
)和位字段(或者字节)的所有功能和良好编程习惯的位类似命名的位集)并使客户端程序员使用它们非常容易,同时保留编译器的基本健全性检查(不会自动转换为数字类型或反之亦然)。似乎需要对语言规范进行更全面的改进才能使得真正闪耀的位域枚举......或者我错过了什么?
答案 0 :(得分:5)
来自§14.8.1/ 6 [temp.arg.explicit]
如果参数类型不包含参与模板参数推断的 template-parameters ,则将对函数参数执行隐式转换(第4节)以将其转换为相应函数参数的类型。
在您的示例中,BitTest()
的第二个参数确实参与模板参数推导,因此不会尝试进行隐式转换,并且编译器无法将类型Flags
转换为const EnumeratedFlags<Flag>&
。 / p>
您可以通过将第二个参数类型转换为非推导的上下文来解决它,从而防止它参与模板参数推断。
template <typename enumT>
bool BitTest(const EnumeratedFlags<enumT>& lhs,
const EnumeratedFlags<typename EnumeratedFlags<enumT>::enum_type>& rhs)
{
return (lhs & rhs);
}
当然,另一种解决方案是提供这些重载
template <typename enumT>
bool BitTest(const EnumeratedFlags<enumT>& lhs,
enumT rhs)
{
return (lhs & EnumeratedFlags<enumT>(rhs));
}
template <typename enumT>
bool BitTest(enumT lhs,
const EnumeratedFlags<enumT>& rhs)
{
return BitTest(rhs, lhs);
}
答案 1 :(得分:1)
上面已经回答了我的问题。然而,这一直困扰着我,所以我能够充分利用每个人的反应并提出,我认为这是第一次,我真正喜欢的机制!
这给了我一种方法来使用C ++枚举来存储位标志值,然后在逻辑上对它们进行位操作,同时永远不会丢失类型信息或编译器的帮助以确保我属于成功的关键。 :)
我希望这对你有帮助! (注意:此代码在VS2013更新1下编译并正常运行)
#pragma once
#include <type_traits>
// This is my ongoing attempt to make a really solid enumeration facility in C++11/14
//
// What I hate about C++98 (and older) enum
// - lack of namespace scoping of the non-class enum (name collisions everywhere)
// - auto conversion to numeric types (int i = MyEnumConstant), but no conversion back again (so supports losing info, but restricts regaining it)
//
// What I hate about C++11/14 built-in `enum class X`
// - having to constantly cast in order to treat them (now neither direction works)
// - no built-in mechanism to treat enumerated values as bits or bit-flags
template <typename enum_type>
class bitflag_enum
{
public:
// expose our underlying types
typedef enum_type enum_type;
typedef typename std::underlying_type<enum_type>::type store_type;
// constructors
bitflag_enum()
: m_bits(0)
{
}
bitflag_enum(enum_type flag)
: m_bits(static_cast<store_type>(flag))
{
}
explicit bitflag_enum(store_type value)
: m_bits(value)
{
}
// operators
operator bool() const
{
return m_bits != store_type(0);
}
// explicit accessors
store_type bits() const
{
return m_bits;
}
private:
store_type m_bits;
};
// because implicit conversion isn't considered if a type participates in template type deduction,
// we've defined both homogeneous and heterogeneous operators here for bitflag_enum<enum_type> and enum_type
// hence we define logical operators &, |, ^ and comparisons for TxT, TxU, UxT (where T is bitflag_enum<enum_type>, and U is enum_type)
template <typename enum_type>
bool operator != (bitflag_enum<enum_type> lhs, bitflag_enum<enum_type> rhs)
{
return bitflag_enum<enum_type>(lhs.bits() != rhs.bits());
}
template <typename enum_type>
bool operator != (bitflag_enum<enum_type> lhs, enum_type rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(lhs.bits() != static_cast<store_type>(rhs));
}
template <typename enum_type>
bool operator != (enum_type lhs, bitflag_enum<enum_type> rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(static_cast<store_type>(lhs) != rhs.bits());
}
template <typename enum_type>
bool operator == (bitflag_enum<enum_type> lhs, bitflag_enum<enum_type> rhs)
{
return bitflag_enum<enum_type>(lhs.bits() == rhs.bits());
}
template <typename enum_type>
bool operator == (bitflag_enum<enum_type> lhs, enum_type rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(lhs.bits() == static_cast<store_type>(rhs));
}
template <typename enum_type>
bool operator == (enum_type lhs, bitflag_enum<enum_type> rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(static_cast<store_type>(lhs) == rhs.bits());
}
template <typename enum_type>
bitflag_enum<enum_type> operator & (bitflag_enum<enum_type> lhs, bitflag_enum<enum_type> rhs)
{
return bitflag_enum<enum_type>(lhs.bits() & rhs.bits());
}
template <typename enum_type>
bitflag_enum<enum_type> operator & (bitflag_enum<enum_type> lhs, enum_type rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(lhs.bits() & static_cast<store_type>(rhs));
}
template <typename enum_type>
bitflag_enum<enum_type> operator & (enum_type lhs, bitflag_enum<enum_type> rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(static_cast<store_type>(lhs)& rhs.bits());
}
template <typename enum_type>
bitflag_enum<enum_type> operator | (bitflag_enum<enum_type> lhs, bitflag_enum<enum_type> rhs)
{
return bitflag_enum<enum_type>(lhs.bits() | rhs.bits());
}
template <typename enum_type>
bitflag_enum<enum_type> operator | (bitflag_enum<enum_type> lhs, enum_type rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(lhs.bits() | static_cast<store_type>(rhs));
}
template <typename enum_type>
bitflag_enum<enum_type> operator | (enum_type lhs, bitflag_enum<enum_type> rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(static_cast<store_type>(lhs) | rhs.bits());
}
template <typename enum_type>
bitflag_enum<enum_type> operator ^ (bitflag_enum<enum_type> lhs, bitflag_enum<enum_type> rhs)
{
return bitflag_enum<enum_type>(lhs.bits() ^ rhs.bits());
}
template <typename enum_type>
bitflag_enum<enum_type> operator ^ (bitflag_enum<enum_type> lhs, enum_type rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(lhs.bits() ^ static_cast<store_type>(rhs));
}
template <typename enum_type>
bitflag_enum<enum_type> operator ^ (enum_type lhs, bitflag_enum<enum_type> rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(static_cast<store_type>(lhs) ^ rhs.bits());
}
// The only missing pieces above are for the UxU cases
// we allow you to have those by defining a specialization of is_bitflag_enum<>, as follows:
//
// template <> struct is_bitflag_enum<YourEnumType> : std::true_type { };
//
// However, by default, no other types will convert to an bitflag_enum<> unless you explicitly say you want it
//
// If you have asked for them, then you can use MyEnum::ValueX | MyEnum::ValueY and that will produce a bitflag_enum<MyEnum>
// so your code can simply use your enumeration values with scope and as-if they were bit flags as you would think you could
// don't mess up existing enumerations or types by defining these global operators on every existing type!
template <typename enum_type> struct is_bitflag_enum : std::false_type { };
template <typename enum_type>
typename std::enable_if<is_bitflag_enum<enum_type>::value, bitflag_enum<enum_type>>::type
operator & (enum_type lhs, enum_type rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(static_cast<store_type>(lhs) & static_cast<store_type>(rhs));
}
template <typename enum_type>
typename std::enable_if<is_bitflag_enum<enum_type>::value, bitflag_enum<enum_type>>::type
operator | (enum_type lhs, enum_type rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(static_cast<store_type>(lhs) | static_cast<store_type>(rhs));
}
template <typename enum_type>
typename std::enable_if<is_bitflag_enum<enum_type>::value, bitflag_enum<enum_type>>::type
operator ^ (enum_type lhs, enum_type rhs)
{
using store_type = std::underlying_type<enum_type>::type;
return bitflag_enum<enum_type>(static_cast<store_type>(lhs) ^ static_cast<store_type>(rhs));
}