82

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

If int falls in range of an enum it should return an enum value, otherwise throw an exception. Is there a way to write it generically? More than one enum type should be supported.

Background: I have an external enum type and no control over the source code. I'd like to store this value in a database and retrieve it.

Ravindra S
  • 6,302
  • 12
  • 70
  • 108
Leonid
  • 22,360
  • 25
  • 67
  • 91

8 Answers8

37

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.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
23

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.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • this supports only one type, MyEnum. – Simone Nov 12 '10 at 14:11
  • 2
    @Leonid: To my knowledge it can't be done generically. At some level, any solution that you come up with that will `throw` (or do anything special) for invalid types must have a switch like I've posted. – John Dibling Nov 12 '10 at 14:14
  • Exactly, *reinterpret_cast* doesn't check for validity of *enum* values and it is ugly. But as enum values are known at compile-time maybe there is a way to do it via templates, or maybe there will be a way to do that in `C++0x`? – Leonid Nov 12 '10 at 14:14
  • 2
    Why is this -1'ed? It's the correct answer. Just because it's not the answer some were hoping for doesn't mean it's wrong. – John Dibling Nov 12 '10 at 14:19
  • 12
    A `static_cast` will work as well, and should be preferred over the `reinterpret_cast` – Sjoerd Nov 12 '10 at 14:39
  • 1
    This approach works well, and even better if you use a tool to generate the function. – Nick Oct 18 '14 at 17:52
  • `enum my_enum_val` should probably read `MyEnum my_enum_val`, right? And the `static_cast(my_int_val)` will throw if `my_int_val` is out of range for the data type that `MyEnum` is using internally -- not that that's much help for your average `int`... – Christian Severin May 29 '15 at 10:51
3

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

luke
  • 36,103
  • 8
  • 58
  • 81
3

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
Sjoerd
  • 6,837
  • 31
  • 44
  • There is no control over **enum** type source code. I agree that a facility can be generated that implements conversion. However, the facility would not be aware of any changes/extensions made to external **enum** type (unless executed everytime at compile time). – Leonid Nov 12 '10 at 14:29
  • @Leonid Then read that enum header and generate the `to_enum(int)` function based on that. – Sjoerd Nov 12 '10 at 14:45
  • @Leonid Every serious project management system, even `make`, can compare the date of two files to see whether the generator has to be rerun. – Sjoerd Nov 12 '10 at 14:46
  • I think I will go with a simpler solution to generator for now. But thanks for the idea. – Leonid Nov 12 '10 at 14:52
  • We use this scheme at our workplace, a tool generates the .hpp code from a template, the template is minimal. – Nick Oct 18 '14 at 17:50
2

What do you think about this one?

#include <iostream>
#include <stdexcept>
#include <set>
#include <string>

using namespace std;

template<typename T>
class Enum
{
public:
    static void insert(int value)
    {
        _set.insert(value);
    }

    static T buildFrom(int value)
    {
        if (_set.find(value) != _set.end()) {
            T retval;
            retval.assign(value);
            return retval;
        }
        throw std::runtime_error("unexpected value");
    }

    operator int() const { return _value; }

private:
    void assign(int value)
    {
        _value = value;
    }

    int _value;
    static std::set<int> _set;
};

template<typename T> std::set<int> Enum<T>::_set;

class Apples: public Enum<Apples> {};

class Oranges: public Enum<Oranges> {};

class Proxy
{
public:
    Proxy(int value): _value(value) {}

    template<typename T>
    operator T()
    {
        T theEnum;
        return theEnum.buildFrom(_value);
    }

    int _value;
};

Proxy convert(int value)
{
    return Proxy(value);
}

int main()
{    
    Apples::insert(4);
    Apples::insert(8);

    Apples a = convert(4); // works
    std::cout << a << std::endl; // prints 4

    try {
        Apples b = convert(9); // throws    
    }
    catch (std::exception const& e) {
        std::cout << e.what() << std::endl; // prints "unexpected value"
    }
    try {
        Oranges b = convert(4); // also throws  
    }
    catch (std::exception const& e) {
        std::cout << e.what() << std::endl; // prints "unexpected value"
    }
}

You could then use code I posted here to switch on values.

Community
  • 1
  • 1
Simone
  • 11,655
  • 1
  • 30
  • 43
1

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);
}
janm
  • 17,976
  • 1
  • 43
  • 61
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/10887195) – Tas Jan 14 '16 at 23:42
  • 1
    @Tas It's a link to a different SO answer -- it doesn't have the same issues that an external link has. Updated in anyway. – janm Jan 15 '16 at 02:19
1

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?

Simone
  • 11,655
  • 1
  • 30
  • 43
  • how would you otherwise retrieve enum values from the database? The integers are known at compile time, so why is that not possible to have a generic conversion based on templates? – Leonid Nov 12 '10 at 14:17
  • 2
    @Leonid: Because at some level, you need to have a switch, like I said. – John Dibling Nov 12 '10 at 14:20
  • 2
    @Leonid Templates are not a silver bullet to solve every problem you can think of. – Sjoerd Nov 12 '10 at 14:22
  • John is right. You need a type more complex than an enum to do what you want, I think it's feasible with a class hierarchy. – Simone Nov 12 '10 at 14:22
  • I posted a solution that uses class hierarchy, check it out. – Simone Nov 12 '10 at 15:47
0

C++0x alternative to the "ugly" version, allows for multiple enums. Uses initializer lists rather than switches, a bit cleaner IMO. Unfortunately, this doesn't work around the need to hard-code the enum values.

#include <cassert>  // assert

namespace  // unnamed namespace
{
    enum class e1 { value_1 = 1, value_2 = 2 };
    enum class e2 { value_3 = 3, value_4 = 4 };

    template <typename T>
    int valid_enum( const int val, const T& vec )
    {
        for ( const auto item : vec )
            if ( static_cast<int>( item ) == val ) return val;

        throw std::exception( "invalid enum value!" );  // throw something useful here
    }   // valid_enum
}   // ns

int main()
{
    // generate list of valid values
    const auto e1_valid_values = { e1::value_1, e1::value_2 };
    const auto e2_valid_values = { e2::value_3, e2::value_4 };

    auto result1 = static_cast<e1>( valid_enum( 1, e1_valid_values ) );
    assert( result1 == e1::value_1 );

    auto result2 = static_cast<e2>( valid_enum( 3, e2_valid_values ) );
    assert( result2 == e2::value_3 );

    // test throw on invalid value
    try
    {
        auto result3 = static_cast<e1>( valid_enum( 9999999, e1_valid_values ) );
        assert( false );
    }
    catch ( ... )
    {
        assert( true );
    }
}
Tom
  • 1,977
  • 1
  • 20
  • 19