FC_REFLECT reflection Macro Tutorial

FC_REFLECT reflection Macro Tutorial

1. Background

1.1 yuan and target languages

Target language: a description of the final mission of the programming language.

Meta-language: because the target language itself is a computer program, meta-language to describe all or part of the target language grammatical rules of the language.

In addition to the compiler engaged in development work, meta-language and the target language needs to be separated, in most cases, the use of meta-language and target language are the same programming language, which is easily confused on the concept that this meta-language piece of content difficult to understand.

In c ++, the templates and macros in fact belong to the meta-language.

1.2 Calculation quadrant. Compiler task

Most include a c ++ programming language, including, in the course of compiling and running, there are four quadrants computing

First quadrant: performing calculation of

Second quadrant: compile Calculation

Third quadrant: numerical isomers

Fourth quadrant: type inference computing

Calculation takes place on the first quadrant easier to understand, this is because a developer to write code for each of the ultimate goal.

Calculation occurs in the second quadrant, including a number of numerical optimization, such as the definition int a = 2 + 3; then the translation of the first quadrant, the result is out int a = 5; wherein 2 + 3 is during compilation results calculated directly, it is often said that the constant folding.

The third and fourth quadrant calculation is usually more abstract, and deduction templates are related, there are certain difficulties in understanding.

Calculation occurs in the third quadrant, heterogeneous computing is called, it can be calculated using the storage containers of different types of objects, for example in c ++ tuple. Moreover function used is a heterogeneous function, which is a complicated way of template function discussion. A main boost :: fusion, as shown in the following example of this type of calculation:

auto to_string=[](auto t){
    std::stringstream ss;
    ss<<t;
    return ss.str();
};

fusion::vector<int,std::string,float> seq{1,"abc",3.4f};
fusion::vector<std::string,std::string,std::string> strings=fusion::transform(seq,to_string);

static_assert(strings==funsion::make_vector("1"s,"abc"s,"3.4"s));

Calculating occurred on the fourth quadrant, a type of computing, using the type of container, the type of function (also called membered nitrogen-containing brushes) and type of algorithm. A container of the type or types membered function receives as a parameter, the results are returned type. This type of calculation for the main boost :: mpl, the following examples:

template<typename T>
struct add_const_pointer{
    using type=T const*;
};

using types=mpl::vector<int,char,float,void>;
using pointers=mpl::transform<types,add_const_pointer<mpl::_1>>::type;

static_assert(mpl::equal<pointers,
    mpl::vecotr<int const*,char const*,float const*,void const*>>::value,"");

If you insist there is a fifth quadrant, then in c ++, you can also count the macro expansion.

In performing this process the compiler, the compiler process is the order from high to low quadrant quadrant, i.e. macro expansion, type inference, heterogeneous numerical, numerical compile and constant folding, execution of the target program.

1.3.λ trends and calculus compiler

As a long time, the third and fourth quadrants of the calculation, has been difficult to understand the mysterious and abnormal, so he was dedicated to peel out from compiler theory, became an independent subject of study, namely λ calculus.

Starting from c ++ 11, c ++ language to expand out beyond the basic library, and began to support the introduction of a large number of λ calculus in the syntax level. Nevertheless, λ calculus for c ++, is still weak, so we had to write their own code to implement a lot of the underlying.

Currently, the semantics used in λ calculus There are three common: the axiomatic semantics, semantics and the alleged manipulation language. And then in c ++, using operational semantics.

Semantic Name Semantic description Semantic advantage Semantic disadvantage Application Language
Axiomatic semantics Based on mathematical set theory axioms derivation process flow Logical and, after a grammar, less prone to bug Usually they do not have the Turing completeness, in order to avoid downtime argument python (incomplete), javascript (not exactly), haskell (completely)
Denotational semantics Predicate logic processing flow derived Relatively easy to describe the knowledge structure for modeling the knowledge base Compare against humanity, difficult to use, difficult to read prolog (rare)
Operational semantics Finite state machine processing flow derived Traditional programming languages ​​commonly used modes and easy to understand If the semantics too complex, will result in the state library is very large, heavy workload and difficult to maintain c, c ++, java and other common programming languages

Principle 2.c ++ analytic reflection

2.1 Key Technology Brief

Since the C ++ language itself does not have this type of reflection mechanism, even in c ++, provided decltype and type_id two simple reflex function, but can not meet our needs, there are mainly the following questions:

1.decltype can only copy variable depending on the type of variables, internal variables can not get information

2.typeid can create a variable type_info according to type, although some simple information can get variables, but because type_info unified standard has not been formed, which brought a huge risk of cross-platform and cross-compilers.

3. c ++ standard, generic ways to access the internal structure of an object or element does not already exist several methods.

So, in FC_REFLECT, the full realization of their own from the ground up a set of reflex scheme, and to ensure that this program has a cross-platform features. The program uses the following types of technologies:

1. Visitor pattern to obtain an object or an internal structure related information by this model

2. template specialization for each basic type, write a simple template exception, the exception type name returns a string

3. macro # operator, is converted into a string identifier

4. macro operator ##, two identifiers spliced ​​into a new identifier

The macro iterative expansion

6. functor, by overloading () operator, the object is provided with a function of the characteristics of an ordinary

2.2. Iterative macro expansion

Let's look at the following code:

struct Test{
    int a;
    float b;
    char c;
};
FC_REFLECT(Test,(a)(b)(c));

This is an example FC_REFLECT, we can see that in order to achieve access to the internal elements of the structure, first of all to the structure of each element within the FC_REFLECT added to make the structure and the internal elements form name association.

Next, we can look at the boost in an iterative macro, BOOST_PP_SEQ_FOR_EACH, which is the key to reflection, let's look at this macro to use:

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/for_each.hpp>

#define SEQ (w)(x)(y)(z)
#define MACRO(r, data, elem) BOOST_PP_CAT(elem, data)
BOOST_PP_SEQ_FOR_EACH(MACRO, _, SEQ);
// 一次展开
MACRO(2,_,w) MACRO(3,_,w) MACRO(4,_,w) MACRO(5,_,w)
// 二次展开
_w _x _y _z

According to this usage, we first define a simple macro reflection, we put this reflection macro name is REFLECT_V1, specifically worded as follows:

template<typename Object>
void visitor_object(Object& obj){}

template<typename Object, typename Type)>
void visitor_elem(Object &obj){
    std::cout<<obj.*Member<<std::endl;
}

#dfefine VISITOR_V1(r,type,elem) \
    void visit_elem<type,decltype(obj.elem),&type::elem>(obj);

#define REFLECT_V1(TYPE, MEN) \
    template<> \
    void visitor_object<TYPE>(TYPE& obj){ \
        BOOST_PP_SEQ_FOR_EACH(VISITOR_V1,TYPE,MEM) \
    }

// 现在我们来用这个宏,展开上面定义的结构体
REFLECT_V1(Test,(a)(b)(c))

// 展开后得到

template<>
void visitor_object<Test>(Test& obj){
    VISITOR_V1(2,Test,a) VISITOR_V1(3,Test,b) VISITOR_V1(4,Test,c)
}

// 二次展开得到
template<>
void visitor_object<Test>(Test& obj){
    visit_elem<Test,decltype(obj.a),&Test::a>(obj);
    visit_elem<Test,decltype(obj.b),&Test::b>(obj);
    visit_elem<Test,decltype(obj.c),&Test::c>(obj);
}

// 最后我们要通过调用visitor_object<Test>的模板特例,遍历打印结构体中的元素
Test t{1,2.0 3};
visitor_object<Test>(t);

The above is a simple reflection of implementation, you can see where BOOST_PP_SEQ_FOR_EACH this macro, is the key to reflection.

2.3 Obtaining the structure, the variable name and type name of each element

The previous example, even though it can traverse the structure of each element, and obtain the value of them, but it is difficult to judge the value of a variable is that, this section will describe how each of the variables associated with the value up, and the name of each type of printing.

The first is how to obtain the type name, in the first section, we discussed, there is a typeid operator in c ++ may be of a reflective type information, and wherein there name () This method gets the name of a string variable , fair to say, we can directly use, like this:

int a=5;
std::cout<<typeid(a).name()<<std::endl;

On Windows, you can print int string, but only prints a i but on linux. Since the c ++ standard has not yet developed this mechanism, so the different systems on different compilers, information output is also inconsistent, which gives us a cross-platform cross-compiler development is a big problem, so you can not use this mechanism to achieve it .

FC_REFLECT by hard-coding basic types of this function is achieved in accordance with the specific code as follows:

namespace fc {
  class value;
  class exception;
  namespace ip { class address; }

  template<typename... T> struct get_typename;
  template<> struct get_typename<int8_t>   { static const char* name()  { return "int8_t";   } };
  template<> struct get_typename<uint8_t>  { static const char* name()  { return "uint8_t";  } };
  template<> struct get_typename<int16_t>  { static const char* name()  { return "int16_t";  } };
  template<> struct get_typename<uint16_t> { static const char* name()  { return "uint16_t"; } };
  template<> struct get_typename<int32_t>  { static const char* name()  { return "int32_t";  } };
  template<> struct get_typename<uint32_t> { static const char* name()  { return "uint32_t"; } };
  template<> struct get_typename<int64_t>  { static const char* name()  { return "int64_t";  } };
  template<> struct get_typename<uint64_t> { static const char* name()  { return "uint64_t"; } };
  template<> struct get_typename<__int128>          { static const char* name()  { return "int128_t";  } };
  template<> struct get_typename<unsigned __int128> { static const char* name()  { return "uint128_t"; } };

  template<> struct get_typename<double>   { static const char* name()  { return "double";   } };
  template<> struct get_typename<float>    { static const char* name()  { return "float";    } };
  template<> struct get_typename<bool>     { static const char* name()  { return "bool";     } };
  template<> struct get_typename<char>     { static const char* name()  { return "char";     } };
  template<> struct get_typename<void>     { static const char* name()  { return "char";     } };
  template<> struct get_typename<string>   { static const char* name()  { return "string";   } };
  template<> struct get_typename<value>    { static const char* name()   { return "value";   } };
  template<> struct get_typename<fc::exception>   { static const char* name()   { return "fc::exception";   } };
  template<> struct get_typename<std::vector<char>>   { static const char* name()   { return "std::vector<char>";   } };
  template<typename T> struct get_typename<std::vector<T>>
  {
     static const char* name()  {
         static std::string n = std::string("std::vector<") + get_typename<T>::name() + ">";
         return n.c_str();
     }
  };
  template<typename T> struct get_typename<flat_set<T>>
  {
     static const char* name()  {
         static std::string n = std::string("flat_set<") + get_typename<T>::name() + ">";
         return n.c_str();
     }
  };
  template<typename T> struct get_typename< std::deque<T> >
  {
     static const char* name()
     {
        static std::string n = std::string("std::deque<") + get_typename<T>::name() + ">";
        return n.c_str();
     }
  };
  template<typename T> struct get_typename<optional<T>>
  {
     static const char* name()  {
         static std::string n = std::string("optional<") + get_typename<T>::name() + ">";
         return n.c_str();
     }
  };
  template<typename K,typename V> struct get_typename<std::map<K,V>>
  {
     static const char* name()  {
         static std::string n = std::string("std::map<") + get_typename<K>::name() + ","+get_typename<V>::name()+">";
         return n.c_str();
     }
  };

  struct signed_int;
  struct unsigned_int;
  template<> struct get_typename<signed_int>   { static const char* name()   { return "signed_int";   } };
  template<> struct get_typename<unsigned_int>   { static const char* name()   { return "unsigned_int";   } };

}

Thus, for common types, we can directly call get_typename <type name> :: name () will be able to get the type name, and for custom types, we can add the following content in reflection macro,

#define REFLECT_V1(TYPE, MEN) \
    template<> \
    void visitor_object<TYPE>(TYPE& obj){ \
        BOOST_PP_SEQ_FOR_EACH(VISITOR_V1,TYPE,MEM) \
    } \
    template<> struct get_typename<TYPE> { static const char* name() { return #TYPE; } };

The key is # Macro operators, the role of the operator, that is, the macro input parameter programming operators, we look at the following examples:

#define STR(x) #x
STR(1234abcd) //展开结果为"1234abcd"

All, to get the variable name and parameters, we only need to make the following changes

template<typename Object>
void visitor_object(Object& obj){}

template<typename Object, typename Type)>
void visitor_elem(const char* type,const char* var,Object &obj){
    std::cout<<"name:"<<type<<std::endl;
    std::cout<<"var:"<<var<<std::endl;
    std::cout<<"value:"<<obj.*Member<<std::endl;
}

#dfefine VISITOR_V2(r,type,elem) \
    void visit_elem<type,decltype(obj.elem),&type::elem>(get_typename<decltype(obj.elem)>::name(),BOOST_PP_STRINGIZE(elem),obj);

#define REFLECT_V2(TYPE, MEN) \
    template<> struct get_typename<TYPE> { static const char* name() { return #TYPE; } }; \
    template<> \
    void visitor_object<TYPE>(TYPE& obj){ \
        BOOST_PP_SEQ_FOR_EACH(VISITOR_V2,TYPE,MEM) \
    }

// 使用方法还是不变
REFLECT_V2(Test,(a)(b)(c))

// 展开后得到

template<>
void visitor_object<Test>(Test& obj){
    VISITOR_V1(2,Test,a) VISITOR_V1(3,Test,b) VISITOR_V1(4,Test,c)
}

// 二次展开得到
template<>
void visitor_object<Test>(Test& obj){
    visit_elem<Test,decltype(obj.a),&Test::a>("int","a",obj);
    visit_elem<Test,decltype(obj.b),&Test::b>("float","b",obj);
    visit_elem<Test,decltype(obj.c),&Test::c>("char","c",obj);
}

// 最后我们要通过调用visitor_object<Test>的模板特例,遍历打印结构体中的元素
Test t{1,2.0,3};
visitor_object<Test>(t);

2.4. Increase scalability

Although the front to achieve a reflection of the type of macro, but the specific process of death is written, we can not be extended and customized. But in specific projects, we need to perform different functions depending on the business, in order to improve development efficiency and reusability of modules, you can use the copy function to solve this problem.

The so-called copy function, is the definition of a class in the class overloads bracket operator, so that the generated instance can be used like an ordinary function, this is called functor, here, we can process the type of internal elements defined functor As follows:

struct Visitor{
    template<typename Object, typename Type, Type(Object::*Member)>
    operator (const char* type,const char* var,Object &obj){
        // 这里定义具体处理流程
    }
}

REFLECT then modified as follows:

template<typename Object,typename Visitor>
void visitor_object(Object& obj,Vistor vistor){}

// visitor_elem这个模板就不需要了

#dfefine VISITOR_V3(r,type,elem) \
    visitor.template operator()<data, decltype(_obj.elem), &data::elem>(get_typename<decltype(_obj.elem)>::name(), BOOST_PP_STRINGIZE(elem), obj);

#define REFLECT_V3(TYPE, MEN) \
    template<> struct get_typename<TYPE> { static const char* name() { return #TYPE; } }; \
    template<typename Visitor> \
    void visitor_object<TYPE>(TYPE& obj, Visitor& visitor){ \
        BOOST_PP_SEQ_FOR_EACH(VISITOR_V2,TYPE,MEM) \
    }

This way, if we want to access the interior of this element type, only a custom type, and reload () operator, then call visitor_object function parameters and is passed from the inside, on the line, code reusability and extensibility are With.

In FC_REFLECT, this process uses a visitor pattern, makes the code more compact, and easy to manage, but the principles and specific content of the appeal is no different.

2.5. In eos, the application introduction reflected

eos, for the application of the reflection it is very broad, basically can be said to be able to look at export of reflex.

1. during the call contract, the binary sequence match with abi

2. a class serialize and deserialize conversion

3. For the processing the value passed between virtual machines and physical machines

4. converted into a class json string back to the client

5. Virtual machine, multiple index access chainbase of container

6. Client cleos using get_table access to virtual machines table

Data exchange between each node and the plugin 7.node

8. Block Irreversible binary file storage and access

2.6 Examples of the use of reflected eos: converts into any type json

// 定义结构体
struct Test{
    char a;
    int b;
    float c;
};
FC_REFLECT(Test,(a)(b)(c));

// 定义访问者类
template<typename T>
class Vistor : fc::reflector_init_visitor<T> {
    public:
        Vistor(T &v) : fc::reflector_init_visitor<T>(v) {}

        //仿函数,重载reflector_init_visitor中的括号操作符
        template<typename Member, class Class, Member(Class::*member)>
        void operator()(const char* name)const {
            result(name, this->obj.*member);
        }

        //获取从Object转换成的json字串
        fc::variant get_result()const {
            return fc::variant(result);
        }

    private:
        //用于保存由Object转换成的json字串,声明为mutable,可被const函数修改
        mutable fc::mutable_variant_object result;
};

// 访问Test中元素
Test t{1,2,3.0};
Vistor<const Test&> v(t);
fc::reflector<Test>::visit(v);
v.get_result();

3. Other technology-related technology

In eos, a few examples of individual reflection, most of the composite structures used things:

1. serialization and deserialization (fc :: variant)

2. Virtual machine multi index container (multi_index)

Match analysis and input parameters 3.abi file

These examples require binding type of computing (fourth quadrant derivation process) is achieved.

Associated documentation (multi_index explain .md)

link

Galaxia public chain

For the latest block chain more related articles
, so stay tuned star chain micro-channel public number, star chain Grows with You
FC_REFLECT reflection Macro Tutorial

Guess you like

Origin blog.51cto.com/14267585/2451131