枚举使用编译时常量转换为字符串

Posted

技术标签:

【中文标题】枚举使用编译时常量转换为字符串【英文标题】:Enum convert to string using compile time constants 【发布时间】:2019-04-01 05:11:27 【问题描述】:

我正在尝试将编译时间字符串与枚举值相关联。

这是我第一次尝试解决这个问题:

EnumValue 将在字符串和枚举之间进行编译时关联

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 将保存字符串和枚举的实际值。 我不喜欢我当前的设计,因为它仍然需要保存一个指向字符串的指针。我更愿意为此使用编译时关联,但未能提出更优雅的解决方案

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 可以轻松引用枚举类型和枚举值持有者构造

#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() 

以下是我的测试用例和使用示例:

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;
    

其中一个目标是删除全局字符串:

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

另一种是创建一个将枚举与字符串关联的宏

//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)
;

所以有两个主要问题:

1) 这种当前的设计真的能保证将枚举与字符串关联起来的编译时间常数吗?

2) 如何定义一个宏来对枚举值进行字符串化并使用 1 行在枚举类中声明该值?

编辑:这应该可以在使用 c++ 11 的 win64 平台上与 msvs2017 一起工作和编译。

谢谢。

【问题讨论】:

【参考方案1】:

我认为它应该适用于 MSVC2017。它在 constexpr 函数中使用 C++14,但您可以将它们拆分为单个返回语句 constexprs 以与 C++11 兼容(但是 MSVC2017 支持 C++14)。

EnumConverter 为每个枚举条目存储 char*、枚举和字符串哈希值。对于每个枚举,您必须专门化 EnumConverter::StrEnumContainer。可以使用您指定的类似宏生成枚举字符串对。

#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" );

【讨论】:

我不熟悉你在constexprStringHash 中所做的事情。您能否详细说明或链接到您正在使用的哈希算法?同样对于 EnumConverter::StrEnumContainer 的专业化,我看到定义了最大大小,在这种情况下我需要某种 count arg 宏吗? 哈希函数是一个简单的 djb2 (link)。在 StrEnumContainer 中,您必须指定数组大小(枚举条目计数)。当然,您可以使用宏或模板魔术来做到这一点。

以上是关于枚举使用编译时常量转换为字符串的主要内容,如果未能解决你的问题,请参考以下文章

编译时常量和变量

如何在 C++11 中创建结构的编译时常量实例

从文字字符串生成编译时常量整数

优化C#程序的四十七种方法

Initialiser 元素不是编译时常量

java中的编译时常量与运行时常量