Skip to content

Instantly share code, notes, and snippets.

@hutorny
Created January 21, 2017 18:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hutorny/5d8b1caac02710b9549be213d7d436e5 to your computer and use it in GitHub Desktop.
Save hutorny/5d8b1caac02710b9549be213d7d436e5 to your computer and use it in GitHub Desktop.
Zero-dependency allocation-free mapping enum values to names for C++11
/** zero-dependency allocation-free mapping enum values to names */
namespace enumnames {
typedef const char* (*name)();
bool match(const char*, const char*) noexcept; /* to be provided by the user */
template<typename T, T K, name V>
struct tuple {
typedef T key_t;
static constexpr T key = K;
static constexpr name val = V;
};
template<typename ... L>
struct map;
template<typename L>
struct map<L> {
static constexpr typename L::key_t get(const char*) noexcept {
return L::key; /* always returning last element */
}
static constexpr const char* get(typename L::key_t key) noexcept {
return (L::val != nullptr) && (key == L::key) ? L::val() : "";
}
};
template<typename L, typename ... R>
struct map<L,R...> {
static typename L::key_t get(const char* val) noexcept {
static_assert(L::val != nullptr, "Only last element may have null name");
return match(val, L::val()) ? L::key : map<R...>::get(val);
}
static constexpr const char* get(typename L::key_t key) noexcept {
return (key == L::key) ? L::val() : map<R...>::get(key);
}
};
template<typename T, typename ... L>
struct names {
static T get(const char* nam) noexcept {
return M::get(nam);
}
static constexpr const char* get(T key) noexcept {
return M::get(key);
}
private:
typedef map<L...> M;
};
}
/* main.cpp - usage example for enumnames */
#include <iostream>
#include <cstring>
#include "enumnames.hpp"
enum class fasion {
fancy,
classic,
sporty,
etno,
emo,
__last__ = emo,
__unknown__ = -1
};
/* needed for iterations only */
static inline constexpr fasion operator+(fasion a, int b) noexcept {
return static_cast<fasion>(static_cast<int>(a) + b);
}
static inline constexpr int operator+(fasion a) noexcept {
return static_cast<int>(a);
}
#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
NAME(fancy)
NAME(classic)
NAME(sporty)
NAME(etno)
NAME(emo)
}
template<fasion K, enumnames::name V>
struct _ : enumnames::tuple<fasion, K,V> {};
typedef enumnames::names<fasion,
_<fasion::fancy, name::fancy>,
_<fasion::classic, name::classic>,
_<fasion::sporty, name::sporty>,
_<fasion::etno, name::etno>,
_<fasion::emo, name::emo>,
_<fasion::__unknown__, nullptr>
> fasion_names;
using namespace std;
int main() {
std::string str;
cout << "Enter fasion from the list:" << endl;
for(fasion i = fasion::fancy; i <= fasion::__last__; i = i+1)
cout << fasion_names::get(i) << endl;
cin >> str;
fasion selected = fasion_names::get(str.data());
cout << "Selected :" << +selected << " (" << fasion_names::get(selected) << ")";
}
namespace enumnames {
bool match(const char* a, const char* b) noexcept {
return strcasecmp(a,b) == 0;
}
}
@olibre
Copy link

olibre commented Jan 23, 2017

I have changed so much your answer on StackOverflow that I am not sure you will accept the changes:

This gist provides a simple mapping based on C++ variadic templates.

This is a C++17-simplified version of the type-based map from the gist:

#include <cstring> // http://stackoverflow.com/q/24520781
    
template<typename KeyValue, typename ... RestOfKeyValues>
struct map {
  static constexpr typename KeyValue::key_t get(const char* val) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)  // C++17 if constexpr
      return KeyValue::key; // Returns last element
    else {
      static_assert(KeyValue::val != nullptr,
                "Only last element may have null name");
      return strcmp(val, KeyValue::val()) 
                ? map<RestOfKeyValues...>::get(val) : KeyValue::key;
    }
  }
  static constexpr const char* get(typename KeyValue::key_t key) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)
      return (KeyValue::val != nullptr) && (key == KeyValue::key)
                ? KeyValue::val() : "";
    else
      return (key == KeyValue::key) 
                ? KeyValue::val() : map<RestOfKeyValues...>::get(key);
  }
};
    
template<typename Enum, typename ... KeyValues>
class names {
  typedef map<KeyValues...> Map;
public:
  static constexpr Enum get(const char* nam) noexcept {
    return Map::get(nam);
  }
  static constexpr const char* get(Enum key) noexcept {
    return Map::get(key);
  }
};

An example usage:

enum class fasion {
        fancy,
        classic,
        sporty,
        emo,
        __last__ = emo,
        __unknown__ = -1
};
    
#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
        NAME(fancy)
        NAME(classic)
        NAME(sporty)
        NAME(emo)
}
    
template<auto K, const char* (*V)()>  // C++17 template<auto>
struct _ {
        typedef decltype(K) key_t;
        typedef decltype(V) name_t;
        static constexpr key_t  key = K; // enum id value
        static constexpr name_t val = V; // enum id name
};
    
typedef names<fasion,
        _<fasion::fancy, name::fancy>,
        _<fasion::classic, name::classic>,
        _<fasion::sporty, name::sporty>,
        _<fasion::emo, name::emo>,
        _<fasion::__unknown__, nullptr>
> fasion_names;

The map<KeyValues...> can be used in both directions:

  • fasion_names::get(fasion::emo)
  • fasion_names::get("emo")

This example is available on godbolt.org

int main ()
{
    constexpr auto str = fasion_names::get(fasion::emo);
    constexpr auto fsn = fasion_names::get(str);
    return (int) fsn;
}

Result from gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment