Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/qlibs/reflect
C++20 Static Reflection library
https://github.com/qlibs/reflect
cpp20 meta-programming reflection
Last synced: 1 day ago
JSON representation
C++20 Static Reflection library
- Host: GitHub
- URL: https://github.com/qlibs/reflect
- Owner: qlibs
- Created: 2024-02-01T10:12:12.000Z (12 months ago)
- Default Branch: main
- Last Pushed: 2024-10-07T12:38:03.000Z (4 months ago)
- Last Synced: 2024-10-30T01:37:05.248Z (3 months ago)
- Topics: cpp20, meta-programming, reflection
- Homepage:
- Size: 161 KB
- Stars: 300
- Watchers: 11
- Forks: 16
- Open Issues: 5
-
Metadata Files:
- Readme: .github/README.md
Awesome Lists containing this project
- awesome-modern-cpp - reflect
README
//
[Overview](#Overview) / [Examples](#Examples) / [API](#API) / [FAQ](#FAQ)## REFLECT: C++20 Static Reflection library
[![MIT Licence](http://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/license/mit)
[![Version](https://img.shields.io/github/v/release/qlibs/reflect)](https://github.com/qlibs/reflect/releases)
[![Build](https://img.shields.io/badge/build-green.svg)](https://godbolt.org/z/zvooxGPP9)
[![Try it online](https://img.shields.io/badge/try%20it-online-blue.svg)](https://godbolt.org/z/oYhh1hfeo)> https://en.wikipedia.org/wiki/Reflective_programming
### Features
- Single header (https://raw.githubusercontent.com/qlibs/reflect/main/reflect - for integration see [FAQ](#faq))
- Minimal [API](#api)
- Verifies itself upon include (can be disabled with `-DNTEST` - see [FAQ](#faq))
- Basically guarantees no UB, no memory leaks*
- Compiles cleanly with ([`-fno-exceptions -fno-rtti -Wall -Wextra -Werror -pedantic -pedantic-errors | /W4 /WX`](https://godbolt.org/z/M747ocGfx))
- Agnostic to compiler changes (no ifdefs for the compiler specific implementations - see [FAQ](#faq))
- Optimized run-time execution and binary size (see [performance](#perf))
- Fast compilation times (see [compilation times](#comp))### Requirements
- C++20 ([gcc-12+, clang-15+, msvc-19.36+](https://godbolt.org/z/xPc19Moef))
---
### Overview
> Hello world (https://godbolt.org/z/oYhh1hfeo)
```cpp
#includeenum E { A, B };
struct foo { int a; E b; };constexpr auto f = foo{.a = 42, .b = B};
// reflect::size
static_assert(2 == reflect::size(f));// reflect::type_id
static_assert(reflect::type_id(f.a) != reflect::type_id(f.b));// reflect::type_name
static_assert("foo"sv == reflect::type_name(f));
static_assert("int"sv == reflect::type_name(f.a));
static_assert("E"sv == reflect::type_name(f.b));// reflect::enum_name
static_assert("B"sv == reflect::enum_name(f.b));// reflect::member_name
static_assert("a"sv == reflect::member_name<0>(f));
static_assert("b"sv == reflect::member_name<1>(f));// reflect::get
static_assert(42 == reflect::get<0>(f)); // by index
static_assert(B == reflect::get<1>(f));static_assert(42 == reflect::get<"a">(f)); // by name
static_assert(B == reflect::get<"b">(f));// reflect::to
constexpr auto t = reflect::to(f);
static_assert(42 == std::get<0>(t));
static_assert(B == std::get<1>(t));int main() {
reflect::for_each([](auto I) {
std::print("{}.{}:{}={} ({}/{}/{})\n",
reflect::type_name(f), // foo, foo
reflect::member_name(f), // a , b
reflect::type_name(reflect::get(f)), // int, E
reflect::get(f), // 42 , B
reflect::size_of(f), // 4 , 4
reflect::align_of(f), // 4 , 4
reflect::offset_of(f)); // 0 , 4
}, f);
}// and more (see API)...
```---
### Examples
- [feature] Opt-in mixins - https://godbolt.org/z/sj7fYKoc3
- [feature] Meta-programming (https://github.com/qlibs/mp) - https://godbolt.org/z/ds3KMGhqP
- [future] Structured Bindings can introduce a Pack (https://wg21.link/P1061) - https://godbolt.org/z/Ga3bc3KKW
- [performance] Minimal Perfect Hashing based `enum_name` (https://github.com/qlibs/mph) - https://godbolt.org/z/WM155vTfv---
> Binary size (https://godbolt.org/z/7TbobjWfj)
```cpp
struct foo { int bar; };
auto type_name(const foo& f) { return reflect::type_name(f); }
``````asm
type_name(foo const&): // $CXX -O3 -DNDEBUG
lea rdx, [rip + type_name]
mov eax, 3
rettype_name
.ascii "foo"
``````cpp
struct foo { int bar; };
auto member_name(const foo& f) { return reflect::member_name<0>(f); }
``````asm
member_name(foo const&): // $CXX -O3 -DNDEBUG
lea rdx, [rip + member_name<0ul, foo>]
mov eax, 3
retmember_name<0ul, foo>
.ascii "bar"
``````cpp
enum class E { A, B, };
auto enum_name(const E e) { return reflect::enum_name(e); }
``````asm
enum_name(E): // $CXX -O3 -DNDEBUG (generates switch)
xor eax, eax
xor ecx, ecx
cmp edi, 1
sete cl
lea rdx, [rip + enum_name<0>]
cmove rax, rdx
test edi, edi
lea rdx, [rip + enum_name<1>]
cmovne rdx, rax
mov eax, 1
cmovne rax, rcx
retenum_name<0ul>:
.ascii "A"enum_name<1ul>:
.ascii "B"
```> [include] https://raw.githubusercontent.com/qlibs/reflect/main/reflect
```cpp
time g++-13.2 -x c++ -std=c++20 reflect -c -DNTEST # 0.113s
time g++-13.2 -x c++ -std=c++20 reflect -c # 0.253s
``````cpp
time clang++-17 -x c++ -std=c++20 reflect -c -DNTEST # 0.119s
time clang++-17 -x c++ -std=c++20 reflect -c # 0.322s
```---
### API
```cpp
template requires std::is_aggregate_v>
[[nodiscard]] constexpr auto visit(Fn&& fn, T&& t) noexcept;
``````cpp
struct foo { int a; int b; };
static_assert(2 == visit([](auto&&... args) { return sizeof...(args); }, foo{}));
``````cpp
template requires std::is_aggregate_v
[[nodiscard]] constexpr auto size() -> std::size_t;template requires std::is_aggregate_v
[[nodiscard]] constexpr auto size(const T&) -> std::size_t;
``````cpp
struct foo { int a; int b; } f;
static_assert(2 == size());
static_assert(2 == size(f));
``````cpp
template [[nodiscard]] constexpr auto type_name() noexcept;
template [[nodiscard]] constexpr auto type_name(const T&) noexcept;
``````cpp
struct foo { int a; int b; };
static_assert(std::string_view{"foo"} == type_name());
static_assert(std::string_view{"foo"} == type_name(foo{}));
``````cpp
template [[nodiscard]] constexpr auto type_id() noexcept;
template [[nodiscard]] constexpr auto type_id(T&&) noexcept;
``````cpp
struct foo { };
struct bar { };
static_assert(type_id(foo{}) == type_id(foo{}));
static_assert(type_id(bar{}) != type_id());
``````cpp
template
[[nodiscard]] constexpr auto to_underlying(const E e) noexcept;template requires std::is_enum_v
consteval auto enum_min(const E = {}) { return REFLECT_ENUM_MIN; }template requires std::is_enum_v
consteval auto enum_max(const E = {}) { return REFLECT_ENUM_MAX; }template
requires (std::is_enum_v and Max > Min)
[[nodiscard]] constexpr auto enum_name(const E e) noexcept -> std::string_view {
``````cpp
enum class Enum { foo = 1, bar = 2 };
static_assert(std::string_view{"foo"} == enum_name(Enum::foo));
static_assert(std::string_view{"bar"} == enum_name(Enum::bar));
``````cpp
enum class Enum { foo = 1, bar = 1024 };
consteval auto enum_min(Enum) { return Enum::foo; }
consteval auto enum_max(Enum) { return Enum::bar; }static_assert(std::string_view{"foo"} == enum_name(Enum::foo));
static_assert(std::string_view{"bar"} == enum_name(Enum::bar));
``````cpp
template
requires (std::is_aggregate_v and N < size())
[[nodiscard]] constexpr auto member_name(const T& = {}) noexcept;
``````cpp
struct foo { int a; int b; };
static_assert(std::string_view{"a"} == member_name<0, foo>());
static_assert(std::string_view{"a"} == member_name<0>(foo{}));
static_assert(std::string_view{"b"} == member_name<1, foo>());
static_assert(std::string_view{"b"} == member_name<1>(foo{}));
``````cpp
template
requires (std::is_aggregate_v> and
N < size>())
[[nodiscard]] constexpr decltype(auto) get(T&& t) noexcept;
``````cpp
struct foo { int a; bool b; };
constexpr auto f = foo{.i=42, .b=true};
static_assert(42 == get<0>(f));
static_assert(true == get<1>(f));
``````cpp
template requires std::is_aggregate_v
concept has_member_name = /*unspecified*/
``````cpp
struct foo { int a; int b; };
static_assert(has_member_name);
static_assert(has_member_name);
static_assert(not has_member_name);
``````cpp
template requires has_member_name
constexpr decltype(auto) get(T&& t) noexcept;
``````cpp
struct foo { int a; int b; };
constexpr auto f = foo{.i=42, .b=true};
static_assert(42 == get<"a">(f));
static_assert(true == get<"b">(f));
``````cpp
template
requires (std::is_aggregate_v and std::is_aggregate_v)
constexpr auto copy(const TSrc& src, TDst& dst) noexcept -> void;
``````cpp
struct foo { int a; int b; };
struct bar { int a{}; int b{}; };bar b{};
foo f{};copy(f, b);
assert(b.a == f.a);
assert(b.b == f.b);copy<"a">(f, b);
assert(b.a == f.a);
assert(0 == b.b);
``````cpp
template class R, class T>
requires std::is_aggregate_v>
[[nodiscard]] constexpr auto to(T&& t) noexcept;
``````cpp
struct foo { int a; int b; };constexpr auto t = to(foo{.a=4, .b=2});
static_assert(4 == std::get<0>(t));
static_assert(2 == std::get<1>(t));auto f = foo{.a=4, .b=2};
auto t = to(f);
std::get<0>(t) *= 10;
f.b = 42;
assert(40 == std::get<0>(t) and 40 == f.a);
assert(42 == std::get<1>(t) and 42 == f.b);
``````cpp
template
[[nodiscard]] constexpr auto to(T&& t);
``````cpp
struct foo { int a; int b; };
struct baz { int a{}; int c{}; };const auto b = to(foo{.a=4, .b=2});
assert(4 == b.a and 0 == b.c);
``````cpp
template requires std::is_aggregate_v
[[nodiscard]] constexpr auto size_of() -> std::size_t;template requires std::is_aggregate_v
[[nodiscard]] constexpr auto size_of(T&&) -> std::size_t;template requires std::is_aggregate_v
[[nodiscard]] constexpr auto align_of() -> std::size_t;template requires std::is_aggregate_v
[[nodiscard]] constexpr auto align_of(T&&) -> std::size_t;template requires std::is_aggregate_v
[[nodiscard]] constexpr auto offset_of() -> std::size_t;template requires std::is_aggregate_v
[[nodiscard]] constexpr auto offset_of(T&&) -> std::size_t;
``````cpp
struct foo { int a; bool b; };static_assert(4 == size_of<0, foo>());
static_assert(1 == size_of<1, foo>());
static_assert(4 == align_of<0, foo>());
static_assert(1 == align_of<1, foo>());
static_assert(0 == offset_of<0, foo>());
static_assert(4 == offset_of<1, foo>());
``````cpp
template
requires std::is_aggregate_v>
constexpr auto for_each(Fn&& fn) -> void;template
requires std::is_aggregate_v>
constexpr auto for_each(Fn&& fn, T&& t) -> void;
``````cpp
struct { int a; int b; } f;reflect::for_each([&f](const auto I) {
std::print("{}:{}={}", member_name(f), get(f)); // prints a:int=4, b:int=2
}, f);
```> Configuration
```cpp
#define REFLECT_ENUM_MIN 0 // Min size for enum name (can be overridden)
// For example: `-DREFLECT_ENUM_MIN=-1`
#define REFLECT_ENUM_MAX 128 // Max size for enum name (can be overridden)
// For example: `-DREFLECT_ENUM_MAX=32`
```---
### FAQ
- How does `reflect` compare to https://wg21.link/P2996?
> `reflect` library only provides basic reflection primitives, mostly via hacks and workarounds to deal with lack of the reflection.
https://wg21.link/P2996 is a language proposal with many more features and capabilities.- How does `reflect` work under the hood?
> There are many different ways to implement reflection. `reflect` uses C++20's structure bindings, concepts and source_location to do it. See `visit` implementation for more details.
- How can `reflect` be agnostic to compiler changes?
> `reflect` precomputes required prefixes/postfixes to find required names from the `source_location::function_name()` output for each compiler upon inclusion.
Any compiler change will end up with new prefixes/postfixes and wont require additional maintenance.- What does it mean that `reflect` tests itself upon include?
> `reflect` runs all tests (via static_asserts) upon include. If the include compiles it means all tests are passing and the library works correctly on given compiler, environment.
- What is compile-time overhead of `reflect` library?
> `reflect` include takes ~.2s (that includes running all tests).
The most expensive calls are `visit` and `enum_to_name` whose timing will depend on the number of reflected elements and/or min/max values provided.
There are no recursive template instantiations in the library.- Can I disable running tests at compile-time for faster compilation times?
> When `-DNTEST` is defined static_asserts tests wont be executed upon inclusion.
Note: Use with caution as disabling tests means that there are no guarantees upon inclusion that the given compiler/env combination works as expected.- How to extend the number of members to be reflected (default: 64)?
> Override `visit`, for example - https://godbolt.org/z/Ga3bc3KKW
```cpp
template // requires https://wg21.link/P1061
[[nodiscard]] constexpr decltype(auto) visit(Fn&& fn, T&& t) noexcept {
auto&& [... ts] = std::forward(t);
return std::forward(fn)(std::forward_like(ts)...);
}
```- How to integrate with [CMake.FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html)?
```
include(FetchContent)FetchContent_Declare(
qlibs.reflect
GIT_REPOSITORY https://github.com/qlibs/reflect
GIT_TAG v1.2.4
)FetchContent_MakeAvailable(qlibs.reflect)
``````
target_link_libraries(${PROJECT_NAME} PUBLIC qlibs.reflect);
```
- Similar projects?
> [boost.pfr](https://github.com/boostorg/pfr), [glaze](https://github.com/stephenberry/glaze), [reflect-cpp](https://github.com/getml/reflect-cpp), [magic_enum](https://github.com/Neargye/magic_enum)