yaLanTingLibs is a collection of modern C++ basic tool libraries that are open-sourced by Alibaba Cloud. It includes serialization, HTTP, RPC, coroutine, compile-time reflection, metrics, and log libraries. It can help C++ developers quickly build high-performance C++ applications. At the 2024 Apsara Conference Operating System Technology Workshop, Yu Qi, a senior technical expert at Alibaba Cloud Intelligence Group, purecpp community leader, and author of Deep Application of C++11, shared An In-depth Application of C++20 Compile-time Reflection. This article focuses on the yaLanTingLibs compile-time reflection library and introduces its application scenarios, functions, and features through some cases, as well as its usage in practical development.
The examples in this article recommend installing the Alibaba Cloud Compiler on the Anolis operating system. For installation instructions, please refer to the link.
First, let's start with a simple example:
#include "ylt/reflection/member_value.hpp"
struct simple {
int color;
int id;
std::string str;
int age;
};
int main() {
using namespace ylt::reflection;
// Obtain the number of simple fields at compile time.
static_assert(member_count_v<simple> == 4);
// Obtain the name of the simple type at compile time.
static_assert(get_struct_name<simple>() == "simple");
// Obtain the list of simple field names at compile time.
static_assert(get_member_names<simple>() == std::array<std::string_view, 4>{"color", "id", "str", "age"});
// Traverse its field names and indexes based on the type.
for_each<simple>([](std::string_view field_name) {
std::cout << field_name << "\n";
});
for_each<simple>([](std::string_view field_name, size_t index) {
std::cout << index << ", " << field_name << "\n";
});
}
In addition to APIs for getting these most basic object metadata, ylt::reflection provides more useful APIs:
int main() {
simple p{.color = 2, .id = 10, .str = "hello reflection", .age = 6};
// Obtain the field value through the compile-time index.
CHECK(std::get<0>(p) == 2);
CHECK(std::get<2>(p) == "hello reflection");
// Obtain the field name based on the compile-time index.
static_assert(name_of<simple, 1>()== "id");
// Obtain the field value based on the compile-time field name.
CHECK(get<"age"_ylts>(p) == 6);
CHECK(get<"str"_ylts>(p) == "hello reflection");
// Obtain the field index based on the compile-time field name.
static_assert(index_of<simple2, "str"_ylts>() == 2);
// Traverse the object's fields, field names, and field indexes, and print them.
for_each(p, [](auto& field, auto name, auto index) {
std::cout << field << ", " << name << ", " << index << "\n";
});
// Access all field values of the object.
visit_members(p, [](auto&&... args) {
((std::cout << args << " "), ...);
std::cout << "\n";
});
}
With these compile-time reflection APIs, you can do some interesting things, such as serialization. In theory, an object can be serialized to any format using compile-time reflection. Similarly, yaLanTingLibs also need to use a unified compile-time reflection API to convert objects into formats such as JSON, XML, YAML, Protobuf, and others.
Based on the yaLanTingLibs reflection library, it is possible to non-intrusively serialize an object into various data formats, and it can also be extended to support custom formats.
#include "ylt/struct_json/json_reader.h"
#include "ylt/struct_json/json_writer.h"
#include "ylt/struct_xml/xml_reader.h"
#include "ylt/struct_xml/xml_writer.h"
#include "ylt/struct_yaml/yaml_reader.h"
#include "ylt/struct_yaml/yaml_writer.h"
#include "ylt/struct_pb.hpp"
struct simple {
int color;
int id;
std::string str;
int age;
};
int main() {
simple p{.color = 2, .id = 10, .str = "hello reflection", .age = 6};
std::string json;
struct_json::to_json(p, json);
std::string xml;
struct_xml::to_xml(p, xml);
std::string yaml;
struct_yaml::to_yaml(p, yaml);
std::string protobuf;
struct_pb::to_pb(p, protobuf);
simple p1;
struct_json::from_json(p1, json);
struct_xml::from_xml(p1, xml);
struct_yaml::from_yaml(p1, xml);
struct_pb::from_pb(p1, xml);
}
The yaLanTingLibs reflection library can be used to easily serialize an object into a custom format. For example, you can serialize an object into a simple JSON5 format, where the key is unquoted.
You only need to use the reflection API for_each to get the object's field values and field names.
struct point {
int x;
int y;
};
void test_json5() {
point pt{2, 4};
std::string json5;
json5.append("{");
ylt::reflection::for_each(pt, [&](auto& field, auto name) {
json5.append(name).append(":").append(std::to_string(field)).append(",");
});
json5.back() = '}';
CHECK(json5 == "{x:2,y:4}");
}
The preceding example can be compiled and run only in C++20 compilers (clang13 +, gcc11 +, and msvc2022). If the compiler version is earlier and only supports C++17, can the yaLanTingLibs reflection library be used?
The answer is also yes. The yaLanTingLibs reflection library is compatible with C++17, supporting the earliest version of gcc9. An additional macro definition is required to use the reflection API (which is not needed in higher versions of C++20 compilers). For example, in the previous example:
struct simple {
int color;
int id;
std::string str;
int age;
};
YLT_REFL(simple, color, id, str, age);
int main() {
using namespace ylt::reflection;
// Obtain the number of simple fields at compile time.
static_assert(member_count_v<simple> == 4);
// Obtain the name of the simple type at compile time.
static_assert(get_struct_name<simple>() == "simple");
// Obtain the list of simple field names at compile time.
static_assert(get_member_names<simple>() == std::array<std::string_view, 4>{"color", "id", "str", "age"});
// Traverse its field names and indexes based on the type.
for_each<simple>([](std::string_view field_name, size_t index) {
std::cout << index << ", " << field_name << "\n";
});
}
After the YLT_REFL macro is defined, the reflection API can be used. If the object's fields are private, the macro needs to be defined within the object itself:
struct simple {
YLT_REFL(simple, color, id, str, age);
private:
int color;
int id;
std::string str;
int age;
};
This approach allows for reflection of private fields but is invasive. If the object has public methods to access private fields, a non-invasive approach can also be implemented:
struct dummy_t5 {
private:
int id = 42;
std::string name = "tom";
int age = 20;
public:
int& get_id() { return id; }
std::string& get_name() { return name; }
int& get_age() { return age; }
const int& get_id() const { return id; }
const std::string& get_name() const { return name; }
const int& get_age() const { return age; }
};
YLT_REFL(dummy_t5, get_id(), get_name(), get_age());
If all the object's fields are private and there are no public access methods provided, is reflection still possible? It is possible to reflect private fields without providing public access methods. By using another macro from the yaLanTingLibs reflection library, YLT_REFL_PRIVATE, you can non-intrusively reflect the private fields of an object:
class private_struct {
int a;
int b;
public:
private_struct(int x, int y) : a(x), b(y) {}
};
YLT_REFL_PRIVATE(private_struct, a, b);
Now you can reflect the private fields of the object:
private_struct st(2, 4);
for_each(st, [](auto& field, auto name, auto index){
std::cout << field << ", " << name << ", " << index << "\n";
});
visit_members(st, [](auto&... args) {
((std::cout << args << " "), ...);
std::cout << "\n";
});
The application scenarios for compile-time reflection are very broad, making it highly suitable for serialization scenarios, ORM (Object-Relational Mapping) scenarios, and non-intrusive access to objects. The yaLanTingLibs reflection library not only supports C++20 but is also compatible with C++17. Whether it's earlier version compilers, later version compilers, or scenarios where objects have private fields, a unified set of easy-to-use APIs can be utilized.
A Comprehensive Upgrade of AI-powered Operating System Service Experience in the Era of Large Models
42% Boost in Compilation Efficiency! A Practical Analysis of C++ Modules
90 posts | 5 followers
FollowXianYu Tech - September 8, 2020
Alibaba Tech - July 11, 2019
Alibaba Container Service - April 28, 2020
Alibaba Cloud Community - July 12, 2023
Alibaba Cloud Community - July 29, 2024
Alibaba Cloud Native Community - April 29, 2024
90 posts | 5 followers
FollowAlibaba Cloud Linux is a free-to-use, native operating system that provides a stable, reliable, and high-performance environment for your applications.
Learn MoreExplore Web Hosting solutions that can power your personal website or empower your online business.
Learn MoreExplore how our Web Hosting solutions help small and medium sized companies power their websites and online businesses.
Learn MoreReach global users more accurately and efficiently via IM Channel
Learn MoreMore Posts by OpenAnolis
Start building with 50+ products and up to 12 months usage for Elastic Compute Service
Get Started for Free Get Started for Free