// [Overview](#Overview) / [Examples](#Examples) / [API](#API) / [FAQ](#FAQ) / [Resources](#Resources) ## `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) / C++20 module (https://github.com/qlibs/reflect/blob/main/reflect.cppm) - 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 #include enum 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::println("{}.{}:{}={} ({}/{}/{})", reflect::type_name(f), // foo, foo reflect::member_name(f), // a , b reflect::type_name(reflect::get(f)), // int, E int(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 ### Performance > 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 ret type_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 ret member_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 ret enum_name<0ul>: .ascii "A" enum_name<1ul>: .ascii "B" ``` ### Compilation times > [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 [[nodiscard]] constexpr decltype(auto) visit(Fn&& fn, T&& t); ``` ```cpp struct foo { int a; int b; }; static_assert(2 == visit([](auto&&... args) { return sizeof...(args); }, foo{})); ``` ```cpp template [[nodiscard]] constexpr auto size() noexcept -> std::size_t; template [[nodiscard]] constexpr auto size(const T&) noexcept -> 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 -> std::string_view; template [[nodiscard]] constexpr auto type_name(const T&) noexcept -> std::string_view; ``` ```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(const 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(E e) noexcept; template [[nodiscard]] consteval auto enum_min(E) { return REFLECT_ENUM_MIN; } template [[nodiscard]] consteval auto enum_max(E) { return REFLECT_ENUM_MAX; } template [[nodiscard]] constexpr auto enum_name(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 [[nodiscard]] constexpr auto member_name() noexcept -> std::string_view; template [[nodiscard]] constexpr auto member_name(const T&) noexcept -> std::string_view; ``` ```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 [[nodiscard]] constexpr decltype(auto) get(T&& t) noexcept; ``` ```cpp struct foo { int i; bool b; }; constexpr auto f = foo{.i=42, .b=true}; static_assert(42 == get<0>(f)); static_assert(true == get<1>(f)); ``` ```cpp template 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 [[nodiscard]] constexpr auto index_of() noexcept -> std::size_t; template [[nodiscard]] constexpr auto index_of(const T&) noexcept -> std::size_t; ``` ```cpp struct foo { int a; int b; }; static_assert(0 == index_of<"a", foo>()); static_assert(1 == index_of<"b">(foo{})); ``` ```cpp template [[nodiscard]] constexpr decltype(auto) get(T&& t) noexcept; ``` ```cpp struct foo { int i; bool b; }; constexpr auto f = foo{.i=42, .b=true}; static_assert(42 == get<"i">(f)); static_assert(true == get<"b">(f)); ``` ```cpp template constexpr auto copy(const TSrc& src, TDst& dst) -> 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