EnumValueToString



  • Ich war gerade dabei, etwas dieser Art zu schreiben:

    enum A { // verzichte hier aus übersichtlichkeit auf enum class
    	A_1,
    	A_2,
    	A_3
    };
    
    inline constexpr const char* AtoString (A value)
    {
    	return (const char*[]) {
    		"A_1",
    		"A_2",
    		"A_3"
    	}[value];
    }
    

    Doch dann habe ich erstmal gezögert, weil mir das doch etwas 'dirty' vorkam.

    1. macht das inline einen unterschied?
    2. Wie macht man das besser richtig?

    EDIT: cool wäre ein macro à la:

    STRINGIFYABLE_ENUM 
    (
        A,
        (A_1)
        (A_2)
        (A_2)
    )
    // implicitly declares AtoString
    

  • Mod

    Darf die Lösung Boost.PP enthalten? 🤡



  • Arcoth schrieb:

    Darf die Lösung Boost.PP enthalten? 🤡

    gerne

    [Sidenote: Ich habe versucht den Preprozessor besser zu verstehen, in dem ich einen selbst schreibe. Der konnte zwar boost/preprocessor/config, boost/preprocessor/cat, boost/preprocessor/stringize schlucken und korrekt verarbeiten, aber tuple und repetition hatte ich nicht zum laufen gekriegt. -.- (bevor mir die Lust verging)]



  • Ich persönlich würde ja einfach ein switch statement nehmen. Das ist übersichtlicher, da man klarer sieht, welche Ausgabe zu welcher Eingabe gehört. Zudem ist es sicherer, da der Kompiler bessere Diagnose machen kann und wahrscheinlich ist es mit Optimierung genau so effizient.



  • Bei sowas griffe ich dann zum X-Makro. 🙂

    Da kriege ich keinen Knoten in den Kopp 🙂

    #define A_PAIRS\
      X(A_1, "A1"),				\
        X(A_2, "A2"),				\
        X(A_3, "A3")
    
    #define AS_ENUM(E,S) E
    #define AS_STRING(E,S) S
    
    #define X AS_ENUM
    enum A{
      A_PAIRS, A_MAX
    };
    #undef X
    
    #define X AS_STRING
    constexpr const char* strings[] = { A_PAIRS };
    inline constexpr const char* AtoString(A value){
      return strings[value];
    }
    #undef X
    

    Wenn die enums und die strings wirklich gleich sind - so wie bei Dir - hat man es sogar noch leichter (mit stringifying(?!) vom Präprozessor).



  • Arcoth schrieb:

    Darf die Lösung Boost.PP enthalten? 🤡

    Falls das Ergebnis gut zu benutzen ist - bitte!



  • Ich habe erstmal auf "Furble Wurble"s Code gewechselt (mit stringizing operator), da dieser 100% sicher ist vor Inkonsistenzen, die enstehen könnten, wenn man die enum erweitert (und vergisste die toString funktion zu ändern).

    Aber vielleicht gefällt mir ja etwas zukünftiges noch ein bisschen besser.



  • Ich hab den folgenden Trick mal irgendwo hier im Forum gelernt, ich glaube von hustbaer.

    Zunaechst kommt die Struktur des Enums in ein Header File, ohne Include-Guards oder #pragma once. Wir druecken die Struktur durch Makros aus, d.h. diese Makros muessen definiert werden, bevor der Header included werden kann.

    ENUM_BEGIN(Foo)
    ENUM_VALUE(X)
    ENUM_VALUE(Y)
    ENUM_VALUE(Z)
    ENUM_END()
    

    Dazu einen passenden #undef Header, ebenfalls keine Include-Guards.

    #undef ENUM_BEGIN
    #undef ENUM_VALUE
    #undef ENUM_END
    

    Jetzt koennen wir uns die drei Makros so definieren wie wir sie brauchen und danach den Header includen, um beliebigen Code zu generieren. (Hier wiederum benoetigen wir die Include-Guards!)

    // erstmal die definition
    #define ENUM_BEGIN(name) enum struct name : size_t {
    #define ENUM_VALUE(name) name,
    #define ENUM_END() };
    
    #include "enum_structure.hpp"
    #include "enum_undef.hpp"
    
    // danach ein array mit den strings
    #define ENUM_BEGIN(name) constexpr char const* const name##_strings[] = {
    #define ENUM_VALUE(name) #name,
    #define ENUM_END() };
    
    #include "enum_structure.hpp"
    #include "enum_undef.hpp"
    
    // und jetzt noch die eigentliche Funktion
    inline constexpr char const* toString(Foo x)
    {
    	return Foo_strings[static_cast<size_t>(x)];
    }
    

    Das klappt so natuerlich nur, wenn die Enum-Werte auch linear sind und ohne luecken. Wenn das irgendwelche Werte sind, dann musst du wiederum auf die drei Makros zurueckgreifen und bei jedem Wert ein if(x == value) return #value; generieren.

    Der Vorteil ist, dass du ganz einfach eine neue Funktion generieren kannst, wenn du sie brauchst. Und nicht nur du, auch Benutzer deines APIs koennen das ganz einfach.


  • Mod

    Der erste Ansatz sieht so aus:

    #include <boost/preprocessor/variadic/to_seq.hpp>
    #include <boost/preprocessor/seq/transform.hpp>
    #include <boost/preprocessor/seq/enum.hpp>
    #include <boost/preprocessor/stringize.hpp>
    
    template <char... ch>
    struct const_string {static constexpr char array[] {ch..., '\0'};};
    template <char... ch>
    constexpr char const_string<ch...>::array[];
    
    template <char c1, char c2, typename, typename=const_string<>>
    struct rtrim;
    
    template <char c1, char c2, char f, char... prev, char... next>
    struct rtrim<c1, c2, const_string<f, next...>, const_string<prev...>> :
        rtrim<c1, c2, const_string<next...>, const_string<prev..., f>> {};
    
    template <char c1, char c2, char... prev, char... next>
    struct rtrim<c1, c2, const_string<c1, next...>, const_string<prev...>> :
    	const_string<prev...> {};
    template <char c1, char c2, char... prev, char... next>
    struct rtrim<c1, c2, const_string<c2, next...>, const_string<prev...>> :
    	const_string<prev...> {};
    template <char c1, char c2, char... prev, char... next>
    struct rtrim<c1, c2, const_string<'\0', next...>, const_string<prev...>> :
    	const_string<prev...> {};
    template <char c1, char c2, char... prev>
    struct rtrim<c1, c2, const_string<>, const_string<prev...>> :
    	const_string<prev...> {};
    
    #define SPLIT_1(s, x) ( x < sizeof(s) ? s[x] : '\n' )
    #define SPLIT_4(s, x) SPLIT_1  (s, x), SPLIT_1  (s, x+1)  , SPLIT_1  (s, x+2)  , SPLIT_1  (s, x+3)
    #define SPLIT_16(s, x) SPLIT_4  (s, x), SPLIT_4  (s, x+4)  , SPLIT_4  (s, x+8)  , SPLIT_4  (s, x+12)
    #define SPLIT_64(s, x) SPLIT_16 (s, x), SPLIT_16 (s, x+16) , SPLIT_16 (s, x+32) , SPLIT_16 (s, x+48)
    
    #define STRINGIZE_ENUM(s, data, elem) (rtrim<'=', ' ', const_string<SPLIT_64(BOOST_PP_STRINGIZE(elem), 0)>>::array)
    
    #define STRING_ENUM(name, string_array_name, ...)   \
                                                        \
    	enum name { __VA_ARGS__ };                      \
                                                        \
    	constexpr char const* string_array_name[]          \
    	{                                                                                                        \
    		BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE_ENUM, 0, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)))  \
    	};
    
    #include <cstddef> // std::size_t
    
    STRING_ENUM(MyEnum : std::size_t, MyEnumStrings,
    	Enumerator1 = 7,
    	Enumerator2)
    
    #include <iostream>
    
    int main()
    {
    	for (auto str : MyEnumStrings)
    		std::cout << str << '\n';
    }
    

    Demo. Einen zweiten gibt's auch, der kommt gleich.



  • Sowas will doch kein Mensch lesen.



  • Kellerautomat schrieb:

    Sowas will doch kein Mensch lesen.

    Muss ja auch keiner.
    Man packt alles bis Zeile 46 in einen Header und gut ist.
    rtrim kann man bestimmt auch durch constexpr Funktionen ersetzen, dann spart man sich das const_string auch. Und SPLIT ist dann auch überflüssig.



  • Ja, der mehrfach-include Trick war vermutlich von mir - zumindest hab' ich das mal irgendwo erwähnt. Wir verwenden das u.A. um konfigurationsabhängige Filenamen für unsere #pragma comment(lib, "filename.ext") Direktiven zusammenzubauen.

    Nathan schrieb:

    Kellerautomat schrieb:

    Sowas will doch kein Mensch lesen.

    Muss ja auch keiner.
    Man packt alles bis Zeile 46 in einen Header und gut ist.

    Arr, zu langsam. Wollte ich auch grad schreiben 🙂



  • Und dann kann ich nur toString-en, buuuh.



  • Kellerautomat schrieb:

    Und dann kann ich nur toString-en, buuuh.

    Naja, find ich angenehmer als extra eine zusätzliche Datei zu erstellen.



  • Naja, man kann das Makro STRING_ENUM ja um weitere Teile - inklusive mehrfache Aufrufe von BOOST_PP_SEQ_ENUM - erweitern.



  • BTW: Ich mach das mit enums in meinen Projekten sehr ... pragmatisch 😃

    Einfach

    enum Foo
    {
       FooA,
       FooB,
       FooC,
    };
    
    ...
    
    #define FOO_TO_STRING_CASE_(code) \
    	case code: \
    		return BOOST_JOIN(L, #code) \
    	// end FOO_TO_STRING_CASE_
    
    std::wstring FooToString(Foo f)
    {
        switch (f)
        {
        FOO_TO_STRING_CASE_(FooA);
        FOO_TO_STRING_CASE_(FooB);
        FOO_TO_STRING_CASE_(FooC);
        default:
            assert(0 && "invalid Foo enum value");
            return L"";
        }
    }
    
    #undef FOO_TO_STRING_CASE_
    

    Klar, ich hab damit die Namen an zwei Stellen stehen.
    Dafür ist es lesbar und Intellisense- und refactoring-freundlich. (Die Bezeichner in den FOO_TO_STRING_CASE_ Aufrufen werden zwar meist nicht mit umbenannt, aber das führt dann ja zu nem Compiler-Fehler => kein Problem.)
    Und es ist immerhin sichergestellt dass das Ergebnis von FooToString() dem Bezeichner entspricht. => Gut genug für mich.

    EDIT: * Renamed FOO_TO_STRING_CASE__ => FOO_TO_STRING_CASE_ to avoid using a reserved identifier.



  • hustbaer schrieb:

    FOO_TO_STRING_CASE__

    Dieser Bezeichner ist reserviert. Ich würde die hinteren beiden Unterstriche weglassen.



  • Ja, hast Recht. Hab's gefixt.
    Ich hatte fälschlicherweise im Kopf dass nur doppelte Underscores am Anfang verboten sind... k.A. warum.


  • Mod

    #include <algorithm>
    #include <stdexcept>
    #include <type_traits>
    
    #include <cstring>
    
    namespace EnumDefaults
    {
    namespace detail
    {
    	struct enum_info
    	{
    		bool increasing;
    		bool continuous;
    	};
    
    	template <typename T>
    	constexpr enum_info information( T const* ptr, std::size_t len, bool increasing = true, bool continuous = true )
    	{
    		return len == 1? enum_info{increasing, continuous} : information(ptr+1, len-1, increasing && ptr[0] < ptr[1], continuous && ptr[0] + 1 == ptr[1] );
    	}
    
    	template <typename T> struct identity {using type = T;};
    }
    
    	template <char const* const* first, std::size_t len, typename underlying_type, underlying_type const* values>
    	std::pair<underlying_type, bool> fromString( char const* str )
    	{
    		auto i = std::find_if(first, first + len, [str] (char const* arg) {return std::strcmp(arg, str) == 0;});
    
    		if (i == first + len)
    			return {values[0], false};
    
    		return {values[i - first], true};
    	}
    }
    
    #include <boost/preprocessor/cat.hpp>
    #include <boost/preprocessor/stringize.hpp>
    
    #include <boost/preprocessor/arithmetic/inc.hpp>
    #include <boost/preprocessor/repetition/enum.hpp>
    
    #include <boost/preprocessor/seq/enum.hpp>
    #include <boost/preprocessor/seq/fold_left.hpp>
    #include <boost/preprocessor/seq/size.hpp>
    #include <boost/preprocessor/seq/subseq.hpp>
    #include <boost/preprocessor/seq/transform.hpp>
    #include <boost/preprocessor/tuple/elem.hpp>
    #include <boost/preprocessor/tuple/size.hpp>
    #include <boost/preprocessor/tuple/to_seq.hpp>
    #include <boost/preprocessor/variadic/to_seq.hpp>
    
    #define STRINGIZE_ENUM(s, data, tuple) BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(0, tuple))
    
    #define EXTRACT_VALUE_1(s, data, tuple) data+1
    #define EXTRACT_VALUE_2(s, data, tuple) BOOST_PP_TUPLE_ELEM(1, tuple)
    #define EXTRACT_VALUE(s, data, tuple) BOOST_PP_CAT(EXTRACT_VALUE_, BOOST_PP_TUPLE_SIZE(tuple))(s, data, tuple)
    
    #define EXPAND_ENUMERATOR_1(s, data, tuple) BOOST_PP_TUPLE_ELEM(0, tuple)
    #define EXPAND_ENUMERATOR_2(s, data, tuple) BOOST_PP_TUPLE_ELEM(0, tuple) = BOOST_PP_TUPLE_ELEM(1, tuple)
    #define EXPAND_ENUMERATOR(s, data, tuple) BOOST_PP_CAT(EXPAND_ENUMERATOR_, BOOST_PP_TUPLE_SIZE(tuple))(s, data, tuple)
    
    #define INDEX_MAP(s, N, data) BOOST_PP_SEQ_FOLD_LEFT(EXTRACT_VALUE, BOOST_PP_TUPLE_ELEM(0, data), BOOST_PP_SEQ_SUBSEQ(BOOST_PP_TUPLE_ELEM(1, data), 0, BOOST_PP_INC(N)))
    
    #define TO_STRING_CASE(s, N, data) case BOOST_PP_TUPLE_ELEM(1,data)::BOOST_PP_TUPLE_ELEM(0,BOOST_PP_SEQ_ELEM(N, BOOST_PP_TUPLE_ELEM(0,data))): return strings[N];
    
    #define STRING_ENUM_IMPL(name, namespace_name, seq)                                                                                     \
                                                                                                                                            \
          typedef enum name { BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(EXPAND_ENUMERATOR, 0, seq)) } Deduced##namespace_name##_enum_type;   \
                                                                                                                                            \
          namespace namespace_name                                                                                                          \
          {                                                                                                                                 \
                using enum_type = Deduced##namespace_name##_enum_type;                                                                      \
                constexpr std::size_t enum_size = BOOST_PP_SEQ_SIZE(seq);                                                                   \
                using underlying_type = std::underlying_type<enum_type>::type;                                                              \
                                                                                                                                            \
                constexpr char const* strings[]                                                                                             \
                {                                                                                                                           \
                      BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE_ENUM, 0, seq))                                                     \
                };                                                                                                                          \
                                                                                                                                            \
                constexpr underlying_type values[]                                                                                          \
                {                                                                                                                           \
                      BOOST_PP_ENUM(BOOST_PP_SEQ_SIZE(seq), INDEX_MAP, ((underlying_type) -1, seq))                                         \
                };                                                                                                                          \
                                                                                                                                            \
                constexpr auto info = EnumDefaults::detail::information(values, enum_size);                                                 \
                                                                                                                                            \
                std::pair<enum_type, bool> fromString( char const* str )                                                                    \
                {                                                                                                                           \
                      auto pair = EnumDefaults::fromString<strings, enum_size, underlying_type, values>(str);                               \
                      return {static_cast<enum_type>(pair.first), pair.second};                                                             \
                }                                                                                                                           \
                                                                                                                                            \
                char const* toStringSwitch( enum_type n )                                                                                   \
                {                                                                                                                           \
                      switch (n)                                                                                                            \
                      {                                                                                                                     \
                            BOOST_PP_REPEAT(BOOST_PP_SEQ_SIZE(seq), TO_STRING_CASE, (seq, enum_type))                                       \
                      }                                                                                                                     \
                      return nullptr;                                                                                                       \
                }                                                                                                                           \
                                                                                                                                            \
                char const* toString( enum_type n )                                                                                         \
                {                                                                                                                           \
                      if (info.continuous)                                                                                                  \
                      {                                                                                                                     \
                            auto val = static_cast<underlying_type>(n);                                                                     \
                            if (val - values[0] >= enum_size)                                                                               \
                                  return nullptr;                                                                                           \
                            return strings[val - values[0]];                                                                                \
                      }                                                                                                                     \
                                                                                                                                            \
                      return toStringSwitch(n);                                                                                             \
                }                                                                                                                           \
          }
    
    #define STRING_ENUM(a, b, ...) STRING_ENUM_IMPL(a, b, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
    
    STRING_ENUM
    (
    	class MyEnum : unsigned long long, MyEnumInfo,
    
    	(Enumerator1, 7),
    	(Enumerator2, 89),
    	(Enumerator3)
    )
    
    #include <iostream>
    
    int main()
    {
    	std::cout << MyEnumInfo::toString( MyEnumInfo::fromString("Enumerator1").first ) << '\n';
    	std::cout << MyEnumInfo::toString( MyEnumInfo::fromString("Enumerator2").first );
    }
    

    Demo. Hoffentlich war das den Schlaf wert...

    Edit: Vor der Nacht noch die Exceptions entfernt. Die machen hier nicht wirklich sinn.
    Edit²: Scoped-Enums fix, siehe unten



  • Ihr seid Klasse.
    Und @Arcoth: nice...


Log in to reply