62

I am reading an enum value from a binary file and would like to check if the value is really part of the enum values. How can I do it?

#include <iostream>

enum Abc
{
    A = 4,
    B = 8,
    C = 12
};

int main()
{
    int v1 = 4;
    Abc v2 = static_cast< Abc >( v1 );

    switch ( v2 )
    {
        case A:
            std::cout<<"A"<<std::endl;
            break;
        case B:
            std::cout<<"B"<<std::endl;
            break;
        case C:
            std::cout<<"C"<<std::endl;
            break;
        default :
            std::cout<<"no match found"<<std::endl;
    }
}

Do I have to use the switch operator or is there a better way?

EDIT

I have enum values set and unfortunately I can not modify them. To make things worse, they are not continuous (their values goes 0, 75,76,80,85,90,95,100, etc.)

SaiyanGirl
  • 16,376
  • 11
  • 41
  • 57
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • 3
    Any enum is just a number, so I don't think there's better way to check it. You probably should define a more rigid structure for your datatypes. – Rizo Feb 11 '11 at 12:58

11 Answers11

26

enum value is valid in C++ if it falls in range [A, B], which is defined by the standard rule below. So in case of enum X { A = 1, B = 3 }, the value of 2 is considered a valid enum value.

Consider 7.2/6 of standard:

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.

There is no retrospection in C++. One approach to take is to list enum values in an array additionally and write a wrapper that would do conversion and possibly throw an exception on failure.

See Similar Question about how to cast int to enum for further details.

Community
  • 1
  • 1
Leonid
  • 22,360
  • 25
  • 67
  • 91
  • 3
    you misinterpreted the standard quote, there is more than `[A,B]` in the valid values. – Matthieu M. Feb 11 '11 at 13:25
  • 16
    Indeed, e.g if the values are 1 and 5, then the latter requires at least 3 bits, so 6 and 7 will also be valid values of the enumerator. – visitor Feb 11 '11 at 13:34
  • 9
    This answer is still incorrect. @visitor pointed out an example that your example fails with. He says "if the values are 1 and 5, then the latter requires at least 3 bits, so 6 and 7 will also be valid values of the enumerator". Your answer implies that only {1, 2, 3, 4, 5} will be valid values, in this case. Your quotation of the standard is correct, but your example is misleading. – mannyglover Nov 12 '18 at 20:58
17

In C++ 11 there is a better way if you are prepared to list your enum values as template parameters. 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.

A possible useful addition to the example below would be some static assertions around the underlying type of EnumType relative to IntType to avoid truncation issues. This is left as an exercise.

#include <stdio.h>

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<IntType>(V) || super::is_value(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
  • Since value of 'int v' is not known at compile time, `is_value` will have to be executed at run time. Wouldn't this result in all kinds of recursive function calls and be very inefficient compared to a simple switch statement or an array of all values? You still have to list all enum values, so it's not like you are gaining anything with this approach. Or did I miss something here? – Super-intelligent Shade Sep 18 '17 at 01:12
  • 1
    @InnocentBystander They're all `constexpr` functions, so the compiler has lots of scope for optimisation. The functions also aren't recursive; it's a chain of functions that happens to have the same name. In some quick tests with the example above, gcc 5.4 generates code one instruction shorter for the template version vs. the switch version. Clang 3.8 is two instructions longer for the template version. The result will vary depending on how many values and whether the values are contiguous. The big win, especially when doing protocol decoding, is you write the codes you expect on one line. – janm Sep 18 '17 at 10:04
  • 3
    you're right -- sorry not "recursive" per se, but chain of function calls. That's interesting that the compilers can optimize all that away. And thank you for following up on a 3 year old answer :) – Super-intelligent Shade Sep 18 '17 at 13:54
15

Maybe use enum like this:

enum MyEnum
{
A,
B,
C
};

and to check

if (v2 >= A && v2 <= C)

If you don't specify values for enum constants, the values start at zero and increase by one with each move down the list. For example, given enum MyEnumType { ALPHA, BETA, GAMMA }; ALPHA has a value of 0, BETA has a value of 1, and GAMMA has a value of 2.

Andrew
  • 24,218
  • 13
  • 61
  • 90
  • 2
    I like the simplicity of this and expanded it by always defining the first item in an enum as SOMETYPE_UNKNOWN and the last one as SOMETYPE_MAX. Then the test will always be AssertTrue(v2 >= SOMETYPE_UNKNOWN && v2 <= SOMETYPE_MAX). Of course, only ever add items after UNKNOWN and before MAX. – Elise van Looij Mar 30 '16 at 16:37
7

Managed Extensions for C++ supports the following syntax:

enum Abc
{
    A = 4,
    B = 8,
    C = 12
};

Enum::IsDefined(Abc::typeid, 8);

Reference: MSDN "Managed Extensions for C++ Programming"

Brett
  • 726
  • 9
  • 18
  • I am not sure what "managed c++" is, but are you sure it is c++, and not c#? [this](http://msdn.microsoft.com/en-us/library/system.enum.isdefined%28v=vs.110%29.aspx) looks like c# – BЈовић Aug 18 '14 at 07:04
  • 4
    @BЈовић: `managed c++` is a Microsoft variant of `c++` which is able to use the libraries of the `.NET framework`. It looks like this is `c++` since the `::` operator is not defined in `c#` like this. – Stefan Aug 18 '14 at 07:11
  • @BЈовић did you try the code in a Managed Extensions C++ project? We are using similar code in one of our C++ projects. Specifically we use the Enum::IsDefined() method. – Brett Feb 03 '15 at 23:50
  • 1
    @Brett sorry I have no idea what "managed extension c++ project is" – BЈовић Feb 10 '15 at 07:59
  • @BЈовић I added a reference to MSDN "Managed Extensions for C++ Programming" in the answer. Hope that helps. – Brett May 03 '15 at 10:24
7

The only way I ever found to make it 'easy', was to create (macro) a sorted array of the enums and checking with that.

The switch trick fail with enums because an enum may have more than one enumerator with a given value.

It's an annoying issue, really.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
5

Kinda necro, but ... makes a RANGE check of int into first/last enum values (can be combined with janm's idea to make exact checks), C++11:

Header:

namespace chkenum
{
    template <class T, T begin, T end>
    struct RangeCheck
    {
    private:
        typedef typename std::underlying_type<T>::type val_t;
    public:
        static
        typename std::enable_if<std::is_enum<T>::value, bool>::type
        inrange(val_t value)
        {
            return value >= static_cast<val_t>(begin) && value <= static_cast<val_t>(end);
        }
    };

    template<class T>
    struct EnumCheck;
}

#define DECLARE_ENUM_CHECK(T,B,E) namespace chkenum {template<> struct EnumCheck<T> : public RangeCheck<T, B, E> {};}

template<class T>
inline
typename std::enable_if<std::is_enum<T>::value, bool>::type
testEnumRange(int val)
{
    return chkenum::EnumCheck<T>::inrange(val);
}

Enum declaration:

enum MinMaxType
{
     Max = 0x800, Min, Equal
};
DECLARE_ENUM_CHECK(MinMaxType, MinMaxType::Max, MinMaxType::Equal);

Usage:

bool r = testEnumRange<MinMaxType>(i);

Mainly difference of above proposed it that test function is dependent on enum type itself only.

Alex Zaharov
  • 69
  • 1
  • 3
2

Speaking about a language, there is no better way, the enum values exist compile time only and there is no way to enumerate them programatically. With a well thought infrastructure you may still be able to avoid listing all values several times, though. See Easy way to use variables of enum types as string in C?

Your sample can then be rewritten using the "enumFactory.h" provided there as:

#include "enumFactory.h"

#define ABC_ENUM(XX) \
    XX(A,=4) \
    XX(B,=8) \
    XX(C,=12) \

DECLARE_ENUM(Abc,ABC_ENUM)

int main()
{
    int v1 = 4;
    Abc v2 = static_cast< Abc >( v1 );

    #define CHECK_ENUM_CASE(name,assign) case name: std::cout<< #name <<std::endl; break;
    switch ( v2 )
    {
        ABC_ENUM(CHECK_ENUM_CASE)
        default :
            std::cout<<"no match found"<<std::endl;
    }
    #undef CHECK_ENUM_CASE
}

or even (using some more facilities already existing in that header):

#include "enumFactory.h"

#define ABC_ENUM(XX) \
    XX(A,=4) \
    XX(B,=8) \
    XX(C,=12) \

DECLARE_ENUM(Abc,ABC_ENUM)
DEFINE_ENUM(Abc,ABC_ENUM)

int main()
{
    int v1 = 4;
    Abc v2 = static_cast< Abc >( v1 );
    const char *name = GetString(v2);
    if (name[0]==0) name = "no match found";
    std::cout << name << std::endl;
}
Community
  • 1
  • 1
Suma
  • 33,181
  • 16
  • 123
  • 191
2

One possible solution without lookup - enum values should be primes

This solution is not universal:

  • will not work for flag enums
  • will not work for huge enums or big enum values (overflow)
  • might be hard to maintain consistency

but practical:

  • O(1) complexity

  • ability to add INVALID=1 as enum definition for error indication

Code:

#include<iostream>
#include <cstdint>
#include <vector>
enum class Some :uint64_t{
   A=2,
   B=3,
   C=5,
   D=7,
   E=11,
   F=13,
// etc. just stick to primes
};
static constexpr uint64_t some_checksum = static_cast<uint64_t>(Some::A)*
static_cast<uint64_t>(Some::B)*
static_cast<uint64_t>(Some::C)*
static_cast<uint64_t>(Some::D)*
static_cast<uint64_t>(Some::E)*
static_cast<uint64_t>(Some::F);

constexpr bool is_some(uint64_t v) {
    return some_checksum % v == 0;
}
constexpr bool get_some(uint64_t v, Some& out){
    if (some_checksum % v == 0) {
        out = static_cast<Some>(v);
        return true;
    }
    return false;//Something to indicate an error;
}

int main(int v) {
    Some s;
    if (get_some(v, s)){
        std::cout << "Ok\n" << static_cast<int>(s) << "\n"; 
    } else {
        std::cout << "No\n";
    }

}
0

Yet another way to do it:

#include <algorithm>
#include <iterator>
#include <iostream>

template<typename>
struct enum_traits { static constexpr void* values = nullptr; };

namespace detail
{

template<typename T>
constexpr bool is_value_of(int, void*) { return false; }

template<typename T, typename U>
constexpr bool is_value_of(int v, U)
{
    using std::begin; using std::end;

    return std::find_if(begin(enum_traits<T>::values), end(enum_traits<T>::values),
        [=](auto value){ return value == static_cast<T>(v); }
    ) != end(enum_traits<T>::values);
}

}

template<typename T>
constexpr bool is_value_of(int v)
{ return detail::is_value_of<T>(v, decltype(enum_traits<T>::values) { }); }

////////////////////
enum Abc { A = 4, B = 8, C = 12 };

template<>
struct enum_traits<Abc> { static constexpr auto values = { A, B, C }; };
decltype(enum_traits<Abc>::values) enum_traits<Abc>::values;

enum class Def { D = 1, E = 3, F = 5 };

int main()
{
    std::cout << "Abc:";
    for(int i = 0; i < 10; ++i)
        if(is_value_of<Abc>(i)) std::cout << " " << i;
    std::cout << std::endl;

    std::cout << "Def:";
    for(int i = 0; i < 10; ++i)
        if(is_value_of<Def>(i)) std::cout << " " << i;
    std::cout << std::endl;

    return 0;
}

The "ugly" part of this approach IMHO is having to define:

decltype(enum_traits<Abc>::values) enum_traits<Abc>::values

If you are not opposed to macros, you can wrap it inside a macro:

#define REGISTER_ENUM_VALUES(name, ...) \
template<> struct enum_traits<name> { static constexpr auto values = { __VA_ARGS__ }; }; \
decltype(enum_traits<name>::values) enum_traits<name>::values;
0

Another option for those using C++17 is to use fold expressions.

template<typename T, typename ...Args>
constexpr typename std::enable_if_t<std::conjunction_v<std::is_same<T, Args>...>, std::optional<T>>
ToOneOf( typename std::underlying_type_t<T> value, Args&& ...args ) noexcept
{
    static_assert( std::is_enum_v<T>, "'T' must be of type enum." );

    using U = typename std::underlying_type_t<T>;
    std::array<T, sizeof...( Args )> values{ std::forward<Args>( args )... };

    const auto it{ std::find_if( std::cbegin( values ), std::cend( values ), 
        [ value ]( auto e ) { return static_cast<U>( e ) == value; } ) };

    return it != std::end( values ) ? std::optional<T>{ *it } : std::nullopt;
}   

template<typename T, typename ...Args>
constexpr typename std::enable_if_t<std::conjunction_v<std::is_same<T, Args>...>, bool>
IsOneOf( typename std::underlying_type_t<T> value, Args&& ...args ) noexcept
{
    static_assert( std::is_enum_v<T>, "'T' must be of type enum." );

    using U = typename std::underlying_type_t<T>;
    return ( ... || ( value == static_cast<U>( args ) ) );
}

enum class Test
{
    E0 = 12,
    E1 = 56,
    E2 = 101
};

int main( )
{
    if ( IsOneOf<Test>( 12, Test::E0, Test::E1, Test::E2 ) )
    {
        std::cout << 12 << " is a valid enum value\n";
    }

    if ( auto opt{ ToOneOf<Test>( 56, Test::E0, Test::E1, Test::E2 ) } )
    {
         std::cout << static_cast<int>( opt.value( ) )  << " is a valid enum value\n";
    }
}
WBuck
  • 5,162
  • 2
  • 25
  • 36
  • Still requires duplication of the enum. What you want is to be able to extend the enum and for everything else to just keep working without modification. This example is actually worse than a simple `switch` in this regard, because with the `switch` you can at least generate a warning when you haven't handled all enum value cases. – Jaap Versteegh Feb 06 '22 at 13:21
  • `IsOneOf` just checks if 12 is one of the 3 named values here. An `enum` can also hold values which do not have a name. In particular, `0` is always legal. Finding those unnamed but legal values is the hard problem. – MSalters Feb 25 '22 at 13:34
  • @MSalters I believe that is what was desired. The OP wanted to ensure that the value read is one of the explicitly specified variants in the `enum`. – WBuck Feb 25 '22 at 13:38
0

A list of possible approaches.

  • If enum values are assigned automatically, check the enum is less than number of enums.

    enum class Enum {
        A, B, C,
    };
    constexpr size_t num_enums = 3;
    
    bool is_valid(Enum e)
    {
        using enum_t = std::underlying_type_t<Enum>;
        auto val = static_cast<enum_t>(e);
        return (val >= 0) && (val < num_enums);
    }
    
  • Duplicate the enum values into a set and check whether this set contains given enum value.
    There are a lot of answers of this kind in this post.
    If you are the one who is creating the enum, make sure you need enum to solve the problem in the first place.

  • Use a platform specific solution.
    Or better, use a 3rd party library that handles platform specific implementations.

    I would recommend using magic_enum library.

    bool is_valid(Enum e)
    {
        return magic_enum::enum_contains(e);
    }
    
Burak
  • 2,251
  • 1
  • 16
  • 33