Generic way to cast int to enum in C++

前端 未结 8 2030
[愿得一人]
[愿得一人] 2020-12-04 11:26

Is there a generic way to cast int to enum in C++?

If int falls in range of an enum it should return

相关标签:
8条回答
  • 2020-12-04 11:30

    If, as you describe, the values are in a database, why not write a code generator that reads this table and creates a .h and .cpp file with both the enum and a to_enum(int) function?

    Advantages:

    • Easy to add a to_string(my_enum) function.
    • Little maintenance required
    • Database and code are in synch
    0 讨论(0)
  • 2020-12-04 11:36

    No- there's no introspection in C++, nor is there any built in "domain check" facility.

    0 讨论(0)
  • 2020-12-04 11:38

    You should not want something like what you describe to exist, I fear there are problems in your code design.

    Also, you assume that enums come in a range, but that's not always the case:

    enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };
    

    This is not in a range: even if it was possible, are you supposed to check every integer from 0 to 2^n to see if they match some enum's value?

    0 讨论(0)
  • 2020-12-04 11:41

    Ugly.

    enum MyEnum { one = 1, two = 2 };
    
    MyEnum to_enum(int n)
    {
      switch( n )
      {
        case 1 :  return one;
        case 2 : return two;
      }
      throw something();
    }
    

    Now for the real question. Why do you need this? The code is ugly, not easy to write (*?) and not easy to maintain, and not easy to incorporate in to your code. The code it telling you that it's wrong. Why fight it?

    EDIT:

    Alternatively, given that enums are integral types in C++:

    enum my_enum_val = static_cast<MyEnum>(my_int_val);
    

    but this is even uglier that above, much more prone to errors, and it won't throw as you desire.

    0 讨论(0)
  • 2020-12-04 11:44

    The obvious thing is to annotate your enum:

    // generic code
    #include <algorithm>
    
    template <typename T>
    struct enum_traits {};
    
    template<typename T, size_t N>
    T *endof(T (&ra)[N]) {
        return ra + N;
    }
    
    template<typename T, typename ValType>
    T check(ValType v) {
        typedef enum_traits<T> traits;
        const T *first = traits::enumerators;
        const T *last = endof(traits::enumerators);
        if (traits::sorted) { // probably premature optimization
            if (std::binary_search(first, last, v)) return T(v);
        } else if (std::find(first, last, v) != last) {
            return T(v);
        }
        throw "exception";
    }
    
    // "enhanced" definition of enum
    enum e {
        x = 1,
        y = 4,
        z = 10,
    };
    
    template<>
    struct enum_traits<e> {
        static const e enumerators[];
        static const bool sorted = true;
    };
    // must appear in only one TU,
    // so if the above is in a header then it will need the array size
    const e enum_traits<e>::enumerators[] = {x, y, z};
    
    // usage
    int main() {
        e good = check<e>(1);
        e bad = check<e>(2);
    }
    

    You need the array to be kept up to date with e, which is a nuisance if you're not the author of e. As Sjoerd says, it can probably be automated with any decent build system.

    In any case, you're up against 7.2/6:

    For an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values of the underlying type in the range bmin to bmax, where bmin and bmax are, respectively, the smallest and largest values of the smallest bit-field that can store emin and emax. It is possible to define an enumeration that has values not defined by any of its enumerators.

    So if you aren't the author of e, you may or may not have a guarantee that valid values of e actually appear in its definition.

    0 讨论(0)
  • 2020-12-04 11:51

    If you are prepared to list your enum values as template parameters you can do this in C++ 11 with varadic templates. You can look at this as a good thing, allowing you to accept subsets of the valid enum values in different contexts; often useful when parsing codes from external sources.

    Perhaps not quite as generic as you'd like, but the checking code itself is generalised, you just need to specify the set of values. This approach handles gaps, arbitrary values, etc.

    template<typename EnumType, EnumType... Values> class EnumCheck;
    
    template<typename EnumType> class EnumCheck<EnumType>
    {
    public:
        template<typename IntType>
        static bool constexpr is_value(IntType) { return false; }
    };
    
    template<typename EnumType, EnumType V, EnumType... Next>
    class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...>
    {
        using super = EnumCheck<EnumType, Next...>;
    
    public:
        template<typename IntType>
        static bool constexpr is_value(IntType v)
        {
            return v == static_cast<typename std::underlying_type<EnumType>::type>(V) || super::is_value(v);
        }
    
        EnumType convert(IntType v)
        {
            if (!is_value(v)) throw std::runtime_error("Enum value out of range");
            return static_cast<EnumType>(v);
    };
    
    enum class Test {
        A = 1,
        C = 3,
        E = 5
    };
    
    using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>;
    
    void check_value(int v)
    {
        if (TestCheck::is_value(v))
            printf("%d is OK\n", v);
        else
            printf("%d is not OK\n", v);
    }
    
    int main()
    {
        for (int i = 0; i < 10; ++i)
            check_value(i);
    }
    
    0 讨论(0)
提交回复
热议问题