2

I'm trying to associate compile time strings to enum values.

Here is my first attempt at the problem:

EnumValue will do the compile time assocation between a string and an enum

template<typename EnumType, int EnumIntValue, const char* EnumStrValue>
class EnumValue
{
public:
    static const char* toString()
    {
        return EnumStrValue;
    }

    static const int toInt()
    {
        return EnumIntValue;
    }

    static EnumType get()
    {
        return static_cast<EnumType>(EnumIntValue);
    }
};

EnumValueHolder will hold the actual values for both string and enum. I dislike my current design as it still needs to hold a pointer to string. I would prefer a compile time association for this but fail to come up with a more elegant solution

template<typename EnumType>
class EnumValueHolder
{
public:
    EnumValueHolder()
    {}

    EnumValueHolder(const EnumType& value, const char* str)
        : value(value), str(str)
    {}

    bool operator==(const EnumValueHolder<EnumType>& rhs) { return value == rhs.value; }
    bool operator==(const EnumType& rhs)const { return value == rhs; }

    operator EnumType()const
    {
        return value;
    }

    const char* toString()const
    {
        return str;
    }

    const int toInt()const
    {
        return static_cast<int>(value);
    }

private:
    EnumType value;
    char const* str;
};

Marcos to easily refer to enum types and enum value holder construction

#define ENUM_VALUE_TYPE(enumName, enumValue) \
EnumValue<enumName, (int)enumName::enumValue, str_##enumValue>


#define ENUM_VALUE_MAKE(enumName, enumValue) \
EnumValueHolder<enumName> { \
    ENUM_VALUE_TYPE(enumName, enumValue)::get(), \
    ENUM_VALUE_TYPE(enumName, enumValue)::toString() }

The following are my test cases and usage examples:

const char str_Apple[] = "Apple";
const char str_Orange[] = "Orange";
const char str_Pineapple[] = "Pineapple";


enum class EFruits
{
    Apple,
    Orange,
    Pineapple
};


int main()
{
    auto evApple = ENUM_VALUE_MAKE(EFruits, Apple);
    std::cout << evApple.toString() << std::endl;

    auto evOrange = ENUM_VALUE_MAKE(EFruits, Orange);
    std::cout << evOrange.toString() << std::endl;

    std::cout << "compare: " << (evApple == evOrange) << std::endl;

    evApple = evOrange;
    std::cout << evApple.toString() << std::endl;

    auto myfruit = ENUM_VALUE_MAKE(EFruits, Pineapple);
    std::cout << myfruit.toString() << std::endl;

    switch (myfruit)
    {
    case EFruits::Apple:
        std::cout << "Im an apple!" << std::endl;
        break;

    case EFruits::Orange:
        std::cout << "Im an Orange!" << std::endl;
        break;

    case EFruits::Pineapple:
        std::cout << "Im a Pineapple!" << std::endl;
        break;

    default:break;
    }
}

One of the objectives is to remove the global string:

const char str_Apple[] = "Apple";
const char str_Orange[] = "Orange";
const char str_Pineapple[] = "Pineapple";

The other is to create a macro that assoicates an enum with a string

//Some crazy define that makes pairs of enum values and strings as
//compile time constants
#define DEFINE_ENUM_STRING(enumValue)\
enumValue, #enumValue

//Ideally, the macro would be used like this. This should be usable in any
//scope (global, namespace, class) 
//with any access specifier (private, protected, public)
enum class EFruits
{
    DEFINE_ENUM_STRING(Apple),
    DEFINE_ENUM_STRING(Orange),
    DEFINE_ENUM_STRING(Pineapple)
};

So there are 2 main questions:

1) Will this current design actually guarantee compile time constants for associating the enum to the string?

2) How can I define a macro to stringify an enum value and declare the value in a enum class using 1 line?

Edit: This should work and compile with msvs2017 on win64 platform using c++ 11.

Thanks.

1 Answer 1

1

I think it should work with MSVC2017. It uses C++14 in the constexpr functions but you can split them to single return statement constexprs to be C++11 compatible (however MSVC2017 supports C++14).

EnumConverter stores the char*, the enum and a string hash value for each enum entry. For each enum you must specialize EnumConverter::StrEnumContainer. The enum-string pairs could be generated with a similar macro you specified.

#include <tuple>
#include <array>
#include <stdexcept>

using namespace std;

enum ELogLevel {
    Info,
    Warn,
    Debug,
    Error,
    Critical
};

static constexpr size_t constexprStringHash( char const* const str ) noexcept
{
    return (
        ( *str != 0 ) ?
            ( static_cast< size_t >( *str ) + 33 * constexprStringHash( str + 1 ) ) :
            5381
    );
}

class EnumConverter final
{
public:

    EnumConverter() = delete;
    EnumConverter( const EnumConverter& ) = delete;
    EnumConverter( EnumConverter&& ) = delete;
    EnumConverter& operator =( const EnumConverter& ) = delete;
    EnumConverter& operator =( EnumConverter&& ) = delete;

    template< typename ENUM_T >
    static constexpr const char* toStr( const ENUM_T value )
    {
        const auto& strEnumArray{ StrEnumContainer< ENUM_T >::StrEnumPairs };
        const char* result{ nullptr };
        for( size_t index{ 0 }; index < strEnumArray.size(); ++index ) {
            if( std::get< 1 >( strEnumArray[ index ] ) == value ) {
                result = std::get< 0 >( strEnumArray[ index ] );
                break;
            }
        }
        return ( ( result == nullptr ) ? throw std::logic_error{ "Enum toStrBase conversion failed" } : result );
    }

    template< typename ENUM_T >
    static constexpr ENUM_T fromStr( const char* const str )
    {
        const auto& strEnumArray{ StrEnumContainer< ENUM_T >::StrEnumPairs };
        const size_t hash{ constexprStringHash( str ) };
        const ENUM_T* result{ nullptr };
        for( size_t index{ 0 }; index < strEnumArray.size(); ++index ) {
            if( std::get< 2 >( strEnumArray[ index ] ) == hash ) {
                result = &( std::get< 1 >( strEnumArray[ index ] ) );
            }
        }
        return ( ( result == nullptr ) ? throw std::logic_error{ "Enum toStrBase conversion failed" } : *result );
    }

private:
    template< typename ENUM_T, size_t LEN >
    using ARRAY_T = std::array< std::tuple< const char* const, const ENUM_T, const size_t >, LEN >;

    template< typename ENUM_T >
    static constexpr std::tuple< const char* const, ENUM_T, size_t > getTuple( const char* const str, const ENUM_T type ) noexcept
    {
        return std::tuple< const char* const, ENUM_T, size_t >{ str, type, constexprStringHash( str ) };
    }

    template< typename ENUM_T >
    struct StrEnumContainer
    {
    };

    template< typename ENUM_T >
    friend struct StrEnumContainer;
};

template<>
struct EnumConverter::StrEnumContainer< ELogLevel >
{
    using ENUM_T = ELogLevel;

    static constexpr EnumConverter::ARRAY_T< ENUM_T, 5 > StrEnumPairs{ {
        { getTuple( "Info", ENUM_T::Info ) },
        { getTuple( "Warn", ENUM_T::Warn ) },
        { getTuple( "Debug", ENUM_T::Debug ) },
        { getTuple( "Error", ENUM_T::Error ) },
        { getTuple( "Critical", ENUM_T::Critical ) },
    } };
};

int main()
{
    //static_assert( EnumConverter::fromStr< ELogLevel >( "Info" ) == EnumConverter::fromStr< ELogLevel >( EnumConverter::toStr( Error ) ), "Error" ); // Error
    static_assert(
        EnumConverter::toStr( Warn )[ 0 ] == 'W' &&
        EnumConverter::toStr( Warn )[ 1 ] == 'a' &&
        EnumConverter::toStr( Warn )[ 2 ] == 'r' &&
        EnumConverter::toStr( Warn )[ 3 ] == 'n',
        "Error"
    );
    static_assert( EnumConverter::fromStr< ELogLevel >( "Info" ) == EnumConverter::fromStr< ELogLevel >( EnumConverter::toStr( Info ) ), "Error" );
}
Sign up to request clarification or add additional context in comments.

2 Comments

I'm not familiar with what you did in constexprStringHash. Could you elaborate or link to what hash algo you are using? Also for the specialization of EnumConverter::StrEnumContainer i see that the max size is defined, I would need some kind of count arg macro in that case?
The hash function is a simple djb2 (link). In StrEnumContainer you must specify the array size (enum entry count). You can do it with macro or template magic of course.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.