为什么我的隐含ctor不是在下面调用的

Mor*_*hai 1 c++ enums c++11 visual-studio-2013 c++14

使用以下模板尝试使C++ 11/14的新类枚举按需工作,我发现以下代码甚至不会尝试调用隐式ctor来使用适合VS2013更新1的非成员模板:

BitTest 定义为:

template <typename enumT>
bool BitTest(const EnumeratedFlags<enumT>& lhs, const EnumeratedFlags<enumT>& rhs)
{
    return (lhs & rhs);
}
Run Code Online (Sandbox Code Playgroud)

测试带有异常的代码:

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

这是我目前正在尝试使用的模板定义:

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

基本上,我会认为任何形式的自由函数X (const T & lhs, const T & rhs)都会使用一个用户定义的转换来查找BitTest<>()上面的有效调用.相反,我必须在上面的代码中明确声明转换,以使编译器使用此函数,这大大降低了表达能力template class EnumeratedFlags<>.

一般来说,C++让我感到疯狂,因为没有一种好方法可以使用结合了所有特性和使用范围enum(enum class Foo)和位字段(或类似命名的位组)的良好编程习惯的位并制作客户端程序员使用它们非常容易,同时保留编译器的基本健全性检查(不会自动转换为数字类型或反之亦然).似乎需要对语言规范进行更全面的改进才能使得真正闪耀的位域枚举......或者我错过了什么?

Pra*_*ian 5

来自§14.8.1/ 6 [temp.arg.explicit]

如果参数类型不包含参与模板参数推导的模板参数,则将对函数参数执行隐式转换(第4节)以将其转换为相应函数参数的类型.

在您的示例中,第二个参数BitTest()确实参与模板参数推导,因此不会尝试进行隐式转换,并且编译器无法将类型转换Flagsconst EnumeratedFlags<Flag>&.

您可以通过将第二个参数类型转换为非推导的上下文来解决它,从而防止它参与模板参数推断.

template <typename enumT>
bool BitTest(const EnumeratedFlags<enumT>& lhs, 
             const EnumeratedFlags<typename EnumeratedFlags<enumT>::enum_type>& rhs)
{
    return (lhs & rhs);
}
Run Code Online (Sandbox Code Playgroud)

现场演示

当然,另一种解决方案是提供这些过载

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