ToplingDB 的 Enum Reflection

1 Overview

In short, reflection in programming languages ​​refers to obtaining information such as the type of the language itself from the runtime. C++ lacks such a mechanism, and for the simplest enum types, we might be able to implement an enum with reflection. We have implemented several macros, and the enum defined by the macro automatically has the reflection function.

2. Usage

2.2 Macro Definition

// 可在任意 namespace 中调用,不可在 struct/class 内调用
#define ROCKSDB_ENUM_PLAIN(EnumType, IntRep, ...) details...
#define ROCKSDB_ENUM_CLASS(EnumType, IntRep, ...) details...

// 可在 struct/class 内调用,不可在任意 namespace 中调用
#define ROCKSDB_ENUM_PLAIN_INCLASS(EnumType, IntRep, ...) details...
#define ROCKSDB_ENUM_CLASS_INCLASS(EnumType, IntRep, ...) details...

3. Supported functions

3.1 Functions

The supported functions are defined in the global namespace:

template<class Enum> Slice enum_name(Enum v);

template<class Enum> bool enum_value(const Slice& name, Enum* result);

/// for convenient
template<class Enum> Enum enum_value(const Slice& name, Enum Default);

// use case:
// enum_for_each([](Slice name, Enum val){...});
template<class Enum> void enum_for_each(Func fn);

template<class Enum> std::string enum_str_all_names();

3.2 For example:

#include <rocksdb/enum_reflection.hpp>
// 在 namespace 中调用该宏,不能在 class/struct 内调用
ROCKSDB_ENUM_CLASS(MyEnum, char,
  Value1,
  Value2 = (SomeTemplate<1,2>::value),
  Value3 = 30 // 限制:这里不能有逗号
)

The macro expansion above produces the following code:

// 宏展开的 enum 定义
enum class MyEnum : int {
  Value1, Value2 = (SomeTemplate<1,2>::value), Value3 = 30
};
// 宏展开的反射功能:
int enum_rep_type(MyEnum*);
inline Slice enum_str_define(MyEnum*) {
return "enum class MyEnum : int"
  " { Value1, Value2 = (SomeTemplate<1,2>::value), Value3 = 30 }";
}
inline std::pair<const Slice*, size_t>
enum_all_names(MyEnum*) {
  static const Slice s_names[] = {
    var_symbol("Value1"),
    var_symbol("Value2 = (SomeTemplate<1,2>::value)"),
    var_symbol("Value3 = 30")
  };
  return std::make_pair(s_names, sizeof(s_names)/s_names[0]);
}
inline const MyEnum* enum_all_values(MyEnum*) {
  static const MyEnum s_values[] = {
    EnumValueInit() - MyEnum::Value1,
    EnumValueInit() - MyEnum::Value2 = (SomeTemplate<1,2>::value),
    EnumValueInit() - MyEnum::Value3 = 30
  };
  return s_values;
}

4. Application scenarios

The most typical application scenario is to process configuration information, convert the string configured by the user into an Enum value, and convert the Enum into a string when writing Log. For example, there are a lot of such scenarios in RocksDB.

At present, the enum reflection has been submitted to RocksDB as a Pull Request to improve a large number of manually implemented enum reflections (samples) in RocksDB . 

5. Implementation Details

For emphasis, only a few key points in the implementation are described.

5.1 s_name 与 s_value

s_name and is a parallel array, an enum of , whose value is . These two parallel arrays can be used to implement almost all reflection functions, and they are defined in s and . The key point is how the AND is generated by macro expansion . s_value name  s_name[i]  s_value[i] enum_all_name enum_all_values  s_name  s_value

5.2 宏 ROCKSDB_PP_MAP(map,ctx,...)

Traverse the variable parameter list of the macro and generate a result list. The implementation of this macro contains a little tricks and tricks, but the maximum length of the variable parameter list is limited to 61 (Visual C++ supports up to 127 macro parameters, and gcc supports almost unlimited macros parameter).

5.3 EnumValue = SomeValue is a whole

EnumName = SomeValue Such a grammatical structure, when used as a macro parameter, is a whole, which can be turned into a string , and other operations cannot be performed on it (the disassembly we expect). "EnumName = SomeValue" 

5.4 Initialization of s_name

As the name of the enum, in , we only need EnumName, this is easier to deal with, we have implemented a var_symbol function, from which EnumName can be segmented. In the initialization list of s_name, we use and call the var_symbol function one by one to generate EnumName. Therefore, compared to the initialization of s_value, the initialization of s_name is relatively simple. EnumName = SomeValue  ROCKSDB_PP_MAP

5.5 Initialization of s_value

The initialization of s_value also needs to be processed , because to get the value of EnumName instead of its string form, we have to deal with the entire syntax structure, which is optional, so we should only keep it and delete it. This requirement It cannot be done in the preprocessor. EnumName = SomeValue  EnumName = SomeValue  = SomeValue  EnumName  = SomeValue 

We can only find a way to use the C++ syntax to achieve the deleted function, which can be achieved by using operator overloading: = SomeValue 

template<class Enum>
class EnumValueInit {
  Enum val;
public:
  operator Enum() const { return val; }
  EnumValueInit& operator-(Enum v) { val = v; return *this; }
  template<class IntRep> /// absorb the IntRep param
  EnumValueInit& operator=(IntRep) { return *this; }
};

Thus, with EnumValueInit, we can define an expression that accepts an EnumName or , the resulting value is always an EnumName. This expression is: EnumName = SomeValue

EnumValueInit() - EnumName = SomeValue

Here, EnumValueInit() constructs an object, then applies the - operator on the object, saves the value corresponding to EnumName in the val member, and then calls the = operator. The = operator does nothing, which is equivalent to Removed the following = SomeValue part.

Finally, since the element type of s_value is Enum, the call returns the stored val. This expression is equivalent to just adding something to the front. The implementation can directly use the predefined macro as the map function, and its ctx is the prefix of prepend, that is, the aforementioned (note the latter ). operator Enum  EnumName = SomeValue  ROCKSDB_PP_PREPEND  ROCKSDB_PP_MAP  EnumValueInit() -  - 

5.6 Preprocessing & C++:

Macro expansion only provides the most basic reflection information. Use templates to implement some wrapper functions to wrap the reflection information of macro expansion.

Use inline functions to wrap s_names and s_values ​​for two reasons:

Overloads are provided for different Enum types. Guaranteed initialization order: The initialization order of global objects in different translation units is indeterminate. If, as in v2, the initialization order of s_name and s_value is not determined from the initialization order of global objects in other translation units, if in other translation units A global object (indirectly) calls Enum Reflection, which may result in access to uninitialized s_name and s_value. In addition, the use of C++'s parameter dependent name lookup feature allows enums to be defined in any namespace, even within class/struct.

enum_rep_type is used to deduce the RepType, currently only used to generate the format string for printf.

When the enum is defined within the class/struct, the macro expansion becomes necessary, otherwise the related function will become the member function of the class/struct surrounding the enum. inline  friend

6. Precautions

As in the sample code, the parentheses are necessary because the preprocessor does not know the parentheses of the template , and not adding parentheses will cause a macro expansion error. This is a basic principle when mixing macros and templates. Value2 = (SomeTemplate<1,2>::value)  <> 

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324074381&siteId=291194637