enum to string in modern C++11 / C++14 / C++17 and future C++20

266,316

Solution 1

Magic Enum header-only library provides static reflection for enums (to string, from string, iteration) for C++17.

#include <magic_enum.hpp>

enum Color { RED = 2, BLUE = 4, GREEN = 8 };

Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name)
if (color.has_value()) {
  // color.value() -> Color::GREEN
};

For more examples check home repository https://github.com/Neargye/magic_enum.

Where is the drawback?

This library uses a compiler-specific hack (based on __PRETTY_FUNCTION__ / __FUNCSIG__), which works on Clang >= 5, MSVC >= 15.3 and GCC >= 9.

Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.

  • If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MIN must be less or equals than 0 and must be greater than INT16_MIN.

  • MAGIC_ENUM_RANGE_MAX must be greater than 0 and must be less than INT16_MAX.

  • If need another range for specific enum type, add specialization enum_range for necessary enum type.

    #include <magic_enum.hpp>
    
    enum number { one = 100, two = 200, three = 300 };
    
    namespace magic_enum {
    template <>
      struct enum_range<number> {
        static constexpr int min = 100;
        static constexpr int max = 300;
    };
    }
    

Solution 2

(The approach of the better_enums library)

There is a way to do enum to string in current C++ that looks like this:

ENUM(Channel, char, Red = 1, Green, Blue)

// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };

Usage:

Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
c._to_string();                                  // string "Green"

for (Channel c : Channel::_values())
    std::cout << c << std::endl;

// And so on...

All operations can be made constexpr. You can also implement the C++17 reflection proposal mentioned in the answer by @ecatmur.

  • There is only one macro. I believe this is the minimum possible, because preprocessor stringization (#) is the only way to convert a token to a string in current C++.
  • The macro is pretty unobtrusive – the constant declarations, including initializers, are pasted into a built-in enum declaration. This means they have the same syntax and meaning as in a built-in enum.
  • Repetition is eliminated.
  • The implementation is most natural and useful in at least C++11, due to constexpr. It can also be made to work with C++98 + __VA_ARGS__. It is definitely modern C++.

The macro's definition is somewhat involved, so I'm answering this in several ways.

  • The bulk of this answer is an implementation that I think is suitable for the space constraints on StackOverflow.
  • There is also a CodeProject article describing the basics of the implementation in a long-form tutorial. [Should I move it here? I think it's too much for a SO answer].
  • There is a full-featured library "Better Enums" that implements the macro in a single header file. It also implements N4428 Type Property Queries, the current revision of the C++17 reflection proposal N4113. So, at least for enums declared through this macro, you can have the proposed C++17 enum reflection now, in C++11/C++14.

It is straightforward to extend this answer to the features of the library – nothing "important" is left out here. It is, however, quite tedious, and there are compiler portability concerns.

Disclaimer: I am the author of both the CodeProject article and the library.

You can try the code in this answer, the library, and the implementation of N4428 live online in Wandbox. The library documentation also contains an overview of how to use it as N4428, which explains the enums portion of that proposal.


Explanation

The code below implements conversions between enums and strings. However, it can be extended to do other things as well, such as iteration. This answer wraps an enum in a struct. You can also generate a traits struct alongside an enum instead.

The strategy is to generate something like this:

struct Channel {
    enum _enum : char { __VA_ARGS__ };
    constexpr static const Channel          _values[] = { __VA_ARGS__ };
    constexpr static const char * const     _names[] = { #__VA_ARGS__ };

    static const char* _to_string(Channel v) { /* easy */ }
    constexpr static Channel _from_string(const char *s) { /* easy */ }
};

The problems are:

  1. We will end up with something like {Red = 1, Green, Blue} as the initializer for the values array. This is not valid C++, because Red is not an assignable expression. This is solved by casting each constant to a type T that has an assignment operator, but will drop the assignment: {(T)Red = 1, (T)Green, (T)Blue}.
  2. Similarly, we will end up with {"Red = 1", "Green", "Blue"} as the initializer for the names array. We will need to trim off the " = 1". I am not aware of a great way to do this at compile time, so we will defer this to run time. As a result, _to_string won't be constexpr, but _from_string can still be constexpr, because we can treat whitespace and equals signs as terminators when comparing with untrimmed strings.
  3. Both the above need a "mapping" macro that can apply another macro to each element in __VA_ARGS__. This is pretty standard. This answer includes a simple version that can handle up to 8 elements.
  4. If the macro is to be truly self-contained, it needs to declare no static data that requires a separate definition. In practice, this means arrays need special treatment. There are two possible solutions: constexpr (or just const) arrays at namespace scope, or regular arrays in non-constexpr static inline functions. The code in this answer is for C++11 and takes the former approach. The CodeProject article is for C++98 and takes the latter.

Code

#include <cstddef>      // For size_t.
#include <cstring>      // For strcspn, strncpy.
#include <stdexcept>    // For runtime_error.



// A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
// macro(a) macro(b) macro(c) ...
// The helper macro COUNT(a, b, c, ...) expands to the number of
// arguments, and IDENTITY(x) is needed to control the order of
// expansion of __VA_ARGS__ on Visual C++ compilers.
#define MAP(macro, ...) \
    IDENTITY( \
        APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
            (macro, __VA_ARGS__))

#define CHOOSE_MAP_START(count) MAP ## count

#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))

#define IDENTITY(x) x

#define MAP1(m, x)      m(x)
#define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
#define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
#define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
#define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
#define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
#define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
#define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))

#define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
    count

#define COUNT(...) \
    IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))



// The type "T" mentioned above that drops assignment operations.
template <typename U>
struct ignore_assign {
    constexpr explicit ignore_assign(U value) : _value(value) { }
    constexpr operator U() const { return _value; }

    constexpr const ignore_assign& operator =(int dummy) const
        { return *this; }

    U   _value;
};



// Prepends "(ignore_assign<_underlying>)" to each argument.
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
    IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))

// Stringizes each argument.
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))



// Some helpers needed for _from_string.
constexpr const char    terminators[] = " =\t\r\n";

// The size of terminators includes the implicit '\0'.
constexpr bool is_terminator(char c, size_t index = 0)
{
    return
        index >= sizeof(terminators) ? false :
        c == terminators[index] ? true :
        is_terminator(c, index + 1);
}

constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                 size_t index = 0)
{
    return
        is_terminator(untrimmed[index]) ? s[index] == '\0' :
        s[index] != untrimmed[index] ? false :
        matches_untrimmed(untrimmed, s, index + 1);
}



// The macro proper.
//
// There are several "simplifications" in this implementation, for the
// sake of brevity. First, we have only one viable option for declaring
// constexpr arrays: at namespace scope. This probably should be done
// two namespaces deep: one namespace that is likely to be unique for
// our little enum "library", then inside it a namespace whose name is
// based on the name of the enum to avoid collisions with other enums.
// I am using only one level of nesting.
//
// Declaring constexpr arrays inside the struct is not viable because
// they will need out-of-line definitions, which will result in
// duplicate symbols when linking. This can be solved with weak
// symbols, but that is compiler- and system-specific. It is not
// possible to declare constexpr arrays as static variables in
// constexpr functions due to the restrictions on such functions.
//
// Note that this prevents the use of this macro anywhere except at
// namespace scope. Ironically, the C++98 version of this, which can
// declare static arrays inside static member functions, is actually
// more flexible in this regard. It is shown in the CodeProject
// article.
//
// Second, for compilation performance reasons, it is best to separate
// the macro into a "parametric" portion, and the portion that depends
// on knowing __VA_ARGS__, and factor the former out into a template.
//
// Third, this code uses a default parameter in _from_string that may
// be better not exposed in the public interface.

#define ENUM(EnumName, Underlying, ...)                               \
namespace data_ ## EnumName {                                         \
    using _underlying = Underlying;                                   \
    enum { __VA_ARGS__ };                                             \
                                                                      \
    constexpr const size_t           _size =                          \
        IDENTITY(COUNT(__VA_ARGS__));                                 \
                                                                      \
    constexpr const _underlying      _values[] =                      \
        { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     \
                                                                      \
    constexpr const char * const     _raw_names[] =                   \
        { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         \
}                                                                     \
                                                                      \
struct EnumName {                                                     \
    using _underlying = Underlying;                                   \
    enum _enum : _underlying { __VA_ARGS__ };                         \
                                                                      \
    const char * _to_string() const                                   \
    {                                                                 \
        for (size_t index = 0; index < data_ ## EnumName::_size;      \
             ++index) {                                               \
                                                                      \
            if (data_ ## EnumName::_values[index] == _value)          \
                return _trimmed_names()[index];                       \
        }                                                             \
                                                                      \
        throw std::runtime_error("invalid value");                    \
    }                                                                 \
                                                                      \
    constexpr static EnumName _from_string(const char *s,             \
                                           size_t index = 0)          \
    {                                                                 \
        return                                                        \
            index >= data_ ## EnumName::_size ?                       \
                    throw std::runtime_error("invalid identifier") :  \
            matches_untrimmed(                                        \
                data_ ## EnumName::_raw_names[index], s) ?            \
                    (EnumName)(_enum)data_ ## EnumName::_values[      \
                                                            index] :  \
            _from_string(s, index + 1);                               \
    }                                                                 \
                                                                      \
    EnumName() = delete;                                              \
    constexpr EnumName(_enum value) : _value(value) { }               \
    constexpr operator _enum() const { return (_enum)_value; }        \
                                                                      \
  private:                                                            \
    _underlying     _value;                                           \
                                                                      \
    static const char * const * _trimmed_names()                      \
    {                                                                 \
        static char     *the_names[data_ ## EnumName::_size];         \
        static bool     initialized = false;                          \
                                                                      \
        if (!initialized) {                                           \
            for (size_t index = 0; index < data_ ## EnumName::_size;  \
                 ++index) {                                           \
                                                                      \
                size_t  length =                                      \
                    std::strcspn(data_ ## EnumName::_raw_names[index],\
                                 terminators);                        \
                                                                      \
                the_names[index] = new char[length + 1];              \
                                                                      \
                std::strncpy(the_names[index],                        \
                             data_ ## EnumName::_raw_names[index],    \
                             length);                                 \
                the_names[index][length] = '\0';                      \
            }                                                         \
                                                                      \
            initialized = true;                                       \
        }                                                             \
                                                                      \
        return the_names;                                             \
    }                                                                 \
};

and

// The code above was a "header file". This is a program that uses it.
#include <iostream>
#include "the_file_above.h"

ENUM(Channel, char, Red = 1, Green, Blue)

constexpr Channel   channel = Channel::_from_string("Red");

int main()
{
    std::cout << channel._to_string() << std::endl;

    switch (channel) {
        case Channel::Red:   return 0;
        case Channel::Green: return 1;
        case Channel::Blue:  return 2;
    }
}

static_assert(sizeof(Channel) == sizeof(char), "");

The program above prints Red, as you would expect. There is a degree of type safety, since you can't create an enum without initializing it, and deleting one of the cases from the switch will result in a warning from the compiler (depending on your compiler and flags). Also, note that "Red" was converted to an enum during compilation.

Solution 3

For C++17 C++20, you will be interested in the work of the Reflection Study Group (SG7). There is a parallel series of papers covering wording (P0194) and rationale, design and evolution (P0385). (Links resolve to the latest paper in each series.)

As of P0194r2 (2016-10-15), the syntax would use the proposed reflexpr keyword:

meta::get_base_name_v<
  meta::get_element_m<
    meta::get_enumerators_m<reflexpr(MyEnum)>,
    0>
  >

For example (adapted from Matus Choclik's reflexpr branch of clang):

#include <reflexpr>
#include <iostream>

enum MyEnum { AAA = 1, BBB, CCC = 99 };

int main()
{
  auto name_of_MyEnum_0 = 
    std::meta::get_base_name_v<
      std::meta::get_element_m<
        std::meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
    >;

  // prints "AAA"
  std::cout << name_of_MyEnum_0 << std::endl;
}

Static reflection failed to make it into C++17 (rather, into the probably-final draft presented at the November 2016 standards meeting in Issaquah) but there is confidence that it will make it into C++20; from Herb Sutter's trip report:

In particular, the Reflection study group reviewed the latest merged static reflection proposal and found it ready to enter the main Evolution groups at our next meeting to start considering the unified static reflection proposal for a TS or for the next standard.

Solution 4

This is similar to Yuri Finkelstein; but does not required boost. I am using a map so you can assign any value to the enums, any order.

Declaration of enum class as:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

The following code will automatically create the enum class and overload:

  • '+' '+=' for std::string
  • '<<' for streams
  • '~' just to convert to string (Any unary operator will do, but I personally don't like it for clarity)
  • '*' to get the count of enums

No boost required, all required functions provided.

Code:

#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())

std::vector<std::string> splitString(std::string str, char sep = ',') {
    std::vector<std::string> vecString;
    std::string item;

    std::stringstream stringStream(str);

    while (std::getline(stringStream, item, sep))
    {
        vecString.push_back(item);
    }

    return vecString;
}

#define DECLARE_ENUM_WITH_TYPE(E, T, ...)                                                                     \
    enum class E : T                                                                                          \
    {                                                                                                         \
        __VA_ARGS__                                                                                           \
    };                                                                                                        \
    std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__));                                    \
    std::ostream &operator<<(std::ostream &os, E enumTmp)                                                     \
    {                                                                                                         \
        os << E##MapName[static_cast<T>(enumTmp)];                                                            \
        return os;                                                                                            \
    }                                                                                                         \
    size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); }                                 \
    std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; }                          \
    std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
    std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
    std::string &operator+=(std::string &str, E enumTmp)                                                      \
    {                                                                                                         \
        str += E##MapName[static_cast<T>(enumTmp)];                                                           \
        return str;                                                                                           \
    }                                                                                                         \
    E operator++(E &enumTmp)                                                                                  \
    {                                                                                                         \
        auto iter = E##MapName.find(static_cast<T>(enumTmp));                                                 \
        if (iter == E##MapName.end() || std::next(iter) == E##MapName.end())                                  \
            iter = E##MapName.begin();                                                                        \
        else                                                                                                  \
        {                                                                                                     \
            ++iter;                                                                                           \
        }                                                                                                     \
        enumTmp = static_cast<E>(iter->first);                                                                \
        return enumTmp;                                                                                       \
    }                                                                                                         \
    bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }

#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
    STRING_REMOVE_CHAR(strMap, ' ');
    STRING_REMOVE_CHAR(strMap, '(');

    std::vector<std::string> enumTokens(splitString(strMap));
    std::map<T, std::string> retMap;
    T inxMap;

    inxMap = 0;
    for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
    {
        // Token: [EnumName | EnumName=EnumValue]
        std::string enumName;
        T enumValue;
        if (iter->find('=') == std::string::npos)
        {
            enumName = *iter;
        }
        else
        {
            std::vector<std::string> enumNameValue(splitString(*iter, '='));
            enumName = enumNameValue[0];
            //inxMap = static_cast<T>(enumNameValue[1]);
            if (std::is_unsigned<T>::value)
            {
                inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
            }
            else
            {
                inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
            }
        }
        retMap[inxMap++] = enumName;
    }

    return retMap;
}

Example:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

int main(void) {
    TestEnumClass first, second;
    first = TestEnumClass::FOUR;
    second = TestEnumClass::TWO;

    std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)

    std::string strOne;
    strOne = ~first;
    std::cout << strOne << std::endl; // FOUR

    std::string strTwo;
    strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
    std::cout << strTwo << std::endl; // Enum-TWOTHREE-test

    std::string strThree("TestEnumClass: ");
    strThree += second;
    std::cout << strThree << std::endl; // TestEnumClass: TWO
    std::cout << "Enum count=" << *first << std::endl;
}

You can run the code here

Solution 5

Back in 2011 I spent a weekend fine-tuning a macro-based solution and ended up never using it.

My current procedure is to start Vim, copy the enumerators in an empty switch body, start a new macro, transform the first enumerator into a case statement, move the cursor to the beginning of the next line, stop the macro and generate the remaining case statements by running the macro on the other enumerators.

Vim macros are more fun than C++ macros.

Real-life example:

enum class EtherType : uint16_t
{
    ARP   = 0x0806,
    IPv4  = 0x0800,
    VLAN  = 0x8100,
    IPv6  = 0x86DD
};

I will create this:

std::ostream& operator<< (std::ostream& os, EtherType ethertype)
{
    switch (ethertype)
    {
        case EtherType::ARP : return os << "ARP" ;
        case EtherType::IPv4: return os << "IPv4";
        case EtherType::VLAN: return os << "VLAN";
        case EtherType::IPv6: return os << "IPv6";
        // omit default case to trigger compiler warning for missing cases
    };
    return os << static_cast<std::uint16_t>(ethertype);
}

And that's how I get by.

Native support for enum stringification would be much better though. I'm very interested to see the results of the reflection workgroup in C++17.

An alternative way to do it was posted by @sehe in the comments.

Share:
266,316
oHo
Author by

oHo

Excellent relational skills, both face to face and with a broader audience. Back-end developer loving Python, Go, C++, Linux, Ansible, Docker, Bar-metal hosting, InfluxDB. Experiences in stock market place, bank &amp; finance, crypto-currencies, blockchain and open finance. Practitioner of self-governance: trusting &amp; empowering my colleagues, and providing both alignment and autonomy. Good writing skills. Past hobbies: Organized C++ Meetups in Paris and on worldwide C++FRUG community Member of my city Greeter association and Hospitality Club too. Created website LittleMap.org to create/share libre maps for impair people. Wrote one piece of the NFC standards (at MasterCard) Developed an early Linux mobile, before Android release. Developed games on my old VG5000µ and CPC6128 computers.

Updated on November 30, 2021

Comments

  • oHo
    oHo over 2 years

    Contrary to all other similar questions, this question is about using the new C++ features.

    After reading many answers, I did not yet find any:

    Example

    An example is often better than a long explanation.
    You can compile and run this snippet on Coliru.
    (Another former example is also available)

    #include <map>
    #include <iostream>
    
    struct MyClass
    {
        enum class MyEnum : char {
            AAA = -8,
            BBB = '8',
            CCC = AAA + BBB
        };
    };
    
    // Replace magic() by some faster compile-time generated code
    // (you're allowed to replace the return type with std::string
    // if that's easier for you)
    const char* magic (MyClass::MyEnum e)
    {
        const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
            { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
            { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
            { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
        };
        auto   it  = MyEnumStrings.find(e);
        return it == MyEnumStrings.end() ? "Out of range" : it->second;
    }
    
    int main()
    {
       std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
       std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
       std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
    }
    

    Constraints

    • Please no valueless duplication of other answers or basic link.
    • Please avoid bloat macro-based answer, or try to reduce the #define overhead as minimum as possible.
    • Please no manual enum -> string mapping.

    Nice to have

    • Support enum values starting from a number different from zero
    • Support negative enum values
    • Support fragmented enum values
    • Support class enum (C++11)
    • Support class enum : <type> having any allowed <type> (C++11)
    • Compile-time (not run-time) conversions to a string,
      or at least fast execution at run-time (e.g. std::map is not a great idea...)
    • constexpr (C++11, then relaxed in C++14/17/20)
    • noexcept (C++11)
    • C++17/C++20 friendly snippet

    One possible idea could be using the C++ compiler capabilities to generate C++ code at compilation-time using meta-programming tricks based on variadic template class and constexpr functions...

  • Quentin
    Quentin about 9 years
    This code block is a crazy journey through the amazing landscapes of metaprogramming black magic. I actually felt relieved upon reaching main — Home, sweet home !
  • oHo
    oHo about 9 years
    Just added a link to coliru to check output (there are some warnings, click on the link within your answer). I have also split into Lib/Usage. Does the stuff namespace xxx can be moved to the header place? You can say in the intro your use boost/preprocessor.hpp and therefore the answer is modern C++ compliant. Please fix the warnings and clean a bit the source code for better quality.
  • PlasmaHH
    PlasmaHH about 9 years
    @olibre: It is copypastad from I think 5 different headers in our library. The enum_cast is from another more general part but I thought to add it too to see what the do_enum_cast in the macro is for.. The warnings is just from my main<tab> of vim including args I do not use. I dont think this code can be really cleaned, it is just to show what can be done and should not be ;) and if I change it here it isnt the code I use in production anymore...it is one of those fragile things that once it works you better never touch since it might collapse in ways no one could predict.
  • oHo
    oHo about 9 years
    All right Plasma, I see this can be seen as a Proof Of Concept. But there is too much macro overhead to be up-voted. Nevertheless thanks for sharing. Cheers
  • oHo
    oHo about 9 years
    Hi Plasma. I have performed a deep source code cleaning + completed by compilation and run output. Please check my edit. I hope this is OK for you. Is the answer more valuable? However, the macro overhead is still horrible! Have a nice day :-) Cheers
  • PlasmaHH
    PlasmaHH about 9 years
    @olibre: I think it can't be done without horrible overhead, but if it is in different layers of one library it can be handled. I think thats what libraries are for, hide the horrible stuff. Ever tried reading boost code? especially the mpl part before C++11 ...
  • oHo
    oHo about 9 years
    Yes you are right, I agree. Have a look at the Matthieu's answer also based on Boost Preprocessor but using less macro overhead. OK, his solution provides less services than your lib... Cheers
  • PlasmaHH
    PlasmaHH about 9 years
    @olibre: yes, getting the x=y part nicely was a major hurdle. Also I was preferring the (a,b,c) syntax instead of (a)(b)(c) for having things look more like real enum lists. Both dramatically increased the need for magic.
  • sehe
    sehe about 9 years
    I do exactly this. Although I usually use Surround vim and block selections along the way
  • StackedCrooked
    StackedCrooked about 9 years
    @sehe Interesting. I should have a look at "surround" because I require way to many keystrokes currently.
  • sehe
    sehe about 9 years
    Here it is in full gory, no macros (unless . counts): i.imgur.com/gY4ZhBE.gif
  • ecatmur
    ecatmur almost 9 years
    @antron sorry your edit got rejected; I'd have approved it if I'd seen it in time. I hadn't seen N4428 so thanks for giving the heads up.
  • antron
    antron almost 9 years
    No problem, thanks for incorporating it. I kind of wonder why it got rejected. I see the "doesn't make it more accurate" boilerplate reason, but it's clearly more accurate for the present day.
  • oHo
    oHo over 8 years
    Hi Madwyn. Thanks for your idea. But how it works? What is the overhead? (zero overhead or does it creates extra data?). Your proposition seems fine, but unfortunately, one statement DEF_MSG has to be used/updated/maintained for each enum value :-/ And this is what ideally we would like to stop doing... Cheers
  • Madwyn
    Madwyn over 8 years
    Thank you for the reply, @olibre. Please check the updated answer. I don't see overhead here, except a function call is needed for accessing the strings. DEF_MSG makes the enum closely paired with the message, although it has some limitations.
  • JAL
    JAL over 8 years
    While this code may answer the question, it would be better to explain how it solves the problem without introducing others and why to use it. Code-only answers are not useful in the long run.
  • desperado_98
    desperado_98 over 8 years
    hey guys , i am sorry i don't speak english very well.
  • desperado_98
    desperado_98 over 8 years
    automatically ENUM_MAKE macro generate 'enum class' and helper class with 'enum reflection function'. / In order to reduce mistakes, at once Everything is defined with only one ENUM_MAKE. The advantage of this code is automatically created for reflection and a close look at macro code ,easy-to-understand code. 'enum to string' , 'string to enum' performance both is algorith O(1). Disadvantages is when first use , helper class for enum relection 's string vector and map is initialized. but If you want you'll also be pre-initialized.
  • oHo
    oHo over 8 years
    Thank you for the appended explanation in your answer :-) Your lib is fine but cannot be used for multiple enums :-/ What about the support of enum class (C++11) ? You can use constexpr to limit _g_messages at run-time. Support multiple enum types (avoiding _g_messages) using meta-programming (type conveying {enum-type, enum-value}) or maybe template variables (C++14). I think your lib does not (yet?) fit the C++11/14/17 requirements. What do you think? Cheers ;-)
  • oHo
    oHo over 8 years
    Hi desperado_98. Thank you for your contribution. Please edit your answer and insert in it your comment content. The compiler can compute your example at compile-time if you use some meta-programming tricks and constexpr. I mean the functions toName() and toType() can be evaluated during the compilation and not during the execution (run-time). Please adopt C++11/14/17 style in your answer. Cheers ;-)
  • oHo
    oHo over 8 years
    Moreover: Is your macro compatible with enum class MyEnum : short { A, B, C }; ?
  • Madwyn
    Madwyn over 8 years
    Thanks for the following up. I learned something new today! The enum class and template variables look good. I think my answer was a little bit "off topic" as it was C flavoured.
  • desperado_98
    desperado_98 over 8 years
    olibre, i don't know well macro but it did not seem to recusive. therefore VA_ARGS macro must be parsing in runtime. i could not solve in compile time.
  • desperado_98
    desperado_98 over 8 years
    enum class MyEnum : short { A, B, C }; <- you can test... , i did not test not yet but it seem no problem
  • user673679
    user673679 over 8 years
    This looks quite neat (is it possible to just not define the unspecialized variable?). Maybe I'm missing something, though as I don't see how it handles the runtime case at all.
  • oHo
    oHo about 8 years
    Thank you for your interesting answer. Please rework your proposal in order to use your macro within a class. See coliru.stacked-crooked.com/a/00d362eba836d04b Moreover try to use constexprand noexept keywords when possible. Cheers :-)
  • FKaria
    FKaria about 8 years
    The question didn't specify this requisite.
  • oHo
    oHo about 8 years
    Question updated (see example). Two other requirements: (1) support type of enum and (2) values can be different from sequence 0, 1, 2...
  • oHo
    oHo about 8 years
    wow! Very original and innovative idea :-) I hope you have the courage to upgrade your generator in order to provide a constexpr and noexcept version ;-) I have also just stared your GitHub project ;-) Cheers
  • Mense
    Mense about 8 years
    Updated the generator. The functions will now be always constexpr and enum : <type> is now supported. Thanks for the star :)
  • FKaria
    FKaria almost 8 years
    I reworked my solution to it can be used inside a class. I haven't figured out how to make the values different from 0,1,2,.. though.
  • yeoman
    yeoman almost 8 years
    Macros not only impede readability once they reach a certain level of complexity but the error messages created are especially hideous. Moreover, it's so easy to break a complex macro's intent unless it's done really meticulously. I personally try to keep macros around three to five lines max and only use them where there is a real benefit without any reasonable alternatives...
  • oHo
    oHo almost 8 years
    Thank you very much for sharing your generator in two languages :-) But do you have any idea how to generate at compile-time? For instance, can we imagine translating your generator using CMake statements in order to refresh the C++ generated code when input data is changed? My dream is to force the C++ compiler to generate enums at compilation using meta-programming (variadic template class and constexpr functions).
  • oHo
    oHo almost 8 years
    Hi FKaria. Thank you very much for your rework. I did some changes in order to support several enums within the same class, and also to support the enum class X : Type format. Please review my contribution: coliru.stacked-crooked.com/a/b02db9190d3491a3 What do you think about my changes? Do you have any idea to support values set within enum? Example enum E{A=3, B=6, C=A-B}; Cheers
  • yeoman
    yeoman almost 8 years
    Otoh, in case it's too cumbersome to add a custom cmake command, you can automate your IDE or call the gererator manually and have the output in source control. It's sometimes a good idea to have generated code in source control anyway, as long it's not too much, and people understand that they're not supposed to make manual changes, because it's sometimes interesting to look at the history of the generated files when you're debugging something weird and have the suspicion that a recent change to the generator may have broken something :)
  • yeoman
    yeoman almost 8 years
    About generating things at compile time, that's so easy in LISP because the syntax is so extremely clean and easy. That's helped by the fact that it's dynamically typed, which allows it to be terse and readable without much syntax. The equivalent of LISP macros in C++ would need a very complicated way to describe the AST of what you're trying to generate. And an AST for C++ is never pretty :(
  • yeoman
    yeoman almost 8 years
    Directly in Make instead of cmake, it's super easy btw. Just generate .h and .cpp targets for each .enum file via find, and have these targets depend on said enum defs, so they're automatically re-generated once the .enum def files change. It's probably a lot easier in cmake because it's full of magic for this kind of things but I regularly use Make, ant, and gradle, but only have limited knowledge of Maven, cmake, and grunt :)
  • oHo
    oHo almost 8 years
    Thanks for your answer :-) I think most of the C++ developers will appreciate if your generator could detect enums directly within C++ code like enum class Hallo{ First=5, Second=6, Third=7, Fourth=8}; or in several lines :-D Do you think you can adapt your generator in order to detect an enum within a C++ file? The best could be to generate code only on detection of a tag like /*<Generate enum to string here>*/. Then your generator writes in-place the corresponding C++ generated code (replacing the previous generated code). ^_^ What an awesome generator isn't it? Cheers :-)
  • yeoman
    yeoman almost 8 years
    Replacing code in place is generally a bad idea. What if you decide to add twenty new enum fields? then this input is lost because it was overwritten. plus, when you attempt to make it SMART, leaving the input inn place and adding the generated code right beneath it, and replace your generated code by the newly generated code in case of a change on the input, you can bet that sooner or later something will go wrong and your generator deletes some code it wasn't supposed to -.-
  • yeoman
    yeoman almost 8 years
    If having a text file in your project that is not C++ source is unacceptable to you, and you get paid for your time and not for the actual work you get done, I suggest manually writing the equivalent manual enums. But don't forget to write a couple of unit tests for each one so you don't accidentally build in the typical copy & paste errors :)
  • oHo
    oHo almost 8 years
    Thank you for your feedback :-) +1 You are right, changing content of a C++ source file is not a good idea (for example, the file could be read-only). Therefore, the generated code should be somewhere with the other generated files (e.g. *.o files). Using your idea, the build tool chain (Makefile or CMake or ...) may call an external module (in Java or Python...) to detect the enums directly within the C++ code and generate the corresponding enum_to_string functions. Your code may evolve to use clang-parser in order to understand the C++ source Abstract Syntax Tree (AST)... Cheers
  • yeoman
    yeoman almost 8 years
    That adds a lot of complexity and comes at a high price. C++ code is easy to debug. An object file without real source is not -.-
  • yeoman
    yeoman almost 8 years
    Btw. what is the problem with having source files in a simple generator input language that automatically are built into generated C++ source files that are then compiled via the C++ compiler, all automatically? What is the pain point there for you? :):)
  • oHo
    oHo almost 8 years
    Sorry about the confusion on generated C++ and object files. I mean the generated C++ files must be written in a read/write directory. And the object files must also be written in read/write directories. Generated C++ files should not be written along the input C++ files (e.g. the directory could be read-only). My conclusion is the generated C++ files should be written in similar directory tree as the object files. Like object files, these generated C++ files are temporaries. On my opinion, all these temporaries should be all removed on full cleanup.
  • oHo
    oHo almost 8 years
    Imagine two tools to generate C++. Both tools are easy to use and reliable. The difference: (1) With the first tool, the developer writes pseudo enum code in a configuration file and the tool generates both enum and enum_to_string function. (2) With the second tool, the developer continues to write enum in C++ (within the right namespace/class), and the tool generates just the corresponding enum_to_string function. I think most developers will prefer the second tool. I do not say the first tool has a problem. I just think the second is more comfortable. Do you agree?
  • yeoman
    yeoman almost 8 years
    Yes, an extra directory for generated source files is a must :) I mostly call it "generated", and I generate a warning comment on top of every generated source file that states that it's going to be overwritten :)
  • yeoman
    yeoman almost 8 years
    If the enum is a C++ enum class, then yes, you can write it in a header file. But you still need information about the name of the generated header and source file and the header file to read the enum from etc. in the config, so you end up with two sources, plus the resulting enum is then spread across several files, and the extra header file must be included manually because the generator won't touch any of your manual code files.
  • yeoman
    yeoman almost 8 years
    If, otoh, the enum is in fact a typesafe enum class with all kinds of potential extra functionality built in, you want all of it generated in any case because the header itself then contains a lot of boilerplate. I usually use this approach, and I also generate lots of other things like structs, connection classes &c. for several platforms (C++, Java, Python, C#, Objective-C, Swift), so I work with extra IDL files in any case, and the enums live there with everything else, and that feels quite natural to me by now :)
  • yeoman
    yeoman almost 8 years
    For C++ enums, and enums only, with extra header files for enum support functions, I think working with the llvm to get at the AST is a bad idea because not only is the llvm a really large code base but it's also a MESS. I've rarely seen less readable code than in the llvm.
  • yeoman
    yeoman almost 8 years
    Plus, there's a world beyond the llvm, and enum classes are sufficiently easy to parse with simple regex, especially with a helper comment containing all information for the genrator so there is NO extra config file (important in this case I think, because single source is always a great idea), and Python does everything, independent of the llvm, so you can even do it in Windows or in a classic gcc setup (and I really dislike using several compilers at once) :)
  • yeoman
    yeoman almost 8 years
    So in a setting with C++ enums, your idea wins, but with python, and there's no extra config file :)
  • oHo
    oHo almost 8 years
    Thank you Yeoman for your explanations. You are right, moreover writing a generator reading a simple text input is a lot easier than from a complex C++ input (the C++ enum may be obfuscated by a #define MACRO).
  • oHo
    oHo almost 8 years
    But using Clang AST should not be so difficult. For example, Loïc has presented at C++ Paris meetup (C++FRUG) a tool to visualize the AST from a C++ code you paste in its left windows: Clang-ast-viewer. We could imagine that the code is generated only if the function std::string EnumX_to_string(EnumX) is declared. The generated filename *.cpp is deduced from the filename where this function is declared. No need to include it. I have enjoyed our constructive discussion ;-) Cheers
  • FKaria
    FKaria almost 8 years
    Several enums within the same class should be supported with my solution. Although you need to use the X::Enum syntax which may be a bit ugly. I wasn't aware of the possibility to specify a type for the enums, that's a nice addition. Regarding the enum assignment, I haven't looked a it yet but I guess it will require some macro of the style ENUM(X, (A,3)(B,6)(C,A-B)) if it can be done at all.
  • yeoman
    yeoman almost 8 years
    :):) using clang's AST is certainly far from hard or impossible. But compared to doing nothing at all because I happen to have my input IDLs and parsing tools up and running, it would indeed be a monumental effort of several days' work :)
  • yeoman
    yeoman almost 8 years
    Btw. even though the code base of the llvm is a mess, it's still fascinating, and I truly recommend you have a look inside and maybe try to get your hands at the AST of an enum and try to create something from it. It'll be very insightful about how things actually work in the llvm in detail, how the sausage REALLY is made, behind the shiny facade with clang and swift and apple's marketing and the hype around it, which I found fascinating in unexpected ways, both glorious and terrifying :D
  • yeoman
    yeoman almost 8 years
    The link is broken... -.-
  • Mense
    Mense almost 8 years
    The link is now fixed.
  • yeoman
    yeoman almost 8 years
    Just out of curiosity - is Loïc a Breton name? :)
  • oHo
    oHo almost 8 years
    As far as I think, all the Loïc I know come from Bretagne (or their parents). My colleagues at Paris think also Loïc is a Breton name. But after reading the article Loïc on Wikipedia, I see that Loïc may come from "old Provençal form of Louis" or from "Greek name Loukas"... Next time I will meet Loïc Joly I will ask him if his name is from Bretagne...
  • antron
    antron almost 8 years
    Heya @mrhthepie, sorry that your edit got rejected. I just saw the email about it. I'm going to incorporate it into the answer – thanks for the bugfix!
  • user3240688
    user3240688 almost 8 years
    this is great. Would this also work if I want an enum of bits? Like I want an enum of BitFlags, each one is 1U shifted over by some amount?
  • user3240688
    user3240688 over 7 years
    can you elaborate how you got it to work with bitflags?
  • antron
    antron over 7 years
    I'm not sure what you mean. How to use it for bit flags, or how the internals support such usage? The usage is ENUM(Foo, uint32_t, A = 1 << 0, B = 1 << 1, C = 1 << 2). The internals support this because the macro uses a regular enum internally to declare the constants (see the answer text and code).
  • oHo
    oHo over 7 years
    This is very interesting :-) However your current version implies you have to manually write the stuff case Enum::RED: return "red";. The question is about automatizing this stuff by the compiler (at compilation time). The idea of the question is to only change or add enum values without having to update the stuff toString(). Do you see? Thanks
  • user3240688
    user3240688 over 7 years
    there seems to be a memory leak in _trimmed_names() in the code you posted here (new char[length + 1] but you don't set initialized to true). am I missing something? i don't see the same problem in your github code.
  • antron
    antron over 7 years
    It is set to true, but outside the if branch (memory leak originally caught by @mrhthepie). Should move it inside... Editing. Thanks for the close look at both this and the GH code.
  • user3240688
    user3240688 over 7 years
    I'm getting compilation error if I use the ENUM class as a parameter in a converting constructor for another class, and try to overload the operator<< for that class. E.g. ENUM(Foo, char, A = 1, B ) and then class Bar { public: Bar() {} Bar (Foo v) {} }; inline std::ostream& operator<<( std::ostream& out, const Bar& o ) { return out; }, and then in my main(), I do this Foo f = Foo:A; cout << f << endl; It complains about ambiguous overload of operator<<. Why is it doing that?
  • zhaorufei
    zhaorufei over 7 years
    @user3240688 I just test version 0.11.1 on MSVC 2013 and mingw 4.8.2 both passed your code snippet(a typo? Foo::A).
  • zhaorufei
    zhaorufei over 7 years
    I've tested the 0.11.1 version, and found something to improve: * A naked c++ enum declaration / definition can appear inside class definition and function body, better enum do not allow this(due to namespace?) * An empty enum is allowed in C++ : enum X {}; but better enum will cause long compiler error. * Use of on-demand static variable initialization within a function have potential race condition in multi thread environment.
  • oHo
    oHo over 7 years
    Thanks :-) I have split the final example to avoid the horizontal scroll bar. What a pity the value MyEnum::AAA cannot be passed as second argument of std::meta::get_enumerators_m :-/
  • oHo
    oHo over 7 years
    Very interesting meta-programming way. I have tried to simplify a bit the answer to be autonomous (without dependency on Gist link). In order to be concise and understandable I have finally edited a lot your answer. Do you still agree with my changes? Cheers ;-)
  • oHo
    oHo about 7 years
    To reduce memory usage, you may replace member const std::string text by just theStrings[v]. However the question is about the features from C++11/C++14/C++17/C++20 to avoid having to write such class by hand :-/
  • oHo
    oHo almost 7 years
    Thank you Malem for your innovative idea. I have edited your answer to improve readability. I hope you like my changes. Please continue to improve your answer: (1) extend the section "Usage example" with something like auto name = MyEnumStrings["Red"]; -- (2) Why do you use enum class? -- (3) Do you support enum class MyEnum : char { Red, Green, Blue };? -- (4) Explain function split() -- (5) Do you need parameter const std::regex& delim? -- (6) What about generating MyEnumStrings at compilation time? => Can you use constexpr? ... Cheers :-)
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 6 years
    to_string could return a string_view from C++17, which does not require null termination, and become constexpr.
  • ikku100
    ikku100 over 6 years
    To me it's not clear what the difference is between you ENUM and your BETTER_ENUM library. For one, it seems i cannot compare BETTER_ENUM (so create one, and then compare it to one of it's predefined values fails with some weird compile error). For two, ENUM is only implemented for up to 8 values (and BETTER_ENUM is much more).
  • HolyBlackCat
    HolyBlackCat over 6 years
    There is no need for a separate file. This is essentially an x-macro.
  • eferion
    eferion over 6 years
    @HolyBlackCat if you split the solution in some files you can reuse the enum values for different purposes
  • HolyBlackCat
    HolyBlackCat over 6 years
    I'm trying to you say that you can do the same thing if you put the list of values into a single macro alongside the enum definition in a header.
  • eferion
    eferion over 6 years
    @HolyBlackCat yes i understand you but i prefer this solution. on the other hand this solution can be found in clang source code so i think it is a good way to solve the problem
  • HolyBlackCat
    HolyBlackCat over 6 years
    Fair enough. Shouldn't have downvoted this I guess, since it can indeed have some uses. (Pardon the dummy edit, the system locks my vote otherwise.)
  • eferion
    eferion over 6 years
    @HolyBlackCat don't worry... Thanks for reading the answer and giving your opinion
  • void.pointer
    void.pointer about 6 years
    The fact that such a conceptually simple task requires 3 levels of nested template arguments is very overengineered. I'm sure there's specific, technical reasons for it. But that doesn't mean the end result is user friendly. I love C++ and the code makes sense to me. But 90% of other programmers I work with on a daily basis shun C++ because of code like this. I'm disappointed in not having seen any simpler more built-in solutions.
  • einpoklum
    einpoklum about 6 years
    Can we have line breaks inside this macro definition?
  • einpoklum
    einpoklum about 6 years
    The animated gif is cute, but it's difficult to tell when it starts and ends, and how far in we're at. ... actually, scratch that, it's not cute, it's distracting. I say kill it.
  • einpoklum
    einpoklum about 6 years
    @Paula_plus_plus: Shouldn't you just use an std::array instead of the unwieldy map? It will only become preferable for enums starting at... what, 2^10 values? Perhaps even more.
  • einpoklum
    einpoklum about 6 years
    This fails with g++ 6.3.0 and C++14.
  • einpoklum
    einpoklum about 6 years
    Can you update this answer now that C++17 has been released? Specifically, you refer to certain proposals which may or may not have made it into the final version.
  • PaperBirdMaster
    PaperBirdMaster about 6 years
    @einpoklum that would be amazing if we can ensure at runtime how many elements an enum have. Unfortunatelly, we cannot. And the whole point of the map is just to associate names with values, which is what std::map is good for.
  • einpoklum
    einpoklum about 6 years
    @Paula_plus_plus: You're already calling an initialize() function whose number of arguments is the number of enum values, so you know the number of values at compile-time. It's only the specific value you're asked to print that is known at run-time-only. Also, even if you didn't know that number, an std::vector would be faster than an std::map, again, in almost all realistic cases.
  • PaperBirdMaster
    PaperBirdMaster about 6 years
    @einpoklum that's a very good point indeed, I'll think about it, thanks! The only thing that keeps me worrying is that std::array is not a key-value container and therefore lacks of find methods; anyways I'll give it a thought.
  • einpoklum
    einpoklum about 6 years
    @Paula_plus_plus: std::find is your friend.
  • antron
    antron about 6 years
    @einpoklum I don't have the time to do a good job researching that right now. If you or someone else proposes an edit, I can check it much faster and accept it.
  • Anton Holmberg
    Anton Holmberg about 6 years
    I really like this approach. Really short and easy to understand.
  • Danilo Ramos
    Danilo Ramos about 6 years
    Added line breaks inside this macro definition as requested.
  • einpoklum
    einpoklum about 6 years
    @void.pointer: Not necessarily. In languages like Java, this is essentially a one-liner. But - there's a catch: You need a millions-of-code-lines virtual machine under you. In C++, we don't have that, so we build our abstractions using what the language provides that's more simple, or more general. Those 90% of programmers should just not try to do this themselves, but rather rely on libraries written by others.
  • void.pointer
    void.pointer about 6 years
    @einpoklum it doesn't require a virtual machine. The compiler can build a string mapping at compile time. The strings are the same as the name of the enumerators. Why over think this?
  • Ruslan
    Ruslan about 6 years
    This block-selection approach in vim is nice and all, but why not simply use something like :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
  • Tim Rae
    Tim Rae about 6 years
    It seems the current estimate for inclusion of the upcoming Reflection TS in the standard is C++23: herbsutter.com/2018/04/02/…
  • Peter VARGA
    Peter VARGA almost 6 years
    +1. Love your solution. The most compact version and it works. Even for my purpose I could minimize it because I don't need the std::string overloads.
  • Peter VARGA
    Peter VARGA almost 6 years
    I added the overload for * to get the count of enums... I hope you don't mind :-)
  • Kevin Anderson
    Kevin Anderson almost 6 years
    I found this question a few months later, but a reason NOT to use an array or vector would be if the enum values themselves don't start at zero. Enums can sometimes be used for message types from binary data, so maybe their values sttart at above 2^10, even if there are only a few dozen values in the enum, because they correspond to "real values" in an embedded system or such.
  • Troyseph
    Troyseph almost 6 years
    @einpoklum The compiler already auto generates default constructors and destructors, why not auto-generate a ostream operator<<(ostream, THING) whenever it sees something like std::cout << std::meta(classInstance) or std::meta(enumvalue)
  • zett42
    zett42 over 5 years
    Interesting because the enum can be declared normally without having to wrap it in a macro. Though I don't like the compiler-dependencies and magic constants.
  • River Tam
    River Tam over 5 years
    Is there any reason this implementation uses std::map (O(log(n)) indexing) rather than std::unordered_map (O(1) indexing)?
  • River Tam
    River Tam over 5 years
    also, I think the methods should be marked inline so you can declared enums in header files like normal without getting "multiple definition of" errors from the linker. (not sure if that's actually the cleanest/best solution, though)
  • River Tam
    River Tam over 5 years
    (sorry to spam but I can't seem to edit comments today) there are other issues with this being in a header file. The map (E##MapName) needs to be moved to a compilation unit which has access to the enum as well. I've created a solution, but it's not very clean and I'd have to get permission to share it. For now, I'm just commenting to say there's no point in marking the methods inline without the additional features necessary to support usage in a header file.
  • Lightness Races in Orbit
    Lightness Races in Orbit over 5 years
    I'm not completely sold by the choice of + (it feels wrong alongside a lack of, say, op+(string, int)) but I don't know what else to suggest. Perhaps stick with a ToString free function?
  • haelix
    haelix over 5 years
    This solution deosn't support having constexpr expressions as values for the enums.
  • yeoman
    yeoman almost 5 years
    The abysmal readability of macro magic of this level is why I prefer source code generation so much. The macro böack magic feels so much smarter until people don't get it and start asking you to fix their mysterious compiler errors they get from holding it wrong. Or until people get stuck whle debugging an unrelated error in code using the macro.
  • PlasmaHH
    PlasmaHH almost 5 years
    @yeoman: both have their pros and cons. if you feed such a macro just a list of enums, adding one becomes as simple as adding it to the macro argument list. In case of code creation you would probably have to add it at a special place and run a special tool.
  • yeoman
    yeoman almost 5 years
    @PlasmaHH that is absolutely accurate 🙂
  • yeoman
    yeoman almost 5 years
    Except that the special tool can be easily integrated in a make / cmake &c. build before anything else so it works even in the face of unrelated compiler errors 😊
  • PlasmaHH
    PlasmaHH almost 5 years
    @yeoman: as I said, all pros and cons. I would for example not want a "header only library" to come with a set of tools that I have to run in addition to compilation.
  • gigabytes
    gigabytes over 4 years
    In clang the definitions are in a separate source file because they are autogenerated by the tablegen tool.
  • Emile Cormier
    Emile Cormier about 4 years
    Why the range limits? Is it to limit some kind of recursion depth, or because of some kind of compile-time linear search?
  • Admin
    Admin about 4 years
    This is amazing. Thank you! It's probably even efficient if the compiler is smart enough to evaluate the constexpr std::array one time only. Very very nice.
  • Alastair Harrison
    Alastair Harrison over 3 years
    @EmileCormier The range limits are necessary because the library has to probe every possible value in the range to see if it corresponds to an enumerator. It instantiates an is_valid function template for every value in the range [-128, 127]. This can result in heavy compile times, so the range is quite conservative by default. Here's a simplified version of the technique: godbolt.org/z/GTxfva
  • MateuszL
    MateuszL over 3 years
    for me the most important drawback is that it fails silently: godbolt.org/z/TTMx1v There is restriction on size of values, but when the constrain is not met, there is no compile error, no exception, only empty string returned.
  • acegs
    acegs almost 3 years
    @Neargye will this won't have problem if i have enum with values outside of min/max range but i will only use the enum-to-string feature?
  • Neargye
    Neargye almost 3 years
    @acegs if a value outside of min/max range enum-to-string will return an empty string.
  • Jean-Michaël Celerier
    Jean-Michaël Celerier almost 3 years
    Sadly only works for strings of length <= 8
  • user1095108
    user1095108 almost 3 years
    we will be at 16 chars soon.
  • Some Guy
    Some Guy over 2 years
    This doesn't work for an arbitrary enum value supplied at runtime (e.g. in a variable).
  • Sz.
    Sz. over 2 years
    @void.pointer Thanks for standing up to tell that the emperor has no clothes. :) (As to the "I love C++" statement, I take it as a nice defensive declaration against potential fanboyism (fortunately not prevalent here), but if taken seriously, just for fun, it'd feel like a strange kind of affection, like: "I love electricity". :) It's a given, it's what it is, it's useful, it's everywhere, it's complex, needs serious tools & expertise, it's fast, it can hit you hard etc. etc., but as a target of romantic attractions..., dunno. My love evaporated some 15 years ago.)
  • void.pointer
    void.pointer over 2 years
    @Sz These days I'm enjoying the fantastic C# language, framework, package management, and tooling support of the .NET ecosystem. I couldn't be bothered to go back to C++ again!
  • user1095108
    user1095108 about 2 years
    here is the current version of h8.