c++: ensure enum values are unique at compile time

怎甘沉沦 提交于 2020-05-13 05:25:23

问题


I have the following enum that describes error codes:

 typedef enum {
    et_general           = 0,
    et_INVALID_CLI_FLAG  = 1,
    ...
    et_undef = 500
  } EErrorType;

The main reason why I explicitly write the enum values, is to ease the debug process.
Anyways, I wonder if there's a way, to make the compiler complain about non unique values. I can always check it at run time easily, but I'd like to avoid that.

I've read this post and reviewed this answer. As I understand, that answer suggests to generate the enum in such way that it "make it much harder to make mistakes".
I'd would like to leave the enum definition as is, or close to it.


回答1:


I'm not sure if Boost is available in your scenario, so here is a solution where the enum has to be defined in a pre-processor sequence. That sequence is then used to build the enum and a corresponding mpl::vector and we compute if the elements of the vector are unique in an odd-fashion. We might want to define a proper is_unique algorithm first, but this should do.

#include <boost/mpl/vector.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/sort.hpp>
#include <boost/mpl/unique.hpp>
#include <boost/mpl/size.hpp>

#include <boost/preprocessor/tuple/elem.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/seq/transform.hpp>


#define MYENUM ((FOO, 0))((BAR, 1))((BAZ, 2))

#define GET_NAME(_, __, elem) BOOST_PP_TUPLE_ELEM(2, 0, elem) = BOOST_PP_TUPLE_ELEM(2, 1, elem)
#define GET_VALUE(_, __, elem) boost::mpl::int_<BOOST_PP_TUPLE_ELEM(2, 1, elem)>

enum E {
  BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(GET_NAME, _, MYENUM))
};

typedef boost::mpl::sort< 
  boost::mpl::vector<
    BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(GET_VALUE, _, MYENUM))
    > 
  >::type evalues;

typedef boost::mpl::unique< evalues, boost::is_same<boost::mpl::_1, boost::mpl::_2> >::type uniqued;
static_assert(boost::mpl::size<uniqued>::value == boost::mpl::size<evalues>::value, "enum values not unique");

int main()
{

  return 0;
}

If you change the enum definition to:

#define MYENUM ((FOO, 0))((BAR, 1))((BAZ, 2))((BAZZ, 2))

you will get an error stating the static_assert failed "enum values not unique".




回答2:


This solution does modify the enum definition, so if you can put up with that...

This also assumes that the values are mainly sequential.

Don't specify the values. After the enum definition, have static_assert checks for humans to know the values.

enum EErrorType : uint16_t
{
  et_general = 0,
  et_INVALID_CLI_FLAG,
  et_FOO,
  et_BAR,
  ...
  et_undef = 500
};
static_assert(EErrorType::et_general          == 0, "Wrong enum value");
static_assert(EErrorType::et_INVALID_CLI_FLAG == 1, "Wrong enum value");
static_assert(EErrorType::et_FOO              == 2, "Wrong enum value");
static_assert(EErrorType::et_BAR              == 3, "Wrong enum value");
...
static_assert(EErrorType::et_undef            == 500, "Wrong enum value");

Thus, they are mostly automatically assigned and therefore unique, and you can also have human-readable values are for debugging and other purposes.




回答3:


One can write a dummy switch statement with the enum values as labels – that will guarantee their uniqueness. Placed in a dummy unreferenced function, it won't get into the executable.




回答4:


The best to make sure the values are all unique, is simply to not explicitly define them. For your debug, simply write a helper program that gives you the values, something like:

awk 'NR>1{print $1, i; i +=1} eerror.h

this assumes the first line of eerror.h is:

typedef enum {



回答5:


C++ doesn't provide any features to restrict the enumerations in this way.

You can do it at run-time with a little hackery if you're prepared to use a macro to create your enum (see below), and you may be able to use something like gccxml, doxygen or some other parser to verify a list of enumerations at some point in your build process.

Simpler but very feeble, if you adopted a coding standard of an enumeration per line you could relate differences in __LINE__ numbers to enumerations, but that wouldn't catch e.g. a duplicate followed by a gap.

Example of run-time indexing of enumerations using a macro (not intended to be particularly robust - e.g. if enumeration values could have embedded commas, comments etc. you'd need to create a stack of matching <[{( and quotes etc..

#include <iostream>
#include <string>
#include <map>

namespace Benum
{
    struct Meta
    {
        Meta(const char* p, int* p_values)
        {
            while (*p)
            {
                if (isalnum(*p) || *p == '_')
                {
                    const char* p_from = p;
                    while (isalnum(*p) || *p == '_')
                        ++p;
                    std::string idn = std::string(p_from, p - p_from);
                    int_to_string_[*p_values] = idn;
                    string_to_int_[idn] = *p_values;
                    ++p_values;
                }
                else if (*p == '=')
                    while (*p && *p != ',')
                        ++p;
                else
                    ++p;
            }
        }
        std::ostream& out(std::ostream& os, int i) const
        {
            Int_To_String::const_iterator it = int_to_string_.find(i);
            if (it != int_to_string_.end())
                return os << it->second;
            else
                return os << "<unmatched enum " << i << '>';
        }
        typedef std::map<int, std::string> Int_To_String;
        std::map<int, std::string> int_to_string_;
        std::map<std::string, int> string_to_int_;
    };

    template <typename T>
    struct Incrementing
    {
        Incrementing(int n) : n_(n) { s_next_implicit_ = n + 1; }
        Incrementing() : n_(s_next_implicit_++) { }
        operator int() const { return n_; }
        int n_;
        static int s_next_implicit_;
    };

    template <typename T>
    int Incrementing<T>::s_next_implicit_;
}

#define BENUM(IDN, ...) \
    enum IDN ## _Enum { __VA_ARGS__ }; \
    struct IDN { \
        typedef IDN ## _Enum Enum; \
        IDN(Enum e) : e_(e) { } \
        IDN& operator=(Enum e) { e_ = e; return *this; } \
        operator Enum() const { return e_; } \
        friend std::ostream& operator<<(std::ostream& os, Enum e) { \
            return IDN::meta().out(os, e); \
        } \
        static const Benum::Meta& meta() { \
            static Benum::Incrementing<IDN> __VA_ARGS__; \
            static int values[] = { __VA_ARGS__ }; \
            static Benum::Meta m(#__VA_ARGS__, values); \
            return m; \
        } \
        Enum e_; \
    };

// benum example usage...

BENUM(My_Enum, One = 1, Two = 2, Three = 3, Four = 4, Whats_Next);

int main()
{
    std::cout << One << ' ' << Two << ' ' << Three << ' ' <<
        Whats_Next << '\n';
    const Benum::Meta& meta = My_Enum::meta();
    for (std::map<int, std::string>::const_iterator i = meta.int_to_string_.begin();
        i != meta.int_to_string_.end(); ++i)
        std::cout << i->first << ' ' << i->second << '\n';
}


来源:https://stackoverflow.com/questions/21378938/c-ensure-enum-values-are-unique-at-compile-time

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!