What you should update about C++

Transfer from: https://blog.csdn.net/valada/article/details/79910139

brief introduction

So far, C++ is still one of the classic languages ​​in the field of computer programming, and the C++17 standard has been discussed and confirmed in the first half of 2017. In this issue, we have gathered together the many years of experience of programming expert Qi Yu (author of "In-depth Application of C++ 11", founder of C++ open source community purecpp.org), and introduced in detail the new features in the latest C++ 17 standard that are worthy of attention by developers And basic usage.

Contents of this book

C++14 realizes compile-time reflection

Text/Qi Yu

This article will introduce the key technology of magic _ get by analyzing the source code of magic _ get, and analyze the principle of pod type reflection in depth.

Pod type compile-time reflection

Reflection is a mechanism for obtaining class internal information based on metadata. Through metadata, you can obtain information such as object fields and methods. The reflection mechanism of C# and Java is realized by obtaining the metadata of the object. Reflection can be used in areas closely related to the information of the object itself, such as dependency injection, ORM object-entity mapping, serialization and deserialization. For example, Java's Spring framework, whose dependency injection is based on reflection, can obtain type information and dynamically create objects based on metadata. The mapping between ORM objects and entities is also achieved through reflection. Both Java and C# are languages ​​based on the intermediate runtime. The intermediate runtime provides a reflection mechanism, so reflection is easy for runtime languages, but it is very difficult to implement reflection for languages ​​without intermediate runtime.

At the 2016 CppCon Technology Conference, Antony Polukhin gave a speech on C++ reflection. He proposed a new idea to realize reflection, that is, reflection can be realized without the use of macros, markers and additional tools. It seems to be an impossible task, because C++ has no reflection mechanism and cannot directly obtain the meta information of the object. But Antony Polukhin found that using Modern C++'s template meta technique for pod types can achieve such compile-time reflection. He open sourced a pod-type compile-time reflection library magic_get (https://github.com/apolukhin/magic_get), which is also ready to enter boost. Let's take a look at an example of using magic_get.

#include <boost pfr="" core.hpp="">struct foo {    int some_integer;    char c;};foo f {777, '!'};auto& r1 = boost::pfr::flat_get<0>(f); //通过索引来访问对象foo的第1个字段auto& r2 = boost::pfr::flat_get<1>(f); //通过索引来访问对象foo的第2个字段</boost>

As you can see from this example, magic _ get does achieve non-intrusive access to the fields of the foo object, without writing any macros, additional code, and special tools, and you can access the fields of the pod object directly at compile time without running Period burden is indeed a bit magical.

This article will introduce the key technology of magic _ get by analyzing the source code of magic _ get, and analyze the principle of pod type reflection in depth.

Key technology

The idea of ​​implementing pod type reflection is as follows: first convert the pod type to the corresponding tuple type, then assign the value of the pod type to the tuple, and then you can access the elements in the tuple by index. So the key to implementing pod reflection is how to convert the pod type to the corresponding tuple type and assign the pod value to the tuple.

Convert pod type to tuple type

What kind of tuple type does the pod type correspond to? Take the above foo as an example, the tuple corresponding to foo should be  tuple<int, char>, that is, the element type and order in the tuple correspond exactly to the fields in the pod type.

The basic idea of ​​generating a tuple based on the structure is to extract and save the type of each field in the structure in order, and then take it out to generate the corresponding tuple type. However, the types of fields are different, and C++ does not have a container that can directly store different types. Therefore, a workaround is needed. An indirect method is used to store the extracted field type, which is to convert the type to a size_t type. id, save this id in one  array<size_t, N> , then get the actual type based on this id and generate the corresponding tuple type.

One problem that needs to be solved here is how to realize the mutual conversion between type and id.

type and id are converted to each other at compile time

First use an empty template class to save the actual type, and then use the constexpr feature of C++14 to return the compile-time id corresponding to a certain type at compile time, and then the type can be converted to id. The specific code is as follows:

https://ipad-cms.csdn.net/cms/article/code/3445

The above code encodes the types int and char at compile time and converts the type to a specific compile-time constant. Later, you can obtain the corresponding specific type based on these compile-time constants.

The code for obtaining type according to id during compile time is as follows:

constexpr auto id_to_type( std::integral_constant<std::size_t, 6=""> ) noexcept { int res{}; return res; }constexpr auto id_to_type( std::integral_constant<std::size_t, 9=""> ) noexcept { char res{}; return res; }</std::size_t,></std::size_t,>

In the above code, id _ to _ type returns an instance of the type corresponding to id. If you want to get the type corresponding to id, you need to deduce it through decltype. magic _ get encodes all the basic types of pod through a macro to realize the mutual conversion between type and id at compile time.

#define REGISTER_TYPE(Type, Index)                                              \    constexpr std::size_t type_to_id(identity<type>) noexcept { return Index; } \    constexpr auto id_to_type( std::integral_constant<std::size_t, index=""> ) noexcept { Type res{}; return res; }  \// Register all base types here    REGISTER_TYPE(unsigned short    , 1)    REGISTER_TYPE(unsigned int      , 2)    REGISTER_TYPE(unsigned long long , 3)    REGISTER_TYPE(signed char       , 4)    REGISTER_TYPE(short             , 5)    REGISTER_TYPE(int               , 6)    REGISTER_TYPE(long long         , 7)    REGISTER_TYPE(unsigned char     , 8)    REGISTER_TYPE(char              , 9)    REGISTER_TYPE(wchar_t          , 10)    REGISTER_TYPE(long             , 11)    REGISTER_TYPE(unsigned long    , 12)    REGISTER_TYPE(void*            , 13)    REGISTER_TYPE(const void*      , 14)    REGISTER_TYPE(char16_t         , 15)    REGISTER_TYPE(char32_t         , 16)    REGISTER_TYPE(float             , 17)    REGISTER_TYPE(double           , 18)    REGISTER_TYPE(long double      , 19)</std::size_t,></type>

After encoding the type, where to save it and how to take it out is the next problem to be solved. magic _ get saves the structure field type id by defining an array.

template <class t,="" std::size_t="" n="">   struct array {       typedef T type;       T data[N];       static constexpr std::size_t size() noexcept { return N; }   };</class>

The fixed-length array data in the array stores the id corresponding to the field type, and the array subscript is the position index of the field in the structure.

Extract pod structure fields

The previous section introduced how to save and retrieve the field type, so how does this field type be extracted from the pod structure? The specific approach is divided into three steps:

  • Define an array that holds the field type id;
  • Convert the field type of the pod to the corresponding id and save it in the array in order;
  • Filter out the extra part in the array.

The following is the specific implementation code:

template <class t="">constexpr auto fields_count_and_type_ids_with_zeros() noexcept {    static_assert(std::is_trivial<t>::value, "Not applyable");    array<std::size_t, sizeof(t)=""> types{};    detect_fields_count_and_type_ids<t>(types.data, std::make_index_sequence<sizeof(t)>{});    return types;}template <class t="">constexpr auto array_of_type_ids() noexcept {    constexpr auto types = fields_count_and_type_ids_with_zeros<t>();    constexpr std::size_t count = count_nonzeros(types);    array<std::size_t, count=""> res{};    for (std::size_t i = 0; i < count; ++i) {        res.data[i] = types.data[i];    }        return res;    }</std::size_t,></t></class></sizeof(t)></t></std::size_t,></t></class>

When defining an array, you need to define a fixed array length. What is the appropriate length? It should be determined by the maximum number of fields in the structure. Because the number of fields in the structure is at most sizeof(T), the length of the array is set to sizeof(T). The elements in the array are all initialized to 0. Under normal circumstances, the number of structure fields generally does not exceed the length of the array, so extra elements will appear in the array, so the extra fields in the array need to be removed, and only the valid field type id is saved. The specific method is to calculate the number of non-zero elements in the array, and then assign the non-zero elements to a new array. The following is the calculation of the number of non-zero elements of the array, which is also calculated at compile time with the help of constexpr.

template <class array="">constexpr auto count_nonzeros(Array a) noexcept {    std::size_t count = 0;    for (std::size_t i = 0; i < Array::size() && a.data[i]; ++i)        ++ count;    return count;}</class>

Since the fields are stored in the array in order, the count when the element value is 0 is the number of valid elements. Next, let's take a look at the implementation of detect _ fields _ count _ and _ type _ ids. This constexpr function saves the field type id in the structure to the data of the array.

detect_fields_count_and_type_ids<t>(types.data, std::make_index_sequence<sizeof(t)>{});</sizeof(t)></t>

The first parameter of detect _ fields _ count _ and _ type _ ids is the data of a fixed-length array <std::size _ t, sizeof(T)>, and the second parameter is a std::index _ sequence integer sequence. The specific implementation code of detect _ fields _ count _ and _ type _ ids is as follows:

template <class t,="" std::size_t="" i0,="" std::size_t...="" i="">constexpr auto detect_fields_count_and_type_ids(std::size_t* types, std::index_sequence<i0, i...="">) noexcept-> decltype( type_to_array_of_type_ids<t, i0,="" i...="">(types) ){    return type_to_array_of_type_ids<t, i0,="" i...="">(types);}template <class t,="" std::size_t...="" i="">constexpr T detect_fields_count_and_type_ids(std::size_t* types, std::index_sequence<i...>) noexcept {    return detect_fields_count_and_type_ids<t>(types, std::make_index_sequence<sizeof...(i) -="" 1="">{});}template <class t="">constexpr T detect_fields_count_and_type_ids(std::size_t*, std::index_sequence<>) noexcept {    static_assert(!!sizeof(T), "Failed for unknown reason");    return T{};}</class></sizeof...(i)></t></i...></class></t,></t,></i0,></class>

The above code is to expand the index _ sequence into a sequence of 0, 1, 2..., sizeof(T). After obtaining this sequence, call the type _ to _ array _ of _ type _ ids function to implement the structure The field type id is saved in the array.

Before we talk about the type _ to _ array _ of _ type _ ids function, let's take a look at the auxiliary structure ubiq. The storage pod field type id is actually implemented by the auxiliary structure ubiq, and its implementation is as follows:

template <std::size_t i="">struct ubiq {    std::size_t* ref_;    template <class type="">    constexpr operator Type() const noexcept {        ref_[I] = type_to_id(identity<type>{});        return Type{};    }};</type></class></std::size_t>

This structure is special, so let's simplify it first.

struct ubiq {    template <class type="">    constexpr operator Type() const {        return Type{};    };};</class>

The special feature of this structure is that it can be used to construct any pod type, such as int, char, double, etc.

int i = ubiq{};double d = ubiq{};char c = ubiq{};

Because the type required by the ubiq constructor is automatically inferred by the compiler, it can construct any pod type. After obtaining the type to be constructed through the ubiq structure, we also need to convert this type to id and save it in a fixed-length array in order.

template <std::size_t i="">struct ubiq {    std::size_t* ref_;    template <class type="">    constexpr operator Type() const noexcept {        ref_[I] = type_to_id(identity<type>{});        return Type{};    }};</type></class></std::size_t>

In the above code, first convert the type deduced by the compiler into id, and then save it to the position where the subscript of the array is I.

Look back at the type _ to _ array _ of _ type _ ids function.

template <class t,="" std::size_t...="" i="">constexpr auto type_to_array_of_type_ids(std::size_t* types) noexcept -> decltype(T{ ubiq<i>{types}... }) {    return T{ ubiq<i>{types}... };}</i></i></class>

type _ to _ array _ of _ type _ ids has two template parameters, the first T is the type of pod structure, the second size _ t... is an integer sequence from 0 to sizeof(T), the function of The input parameter is size_t*, which is actually  array<std::size_t, sizeof(T)> data, used to store the pod field type id.

The key code to save the field type is this line: T{ ubiq〈I〉{types}... }, where the pod type constructor is used, through the initializer_list construction, the compiler will deduce the field type of T, And use ubiq to convert the field type to id and save it in the array. This is the magic in magic _ get.

After saving the pod structure field id in the array, then you need to convert the list of ids in the array to tuple.

Pod field id sequence is converted to tuple

The specific method of converting pod field id sequence to tuple is divided into two steps:

  • Put the field type id saved in the array into the plastic sequence std::index _ sequence;
  • Convert the type id in index_sequence to the corresponding type to form a tuple.

The following is the specific implementation code:

template <std::size_t i,="" class="" t,="" std::size_t="" n="">constexpr const T& get(const array<t,n>& a) noexcept {    return a.data[I];}template <class t,="" std::size_t...="" i="">constexpr auto array_of_type_ids_to_index_sequence(std::index_sequence<i...>) noexcept {    constexpr auto a = array_of_type_ids<t>();    return std::index_sequence< get<i>(a)...>{};}</i></t></i...></class></t,n></std::size_t>

get is to return the element value at an index position in the array, that is, the type id, the returned id is put into std::index _ sequence, and then the id in the index _ sequence is converted to type through index _ sequence to form a tuple .

template <std::size_t... i="">constexpr auto as_tuple_impl(std::index_sequence<i...>) noexcept {    return std::tuple< decltype( id_to_type(std::integral_constant<std::size_t, i="">{}) )... >{};}template <class t="">constexpr auto as_tuple() noexcept {    static_assert(std::is_pod<t>::value, "Not applyable");    constexpr auto res = as_tuple_impl(            array_of_type_ids_to_index_sequence<t>(                    std::make_index_sequence< decltype(array_of_type_ids<t>())::size() >()            )    );    static_assert(sizeof(res) == sizeof(T), "sizes check failed");    static_assert(            std::alignment_of<decltype(res)>::value == std::alignment_of<t>::value,            "alignment check failed"    );    return res;}</t></decltype(res)></t></t></t></class></std::size_t,></i...></std::size_t...>

id _ to _ type returns a type instance corresponding to a certain id, so decltype is also needed to deduce the type. In this way, we can get a tuple type based on T. Next, we need to assign the value of T to the tuple, and then we can access the field of T based on the index.

pod is assigned to tuple

For the clang compiler, the pod structure can be directly converted to std::tuple, so for the clang compiler, this is the end.

template <std::size_t i,="" class="" t="">decltype(auto) get(const T& val) noexcept {    auto t = reinterpret_cast<const decltype(detail::as_tuple<t="">())*>( std::addressof(val) );    return get<i>(*t);}</i></const></std::size_t>

However, for other compilers, such as msvc or gcc, the memory of a tuple is not continuous, and T can not be directly converted to a tuple, so a more general approach is to make a tuple with continuous memory, and then you can directly convert T to tuple out.

Tuple with contiguous memory

The following is the tuple code for memory contiguous:

template <std::size_t n,="" class="" t="">struct base_from_member {    T value;};template <class i,="" class="" ...tail="">struct tuple_base;template <std::size_t... i,="" class="" ...tail="">struct tuple_base< std::index_sequence<i...>, Tail... >        : base_from_member<i ,="" tail="">...{    static constexpr std::size_t size_v = sizeof...(I);    constexpr tuple_base() noexcept = default;    constexpr tuple_base(tuple_base&&) noexcept = default;    constexpr tuple_base(const tuple_base&) noexcept = default;    constexpr tuple_base(Tail... v) noexcept            : base_from_member<i, tail="">{ v }...    {}};template <>struct tuple_base<std::index_sequence<> > {    static constexpr std::size_t size_v = 0;};template <class ...values="">struct tuple: tuple_base<        std::make_index_sequence<sizeof...(values)>,        Values...>{    using tuple_base<            std::make_index_sequence<sizeof...(values)>,            Values...    >::tuple_base;};</sizeof...(values)></sizeof...(values)></class></std::index_sequence<></i,></i></i...></std::size_t...></class></std::size_t>

base _ from _ member is used to store the index and value of the tuple element, tuple _ base is derived from base _ from _ member, and each type of base _ from _ member in the tuple is automatically generated. Tuple is derived from tuple _ base to simplify the tuple The definition of _ base. Add an auxiliary method to the tuple to get elements based on index.

template <std::size_t n,="" class="" t="">constexpr const T& get_impl(const base_from_member<n, t="">& t) noexcept {    return t.value;}template <std::size_t n,="" class="" ...t="">constexpr decltype(auto) get(const tuple<t...>& t) noexcept {    static_assert(N < tuple<t...>::size_v, "Tuple index out of bounds");    return get_impl<n>(t);}</n></t...></t...></std::size_t></n,></std::size_t>

In this way, the elements in the tuple can be obtained through get.

At this point, the analysis of the core code of magic_get is complete. Since the actual code will be more complicated, in order to make it easier for readers to understand, I chose the simplified version of the code. For the complete code, please refer to the magic get on GitHub   or the simplified version of the code https://github.com/qicosmos /cosmos/blob/master/podreflection.hpp .

to sum up

magic _ get implements the reflection of the pod type, you can directly access the fields of the pod structure through the index, without any additional macros, tags or tools, it is really magic. magic _ get is mainly implemented through C++11/14 variable template parameters, constexpr, index _ sequence, pod constructor and many template meta techniques. So what can magic _ get be used for? According to the feature of magic_get that can realize compile-time reflection without additional burden and code, it is very suitable as an ORM database access engine and a general serialization/deserialization library. I believe there are more potentials and applications waiting for us to explore .

Some seemingly ordinary features of Modern C++ can be combined to produce magical powers. People can't help but admire the infinite possibilities and magic of Modern C++.

Those noteworthy features in C++17 (on)

Those noteworthy features in C++17 (middle)

Those noteworthy features in C++17 (below)

Read the full text:  http://gitbook.cn/gitchat/geekbook/5a7a81b85a8e2a1cf2e2a993

Guess you like

Origin blog.csdn.net/gouguofei/article/details/109203368