{"id":13730436,"url":"https://github.com/phisko/reflection","last_synced_at":"2025-03-27T02:30:53.120Z","repository":{"id":65318914,"uuid":"362760667","full_name":"phisko/reflection","owner":"phisko","description":"A simple, stand-alone, header-only and easily pluggable reflection system for C++.","archived":false,"fork":false,"pushed_at":"2023-03-08T11:19:45.000Z","size":102,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-05-23T08:20:33.861Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phisko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-04-29T09:23:33.000Z","updated_at":"2023-11-24T19:07:42.000Z","dependencies_parsed_at":"2024-01-25T18:05:30.217Z","dependency_job_id":"ab9382b4-3d3a-4654-a9c5-b0feacc56d7a","html_url":"https://github.com/phisko/reflection","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phisko%2Freflection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phisko%2Freflection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phisko%2Freflection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phisko%2Freflection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phisko","download_url":"https://codeload.github.com/phisko/reflection/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222183038,"owners_count":16944872,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T02:01:14.856Z","updated_at":"2024-10-30T08:12:05.758Z","avatar_url":"https://github.com/phisko.png","language":"C++","readme":"# [Reflection](reflection.hpp)\n\n[![tests](https://github.com/phisko/reflection/workflows/tests/badge.svg)](https://github.com/phisko/reflection/actions/workflows/tests.yml)\n\nA simple, stand-alone, header-only and easily pluggable reflection system.\n\nThis provides an API that you can implement for any type so that you can introspect its attributes, methods, parent types and any types it \"uses\".\n\nThe \"used types\" concept can refer to anything really, but one interesting use case is when registering a type with scripting languages. If you're going to be accessing a type's attributes through a scripting language, chances are you also want to register those attributes' types.\n\nAn example use case for this API is the register_type function provided for [Lua](https://github.com/phisko/putils/blob/master/lua/lua_helper.hpp#L80) and [Python](https://github.com/phisko/putils/blob/master/python/python_helper.inl) in my putils library, that inspects a type and registers all its attributes and methods to the scripting language.\n\n## Helpers\n\nA [generate_reflection_headers](scripts/generate_reflection_headers.md) script is provided to automatically generate reflection info. This is however completely optional, and you might prefer writing your reflection info by hand to start with.\n\n## Overview\n\nMaking a type reflectible is done like so:\n\n```cpp\nstruct parent {};\n\nstruct reflectible : parent {\n    int i = 0;\n\n    int get_value() const { return i; }\n};\n\n#define refltype reflectible\nputils_reflection_info {\n    putils_reflection_class_name;\n    putils_reflection_attributes(\n        putils_reflection_attribute(i)\n    );\n    putils_reflection_methods(\n        putils_reflection_attribute(get_value)\n    );\n    putils_reflection_parents(\n        putils_reflection_type(parent)\n    );\n    putils_reflection_used_types(\n        putils_reflection_type(int)\n    );\n};\n#undef refltype\n```\n\nNote that all the information does not need to be present. For instance, for a type with only two attributes, and for which we don't want to expose the class name:\n\n```cpp\nstruct simple {\n    int i = 0;\n    double d = 0;\n};\n\n#define refltype simple\nputils_reflection_info {\n    putils_reflection_attributes(\n        putils_reflection_attribute(i),\n        putils_reflection_attribute(d)\n    );\n};\n#undef refltype\n```\n\nAccessing a type's name, attributes, methods and used types is done like so:\n\n```cpp\nint main() {\n    reflectible obj;\n\n    std::cout \u003c\u003c putils::reflection::get_class_name\u003creflectible\u003e() \u003c\u003c std::endl;\n\n    // Obtaining member pointers\n    {\n        putils::reflection::for_each_attribute\u003creflectible\u003e(\n            [\u0026](const auto \u0026 attr) {\n                assert(attr.ptr == \u0026reflectible::i);\n                std::cout \u003c\u003c attr.name \u003c\u003c \": \" \u003c\u003c obj.*attr.ptr \u003c\u003c std::endl;\n            }\n        );\n        constexpr auto member_ptr = putils::reflection::get_attribute\u003cint, reflectible\u003e(\"i\");\n        static_assert(member_ptr == \u0026reflectible::i);\n    }\n\n    // Obtaining attributes of a specific object\n    {\n        putils::reflection::for_each_attribute(obj,\n            [\u0026](const auto \u0026 attr) {\n                assert(\u0026attr.member == \u0026obj.i);\n                std::cout \u003c\u003c attr.name \u003c\u003c \": \" \u003c\u003c attr.member \u003c\u003c std::endl;\n            }\n        );\n        const auto attr = putils::reflection::get_attribute\u003cint\u003e(obj, \"i\");\n        assert(attr == \u0026obj.i);\n    }\n\n    // Obtaining member function pointers\n    putils::reflection::for_each_method\u003creflectible\u003e(\n        [\u0026](const auto \u0026 method) {\n            assert(method.ptr == \u0026reflectible::get_value);\n            std::cout \u003c\u003c method.name \u003c\u003c \": \" \u003c\u003c (obj.*method.ptr)() \u003c\u003c std::endl;\n        }\n    );\n\n    // Obtaining functors to call the method on a given object\n    putils::reflection::for_each_method(obj,\n        [](const auto \u0026 method) {\n            // func is a functor that calls obj.get_value()\n            std::cout \u003c\u003c method.name \u003c\u003c \": \" \u003c\u003c method.member() \u003c\u003c std::endl;\n        }\n    );\n\n    putils::reflection::for_each_parent\u003creflectible\u003e(\n        [](const auto \u0026 type) {\n            // type: putils::meta::type\u003cparent\u003e\n            using T = putils_wrapped_type(type.type);\n            std::cout \u003c\u003c typeid(T).name() \u003c\u003c std::endl;\n        }\n    );\n\n    putils::reflection::for_each_used_type\u003creflectible\u003e(\n        [](const auto \u0026 type) {\n            // type: putils::meta::type\u003cint\u003e\n            using T = putils_wrapped_type(type.type);\n            std::cout \u003c\u003c typeid(T).name() \u003c\u003c std::endl;\n        }\n    );\n\n    return 0;\n}\n```\n\nAll these operations can also be done at compile-time:\n\n```cpp\nconstexpr reflectible obj;\n\nconstexpr auto \u0026 attributes = putils::reflection::get_attributes\u003creflectible\u003e();\nusing expected_type = std::tuple\u003cstd::pair\u003cconst char *, int reflectible:: *\u003e\u003e;\nstatic_assert(std::is_same_v\u003cstd::decay_t\u003cdecltype(attributes)\u003e, expected_type\u003e);\nstatic_assert(std::get\u003c0\u003e(attributes).second == \u0026reflectible::i);\n\nconstexpr auto attr = putils::reflection::get_attribute\u003cint\u003e(obj, \"i\");\nstatic_assert(attr == \u0026obj.i);\nstatic_assert(*attr == 0);\n\nconstexpr size_t count_attributes() {\n    size_t ret = 0;\n    putils::reflection::for_each_attribute\u003creflectible\u003e(\n        [\u0026](const auto \u0026 attr) {\n            ++ret;\n        }\n\t);\n    return ret;\n}\n\nstatic_assert(count_attributes() == 1);\n```\n\nFunctions and concepts are also provided to check if a type exposes a given property:\n\n```cpp\nstatic_assert(putils::reflection::has_class_name\u003creflectible\u003e());\nstatic_assert(putils::reflection::has_attributes\u003creflectible\u003e());\nstatic_assert(putils::reflection::has_methods\u003creflectible\u003e());\nstatic_assert(putils::reflection::has_parents\u003creflectible\u003e());\nstatic_assert(putils::reflection::has_used_types\u003creflectible\u003e());\n\nstatic_assert(putils::reflection::with_class_name\u003creflectible\u003e);\n...\n```\n\n## Metadata\n\nTypes, attributes and methods can be annotated with custom metadata like so:\n\n```cpp\nstruct with_metadata {\n    int i = 0;\n    void f() const;\n};\n\n#define refltype with_metadata\nputils_reflection_info {\n    putils_reflection_type_metadata(\n        putils_reflection_metadata(\"key\", \"value\")\n    );\n    putils_reflection_attributes(\n        putils_reflection_attribute(i, putils_reflection_metadata(\"meta_key\", \"meta_value\"))\n    );\n    putils_reflection_methods(\n        putils_reflection_attribute(f, putils_reflection_metadata(42, std::string(\"value\")))\n    );\n};\n#undef refltype\n```\n\nMetadata are key-value pairs, with no specific constraint regarding the types for either the key or the value.\n\nMetadata can then be accessed directly from the `metadata` table in the `attribute_info` returned by `get_attributes`/`get_methods` and iterated on by `for_each_attribute`/`for_each_method`.\n\nThey may also be queried and accessed through helper functions:\n```cpp\ntemplate\u003ctypename T, typename Key\u003e\nconstexpr bool has_metadata(Key \u0026\u0026 key) noexcept;\n\ntemplate\u003ctypename Ret, typename T, typename Key\u003e\nconstexpr const Ret * get_metadata(Key \u0026\u0026 key) noexcept;\n\ntemplate\u003ctypename T, typename Key\u003e\nconstexpr bool has_attribute_metadata(std::string_view attribute, Key \u0026\u0026 key) noexcept;\n\ntemplate\u003ctypename Ret, typename T, typename Key\u003e\nconstexpr const Ret * get_attribute_metadata(std::string_view attribute, Key \u0026\u0026 key) noexcept;\n\ntemplate\u003ctypename T, typename Key\u003e\nconstexpr bool has_method_metadata(std::string_view method, Key \u0026\u0026 key) noexcept;\n\ntemplate\u003ctypename Ret, typename T, typename Key\u003e\nconstexpr const Ret * get_method_metadata(std::string_view method, Key \u0026\u0026 key) noexcept;\n\ntemplate\u003ctypename ... Metadata, typename Key\u003e\nconstexpr bool has_metadata(const putils::table\u003cMetadata...\u003e \u0026 metadata, Key \u0026\u0026 key) noexcept;\n\ntemplate\u003ctypename Ret, typename ... Metadata, typename Key\u003e\nconstexpr const Ret * get_metadata(const putils::table\u003cMetadata...\u003e \u0026 metadata, Key \u0026\u0026 key) noexcept;\n```\n\n## API\n\nMaking a type reflectible consists in specializing the `putils::reflection::type_info` template with (up to) 5 static members that provide type information.\n\n```cpp\nnamespace putils::reflection {\n\ttemplate\u003ctypename T\u003e\n\tstruct type_info {\n\t\tstatic constexpr auto class_name = const char *;\n\t\tstatic constexpr auto attributes = std::tuple\u003cstd::pair\u003cconst char *, member_pointer\u003e...\u003e;\n\t\tstatic constexpr auto methods = std::tuple\u003cstd::pair\u003cconst char *, member_pointer\u003e...\u003e;\n\t\tstatic constexpr auto parents = std::tuple\u003cputils::meta::type\u003cparent\u003e...\u003e;\n\t\tstatic constexpr auto used_types = std::tuple\u003cputils::meta::type\u003cused_type\u003e...\u003e;\n\t};\n}\n```\n\nFor instance, for the `reflectible` struct given as an example above:\n\n```cpp\ntemplate\u003c\u003e\nstruct putils::reflection::type_info\u003creflectible\u003e {\n    static constexpr auto class_name = \"reflectible\";\n    static constexpr auto attributes = std::make_tuple(\n        std::make_pair(\"i\", \u0026reflectible::i)\n    );\n    static constexpr auto methods = std::make_tuple(\n        std::make_pair(\"get_value\", \u0026reflectible::get_value)\n    );\n    static constexpr auto parents = std::make_tuple(\n        putils::meta::type\u003cparent\u003e{}\n    );\n    static constexpr auto used_types = std::make_tuple(\n        putils::meta::type\u003cint\u003e{}\n    );\n};\n```\n\nThe `type_info` specialization can be easily defined through the use of helper macros, described below.\n\n### class_name\n\n```cpp\nstatic constexpr auto class_name = \"my_class\";\n```\nCan be easily generated with `putils_reflection_class_name`.\n\n### attributes\n\n```cpp\nstatic constexpr auto attributes = std::make_tuple(\n    std::make_pair(\"attribute\", \u0026MyClass::attribute),\n    ...\n);\n```\n[table](https://github.com/phisko/meta/blob/main/table.md) mapping strings to pointers to the attributes.\nCan be easily generated with `putils_reflection_attributes`.\n\n### methods\n\n```cpp\nstatic constexpr auto methods = std::make_tuple(\n    std::make_pair(\"method\", \u0026MyClass::method),\n    ...\n);\n```\n[table](https://github.com/phisko/meta/blob/main/table.md) mapping strings to pointers to the methods.\nCan be easily generated with `putils_reflection_methods`.\n\n### parents\n```cpp\nstatic constexpr auto parents = std::make_tuple(\n    putils::meta::type\u003cparent\u003e{},\n    ...\n);\n```\n`std::tuple` of `putils::meta::type` objects for each of the type's parents.\nCan be easily generated with `putils_reflection_parents`.\n\n### used_types\n```cpp\nstatic constexpr auto used_types = std::make_tuple(\n    putils::meta::type\u003cUsedType\u003e{},\n    ...\n);\n```\n`std::tuple` of `putils::meta::type` objects for each type used by the class (which should also be registered with scripting systems, for instance).\nCan be easily generated with `putils_reflection_used_types`.\n\n## Detector functions\n\nThe following functions are defined to let client code check whether a given type is reflectible.\n\n```cpp\nnamespace putils::reflection {\n    template\u003ctypename T\u003e\n    constexpr bool is_reflectible() noexcept; // Returns true if reflection info, even empty, was provided\n\n    template\u003ctypename T\u003e\n    constexpr bool has_class_name() noexcept;\n\n    template\u003ctypename T\u003e\n    constexpr bool has_attributes() noexcept;\n\n    template\u003ctypename T\u003e\n    constexpr bool has_methods() noexcept;\n\n    template\u003ctypename T\u003e\n    constexpr bool has_parents() noexcept;\n\n    template\u003ctypename T\u003e\n    constexpr bool has_used_types() noexcept;\n}\n```\n\n## Iterating attributes\n\nOnce a type is declared reflectible, iterating over any of its reflectible properties is made easy by the following helper functions. Note that calling these functions with a non-reflectible type is supported, and will do nothing.\n\n### for_each_attribute\n\n```cpp\nnamespace putils::reflection {\n    template\u003ctypename T, typename Func\u003e // Func: void(const attribute_info \u0026 attr)\n    void for_each_attribute(Func \u0026\u0026 func) noexcept;\n\n    template\u003ctypename T, typename Func\u003e // Func: void(const object_attribute_info \u0026 attr)\n    void for_each_attribute(T \u0026\u0026 obj, Func \u0026\u0026 func) noexcept;\n}\n```\n\nLets client code iterate over the attributes for a given type.\n\n### for_each_method\n\n```cpp\nnamespace putils::reflection {\n    template\u003ctypename T, typename Func\u003e // Func: void(const attribute_info \u0026 attr)\n    void for_each_method(Func \u0026\u0026 func) noexcept;\n\n    template\u003ctypename T, typename Func\u003e // Func: void(const object_attribute_info \u0026 attr)\n    void for_each_method(T \u0026\u0026 obj, Func \u0026\u0026 func) noexcept;\n}\n```\n\nLets client code iterate over the methods for a given type.\n\n### for_each_parent\n\n```cpp\nnamespace putils::reflection {\n    template\u003ctypename T, typename Func\u003e // Func: void(const used_type_info \u0026 attr)\n    void for_each_parent(Func \u0026\u0026 func) noexcept;\n}\n```\n\nLets client code iterate over the parents for a given type.\n\n### for_each_used_type\n\n```cpp\nnamespace putils::reflection {\n    template\u003ctypename T, typename Func\u003e // Func: void(const used_type_info \u0026 attr)\n    void for_each_used_type(Func \u0026\u0026 func) noexcept;\n}\n```\n\nLets client code iterate over the types used by a given type.\n\n## Querying attributes\n\n```cpp\ntemplate\u003ctypename T, typename Parent\u003e\nconstexpr bool has_parent();\n\ntemplate\u003ctypename T, typename Used\u003e\nconstexpr bool has_used_type();\n\ntemplate\u003ctypename T\u003e\nconstexpr bool has_attribute(std::string_view name);\n\ntemplate\u003ctypename T\u003e\nconstexpr bool has_method(std::string_view name);\n```\n\nReturns whether `T` has the specified parent, used type, attribute or method.\n\n## Getting specific attributes\n\n### get_attribute\n\n```cpp\ntemplate\u003ctypename Member, typename T\u003e\nstd::optional\u003cMember T::*\u003e get_attribute(std::string_view name) noexcept;\n\ntemplate\u003ctypename Member, typename T\u003e\nMember * get_attribute(T \u0026\u0026 obj, std::string_view name) noexcept;\n```\n\nReturns the attribute called `name` if there is one.\n* The first overload returns an `std::optional` member pointer (or `std::nullopt`)\n* The second overload returns a pointer to `obj`'s attribute (or `nullptr`)\n\n### get_method\n\n```cpp\ntemplate\u003ctypename Signature, typename T\u003e\nstd::optional\u003cSignature T::*\u003e get_method(std::string_view name) noexcept;\n\ntemplate\u003ctypename Signature, typename T\u003e\nstd::optional\u003cFunctor\u003e get_method(T \u0026\u0026 obj, std::string_view name) noexcept;\n```\n\nReturns the method called `name` if there is one.\n* The first overload returns an `std::optional` member pointer (or `std::nullopt`)\n* The second overload returns an `std::optional` functor which calls the method on `obj` (or `std::nullopt`)\n\n## Helper macros\n\nThe following macros can be used to greatly simplify defining the `putils::reflection::type_info` specialization for a type.\n\nThese macros expect a `refltype` macro to be defined for the given type:\n```cpp\n#define refltype ReflectibleType\n...\n#undef refltype\n```\n\n### putils_reflection_info\n\nDeclares a specialization of `putils::reflection::type_info` for `refltype`.\n\n### putils_reflection_info_template\n\nDeclares a specialization of `putils::reflection_type_info` for a template type, e.g.:\n```cpp\ntemplate\u003ctypename T\u003e\nstruct my_type {};\n\ntemplate\u003ctypename T\u003e\n#define refltype my_type\u003cT\u003e\nputils_reflection_info_template {\n    ...\n};\n#undef refltype\n```\n\n### putils_reflection_friend(type)\n\nUsed inside a reflectible type to mark the corresponding `type_info` as `friend`, in order to reflect private fields. Takes as parameter the name of the type.\n\n### putils_reflection_class_name\n\nDefines a `class_name` static string with `refltype` as its value.\n\n### putils_reflection_custom_class_name(name)\n\nDefines a `class_name` static string with the macro parameter as its value.\n\n### putils_reflection_attributes(attributes...)\n\nDefines an `attributes` static table of `std::pair\u003cconst char *, member_pointer\u003e`.\n\n### putils_reflection_methods(methods...)\n\nDefines a `methods` static table of `std::pair\u003cconst char *, member_pointer\u003e`.\n\n### putils_reflection_parents(parents...)\n\nDefines a `parents` static tuple of `putils::meta::type`.\n\n### putils_reflection_attribute(attributeName)\n\nTakes the name of an attribute as parameter and generates of pair of parameters under the form `\"var\", \u0026refltype::var` to avoid redundancy when passing parameters to `putils::make_table`. For instance:\n\n```cpp\nconst auto table = putils::make_table(\n    \"x\", \u0026point::x,\n    \"y\", \u0026point::y\n);\n```\n\ncan be refactored to:\n\n```cpp\n#define refltype point\nconst auto table = putils::make_table(\n    putils_reflection_attribute(x),\n    putils_reflection_attribute(y)\n);\n#undef refltype\n```\n\n### putils_reflection_attribute_private(member_ptr)\n\nProvides the same functionality as `putils_reflection_attribute`, but skips the first character of the attribute's name (such as an `_` or `m`) that would mark a private member. For instance:\n\n```cpp\nconst auto table = putils::make_table(\n    \"name\", \u0026human::_name,\n    \"age\", \u0026human::_age\n);\n```\n\ncan be refactored to:\n\n```cpp\n#define refltype human\nconst auto table = putils::make_table(\n    putils_reflection_attribute_private(_name),\n    putils_reflection_attribute_private(_age)\n);\n#undef refltype\n```\n\n### putils_reflection_type(name)\n\nProvides the same functionality as `putils_reflection_attribute`, but for types. It takes a type name as parameter and expands to `putils::meta::type\u003cclass_name\u003e{}` to avoid redundancy when passing parameters to `putils::make_table`.\n","funding_links":[],"categories":["C++"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphisko%2Freflection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphisko%2Freflection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphisko%2Freflection/lists"}