【问题标题】:How to write common functions for enums, that only affect specific enum types?如何为枚举编写只影响特定枚举类型的通用函数?
【发布时间】:2015-06-25 22:55:33
【问题描述】:

我有几个想用作位标志的枚举。我可以为所有这些编写代码而不影响那些我不想被这样对待的代码吗?

【问题讨论】:

    标签: c++ c++11 templates enums operator-overloading


    【解决方案1】:

    您可以使用特化在函数上标记合适的枚举和 SFINAE:

    template<typename E> struct is_bitwise_enum : std::false_type {};
    

    现在像这样标记所有按位枚举:

    template<> struct is_bitwise_enum< my_enum > : std::true_type {};
    template<> struct is_bitwise_enum< my_other_enum > : std::true_type {};
    

    并像这样保护函数:

    template<typename E>
    typename std::enable_if<is_bitwise_enum<E>::value,E>::type
    operator|(const E lhs, const E rhs) { ... }
    

    【讨论】:

    • 不错。我更喜欢这个,因为它不会给枚举本身增加噪音。 :)
    • 您甚至可以拥有多个正交特征类。对枚举没有影响,您可以根据需要对每个函数编写需求! :)
    【解决方案2】:

    是的,通过使用类型特征和 SFINAE,您可以指定函数/运算符是否可以与枚举一起使用。

    已编辑

    我完成了我的头文件,并决定使用 Daniel Frey 所描述的专业化想法进行分享,以防有人想要它或想要在它的基础上进行构建。

    #ifdef _MSC_VER
    #pragma once // For VC++
    #endif
    
    #ifndef ENUM_FUNCTIONS_H
    #define ENUM_FUNCTIONS_H
    ///////////////////////////////////////////////////////////////////////////////
    // Common enum functions so that the same code doesn't have to be written over
    // and over again for different enums.
    //
    //                             -- Written by: Adrian Hawryluk, June 2015
    ///////////////////////////////////////////////////////////////////////////////
    
    #include <type_traits>
    namespace enums
    {
        size_t const version = 1;
        // If your compiler doesn't understand constexpr, then set CONSTEXPR to blank,
        // otherwise set to constexpr.
    #define CONSTEXPR //constexpr
        ///////////////////////////////////////////////////////////////////////////
        // Use Enums as Bit Flags
        //
        // These functions allow the treating of an enum as a set of bits with
        // which you can use a subset of the standard bitwise operators:
        //
        //    |, |=, &, &=, ^, ^=
        //
        // and the functions:
        //
        //    Cast, CastRef, CastSigned, CastSignedRef, CastUnsigned, CastUnsignedRef,
        //    MakeSet, Set, MakeClear, Clear, MakeToggle, Toggle, AreAllSet,
        //    AreAnySet, AreAllClear, AreAnyClear, FirstSet, FirstCleared
        //
        // To enable the use of these operators and functions, define:
        //
        //    namespace enums {
        //      template<> struct is_bitflag_enum<my_enum> : std::true_type {};
        //    }
        //
        // where my_enum is the enum being defined to use these functions.
        //
        // To be able to use the operators, you need to state that you are using the
        // namespace in the appropriate scope:
        //
        //     using namespace enums;
    
        template<typename E> struct is_bitflag_enum : std::false_type {};
    
        // operator ^=
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E&>::type
            operator^=(E& lhs, E rhs)
        {
            typedef typename std::make_unsigned<typename std::underlying_type<E>::type>::type uint_t;
            return (E&)((uint_t&)lhs ^= (uint_t)rhs);
        }
    
        // operator ~
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E>::type
            operator~(E rhs)
        {
            typedef typename std::make_unsigned<typename std::underlying_type<E>::type>::type uint_t;
            return (E)(~(uint_t)rhs);
        }
    
        // operator |=
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E&>::type
            operator|=(E& lhs, E rhs)
        {
            typedef typename std::make_unsigned<typename std::underlying_type<E>::type>::type uint_t;
            return (E&)((uint_t&)lhs |= (uint_t)rhs);
        }
    
        // operator |
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E>::type
            operator|(E lhs, E rhs)
        {
            typedef typename std::make_unsigned<typename std::underlying_type<E>::type>::type uint_t;
            return (E)((uint_t)lhs | (uint_t)rhs);
        }
    
        // operator &=
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E&>::type
            operator&=(E& lhs, E rhs)
        {
            typedef typename std::make_unsigned<typename std::underlying_type<E>::type>::type uint_t;
            return (E&)((uint_t&)lhs &= (uint_t)rhs);
        }
    
        // operator &
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E>::type
            operator&(E lhs, E rhs)
        {
            typedef typename std::make_unsigned<typename std::underlying_type<E>::type>::type uint_t;
            return (E)((uint_t)lhs & (uint_t)rhs);
        }
    
        // MakeSet(initialFlagState, flagModification)
        //
        // Sets the on bits in flagModification to on in initialFlagState.
        // initialFlagState is NOT modified, but returs a new value
        // (i.e. "Makes" a new value).
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E>::type
            MakeSet(E initialFlagState, E flagModification)
        {
            return initialFlagState | flagModification;
        }
    
        // Set(initialFlagState, flagModification)
        //
        // Sets the on bits in flagModification to on in initialFlagState.
        // initialFlagState is modified, and returs a reference to that variable.
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E&>::type
            Set(E& initialFlagState, E flagModification)
        {
            return initialFlagState |= flagModification;
        }
    
        // MakeClear(initialFlagState, flagModification)
        //
        // Clears the on bits in flagModification to off in initialFlagState.
        // initialFlagState is NOT modified, but returs a new value
        // (i.e. "Makes" a new value).
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E>::type
            MakeClear(E initialFlagState, E flagModification)
        {
            return initialFlagState & ~flagModification;
        }
    
        // Clear(initialFlagState, flagModification)
        //
        // Clears the on bits in flagModification to off in initialFlagState.
        // initialFlagState is modified, and returs a reference to that variable.
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E&>::type Clear(E& initialFlagState, E flagModification)
        {
            return initialFlagState &= ~flagModification;
        }
    
        // MakeToggle(initialFlagState, flagModification)
        //
        // Toggles the on bits in flagModification in initialFlagState.
        // initialFlagState is NOT modified, but returs a new value
        // (i.e. "Makes" a new value).
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E>::type
            MakeToggle(E initialFlagState, E flagModification)
        {
            return initialFlagState ^ flagModification;
        }
    
        // Toggle(initialFlagState, flagModification)
        //
        // Toggles the on bits in flagModification in initialFlagState.
        // initialFlagState is modified, and returs a reference to that variable.
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E&>::type
            Toggle(E& initialFlagState, E flagModification)
        {
            return initialFlagState ^= flagModification;
        }
    
        // AreAllSet(flagState, flagsToCompare)
        //
        // Returns true if all bits in flagToCompare are set in flagState,
        // false otherwise.
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            bool>::type
            AreAllSet(E flagState, E flagsToCompare)
        {
            return (flagState & flagsToCompare) == flagsToCompare;
        }
    
        // AreAnySet(flagState, flagsToCompare)
        //
        // Returns true if any bits in flagToCompare are set in flagState,
        // false otherwise.
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            bool>::type
            AreAnySet(E flagState, E flagsToCompare)
        {
            return (flagState & flagsToCompare) != (E)0;
        }
    
        // AreAllClear(flagState, flagsToCompare)
        //
        // Returns true if all bits in flagToCompare are cleared in flagState,
        // false otherwise.
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            bool>::type
            AreAllClear(E flagState, E flagsToCompare)
        {
            return (~flagState & flagsToCompare) == flagsToCompare;
        }
    
        // AreAnyClear(flagState, flagsToCompare)
        //
        // Returns true if any bits in flagToCompare are cleared in flagState,
        // false otherwise.
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            bool>::type
            AreAnyClear(E flagState, E flagsToCompare)
        {
            return (~flagState & flagsToCompare) != (E)0;
        }
    
        // FirstSet(flagState, flagToCompare)
        //
        // Return the first flag in the list that is set in flagState.
        // If all are not set, return (E)0.
        //
        // NOTE: If a flag in the list is (E)0, then it will ignore that element 
        //       in the list, and continue to the next one.
        // NOTE: If a flag in the list represents 2 or more bits, only a subset of
        //       those bits need be set to result in the returned value.
        template<typename E, typename ...Es>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E>::type
            FirstSet(E flagState, E flagToCompare)
        {
            if (AreAnySet(flagState, flagToCompare))
            {
                return flagToCompare;
            }
            return (E)0;
        }
    
        // FirstSet(flagState, flagToCompare, ...)
        //
        // Return the first flag in the list that is set in flagState.
        // If all are not set, return (E)0.
        //
        // NOTE: If a flag in the list is (E)0, then it will ignore that element 
        //       in the list, and continue to the next one.
        // NOTE: If a flag in the list represents 2 or more bits, only a subset of
        //       those bits need be set to result in the returned value.
        template<typename E, typename ...Es>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E>::type
            FirstSet(E flagState, E flagToCompare, Es...flagsToCompare)
        {
            if (AreAnySet(flagState, flagToCompare))
            {
                return flagToCompare;
            }
            return FirstSet(flagState, flagsToCompare...);
        }
    
        // FirstCleared(flagState, flagToCompare)
        //
        // Return the first flag in the list that is NOT set in flagState.
        // If all are not set, return (E)0.
        //
        // NOTE: If a flag in the list is (E)0, then it will not go beyond that
        //       element in the list.
        // NOTE: If a flag in the list represents 2 or more bits, only a subset of
        //       those bits need be cleared to result in the returned value.
        template<typename E, typename ...Es>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E>::type
            FirstCleared(E flagState, E flagToCompare)
        {
            if (AreAnyClear(flagState, flagToCompare))
            {
                return (E)0;
            }
            return flagToCompare;
        }
    
        // FirstCleared(flagState, flagToCompare, ...)
        //
        // Return the first flag in the list that is cleared in flagState.
        // If all are cleared, return (E)0.
        //
        // NOTE: If a flag in the list is (E)0, then it will not go beyond that
        //       element in the list.
        // NOTE: If a flag in the list represents 2 or more bits, only a subset of
        //       those bits need be cleared to result in the returned value.
        template<typename E, typename ...Es>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value,
            E>::type
            FirstCleared(E flagState, E flagToCompare, Es...flagsToCompare)
        {
            if (AreAnyClear(flagState, flagToCompare))
            {
                return FirstCleared(flagState, flagsToCompare...);
            }
            return flagToCompare;
        }
    
    
        ///////////////////////////////////////////////////////////////////////////
        // Use With Shift Operators
        //
        // These functions allow the treating of an enum as a siftable set of bits
        // with which you can use a subset of the standard bitwise operators:
        //
        //    <<, <<=, >>, >>=
        //
        // and the functions:
        //
        //    Cast, CastRef, CastSigned, CastSignedRef, CastUnsigned, CastUnsignedRef
        //
        // To enable the use of these operators and functions, define:
        //
        //    namespace enums {
        //      template<> struct is_shiftable_enum<my_enum> : std::true_type {};
        //    }
        //
        // where my_enum is the enum being defined to use these functions.
        //
        // To be able to use the operators, you need to state that you are using the
        // namespace in the appropriate scope:
        //
        //     using namespace enums;
    
        template<typename E> struct is_shiftable_enum : std::false_type {};
    
        // operator <<
        template<typename E>
        CONSTEXPR typename std::enable_if<is_shiftable_enum<E>::value,
            E>::type
            operator<<(E lhs, int rhs)
        {
            typedef typename std::make_unsigned<typename std::underlying_type<E>::type>::type uint_t;
            return (E)(((uint_t)lhs) << rhs);
        }
    
        // operator <<=
        template<typename E>
        CONSTEXPR typename std::enable_if<is_shiftable_enum<E>::value,
            E&>::type
            operator<<=(E& lhs, int rhs)
        {
            typedef typename std::make_unsigned<typename std::underlying_type<E>::type>::type uint_t;
            return (E&)(((uint_t&)lhs) <<= rhs);
        }
    
        // operator >>
        template<typename E>
        CONSTEXPR typename std::enable_if<is_shiftable_enum<E>::value,
            E>::type
            operator>>(E lhs, int rhs)
        {
            typedef typename std::make_unsigned<typename std::underlying_type<E>::type>::type uint_t;
            return (E)(((uint_t)lhs) << rhs);
        }
    
        // operator >>=
        template<typename E>
        CONSTEXPR typename std::enable_if<is_shiftable_enum<E>::value,
            E&>::type
            operator>>=(E& lhs, int rhs)
        {
            typedef typename std::make_unsigned<typename std::underlying_type<E>::type>::type uint_t;
            return (E&)(((uint_t&)lhs) <<= rhs);
        }
    
    
        ///////////////////////////////////////////////////////////////////////////
        // Casting functions
        //
        // These functions are to cast the enum to the underlying type, some of
        // which allowing to coerce the type to be signed or not.
    
        // Cast enum to the underlying type reference (lvalue).
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value || is_shiftable_enum<E>::value,
            typename std::underlying_type<E>::type>::type&
            CastRef(E& flagState)
        {
            return (decltype(CastRef(flagState)))flagState;
        }
    
        // Cast enum to a signed integer reference (lvalue).
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value || is_shiftable_enum<E>::value,
            typename std::make_signed<typename std::underlying_type<E>::type>::type>::type&
            CastSignedRef(E& flagState)
        {
            return (decltype(CastSignedRef(flagState)))flagState;
        }
    
        // Cast enum to an unsigned integer reference (lvalue).
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value || is_shiftable_enum<E>::value,
            typename std::make_unsigned<typename std::underlying_type<E>::type>::type>::type&
            CastUnsignedRef(E& flagState)
        {
            return (decltype(CastUnsignedRef(flagState)))flagState;
        }
    
        // Cast enum to the underlying type value (rvalue).
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value || is_shiftable_enum<E>::value,
            typename std::underlying_type<E>::type>::type
            Cast(E flagState)
        {
            return (decltype(Cast(flagState)))flagState;
        }
    
        // Cast enum to a signed integerr value (rvalue).
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value || is_shiftable_enum<E>::value,
            typename std::make_signed<typename std::underlying_type<E>::type>::type>::type
            CastSigned(E flagState)
        {
            return (decltype(CastSigned(flagState)))flagState;
        }
    
        // Cast enum to an unsigned integerr value (rvalue).
        template<typename E>
        CONSTEXPR typename std::enable_if<is_bitflag_enum<E>::value || is_shiftable_enum<E>::value,
            typename std::make_unsigned<typename std::underlying_type<E>::type>::type>::type
            CastUnsigned(E flagState)
        {
            return (decltype(CastUnsigned(flagState)))flagState;
        }
    #undef CONSTEXPR
    }
    #endif // ENUM_FUNCTIONS_H
    

    在包含的代码中,枚举可以通过指定来访问这些函数

    namespace enums {
        template<> struct is_shiftable_enum<my_enum> : std::true_type {};
    }
    

    my_enum 是您要授予访问权限的枚举。使用运算符时,您必须指定using namespace enums;,以便运算符在您使用的范围内工作。

    【讨论】:

    • 第一部分似乎是试图模拟std::underlying_type加上make_unsignedmake_signed(更不用说对于SIGNED的每个值,四个重载都没有有效的实例化格式错误的 NDR 也是如此)。第二部分似乎也有不必要的限制。将“是位标志”或“允许移位运算符”作为独立元函数会更有意义。
    • @T.C.感谢您提供有关这 3 个功能的提示。虽然我不知道four of the overloads have no valid instantiations and so are ill-formed NDRseems unnecessarily restrictive; it would make more sense for "is bit flag" or "allow shift operators" to be standalone metafunctions. 是什么意思
    • @T.C. no valid instantiations 是什么意思? unnecessarily restrictive 是什么?
    • “不必要的限制”我的意思是它要求枚举有一个具有特定名称的成员,因此它不能与最初设计用于您的代码的东西一起使用。相比之下,独立的元函数(如 Daniel Frey 的回答中的那个)没有这样的限制。
    • 关于“没有有效的实例化”,我的意思是对于SIGNED 的任何给定值,其中四个函数的形式为template&lt;class T&gt; typename std::enable_if&lt;/* something */ &amp;&amp; false, /*something */&gt;::type getType();。这样的函数模板没有有效的实例化,因为无论T 是什么,enable_if 的条件始终为假。标准说这些东西是不正确的,不需要诊断。
    猜你喜欢
    • 1970-01-01
    • 2014-10-08
    • 2018-03-26
    • 2014-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-11
    相关资源
    最近更新 更多