Overview • How To Build • Usage • Documentation • License
[TOC]
C++ allows for templates to be defined with variadic parameters, that is, the number of parameters can be unknown. These can be used for encoding arbitrary static information into types. Here is an example:
// Template struct to hold static information
template <typename ...Args>
struct pack {};
// Example use - Static reflection
template <typename T, std::size_t Offset>
struct field {
using type = T;
static constexpr std::size_t offset = Offset;
};
struct SomeObject {
int some_int {};
double some_double {};
std::string some_string {};
using field_types = pack<
field<int, 0>,
field<double, 8>,
field<std::string, 16>
>;
};In this case, the type alias SomeObject::field_types encodes a list of types corresponding to the fields of the struct SomeObject, as well as their offsets. The type pack is merely a compile-time construct, used only for holding static information. Note that none of the reflection information makes it to runtime, thus not affecting memory usage nor performance.
This library provides operations for accessing and mutating constructs like this. Some accessor examples are:
using size = packtl::get_size<SomeObject::field_types>::value; // 3
using first = packtl::get_first<SomeObject::field_types>::type; // field<int, 0>
using last = packtl::get_last<SomeObject::field_types>::type; // field<std::string, 16>
using second = packtl::get<2, SomeObject::field_types>::type; // field<double, 8>
bool has_int = packtl::has_item<field<int, 0>, SomeObject::field_types>::value; // trueMeanwhile, some of the mutations that this library can perform are:
using appended = packtl::append<field<char, 24>>::to<SomeObject::field_types>::type;
using prepended = packtl::prepend<field<char, 24>>::to<SomeObject::field_types>::type;
using sliced = packtl::slice<1, 2, SomeObject::field_types>::type; // Returns a pack containing the 2nd and 3rd fields
After cloning this repository, or otherwise getting a copy of this directory, configure the library by running the following CMake command in this directory:
# Built type (Debug/Release) Build dir Generator
# /--------------------------------\/--------\/-------\
cmake -DCMAKE_BUILD_TYPE:STRING=Release -B./build -G NinjaOnce the project is configured, the library can be build with the following command:
# Build dir Built type Build target
# /--------\/---------------\/--------------\
cmake -B./build --config Release --target packtlDoxygen documentation can be build with the following command:
# Build dir Built type Build target
# /--------\/---------------\/-------------------\
cmake -B./build --config Release --target packtl_docsTests can be built either all at once or separately with the following commands:
# Build dir Built type Build target
# /--------\/---------------\/-------------------------\
cmake -B./build --config Release --target TEST_SUITE_packtl# Build dir Built type Build target
# /--------\/---------------\/------------------------\
cmake -B./build --config Release --target TEST_<test_name>Integrating this library into a CMake project is as simple as adding the following to the CmakeLists.txt file:
include(FetchContent)
FetchContent_Declare(packtl
GIT_REPOSITORY https://github.com/castle055/packtl.git
GIT_TAG main # or a tag (check repository for latest version)
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(packtl)Everything in this library can be imported from the module packtl, where all operations are placed withing the packtl namespace. Carrying on with the static reflection example, here is a very simple serializer:
export module serializer;
export import std;
export import packtl;
// Access any field from any object, given its type and offset
template <typename T, std::size_t Offset>
struct field_accessor {
char offset_padding[Offset];
T value;
}
// Serialize a specific field within an object
template <typename T, typename Field>
std::string serialize_field(const T& obj) {
using field_type = typename Field::type;
constexpr std::size_t field_offset = Field::offset;
field_type& field_data = static_cast<field_accessor<field_type, field_offset>*>(&obj)->value;
return std::format("{}", field_data);
}
// Serialize all fields within an object
template <typename T, std::size_t ...I>
std::string serialize_all_fields(const T& obj, std::index_sequence<I...>) {
using fields = typename T::field_types;
std::string str = "{ ";
// Serialize each field and append it to `str`
(str.append(serialize_field<packtl::get<I, fields>::type>(obj).append("; ")), ...);
str.substr(0, str.size()-2); // remove last semicolon
str.append("}");
return str;
}
// Serialize any object that contains a `field_types` pack
export template <typename T>
requires requires {
typename T::field_types;
}
std::string serialize(const T& obj) {
using fields = typename T::field_types;
return serialize_all_fields(obj, std::make_index_sequence<packtl::get_size<fields>::value>());
}And this would be the result:
import serializer;
int main(int argc, char* argv[]) {
SomeObject obj {
.some_int = 1,
.some_double = 3.14,
.some_string = "Hello, World!",
};
std::cout << serialize(obj) << std::endl;
return 0;
}OUTPUT:
{ 1; 3.14; Hello, world!}
Documentation can be found here.
Apache License 2.0 · LICENSE.md
GitHub @castle055 ·