// Overview / Examples / API / FAQ / Resources
- Single header (https://raw.githubusercontent.com/qlibs/reflect/main/reflect) / C++20 module (https://github.com/qlibs/reflect/blob/main/reflect.cppm)
- Minimal API
- Verifies itself upon include (can be disabled with
-DNTEST- see FAQ)- Basically guarantees no UB, no memory leaks*
- Compiles cleanly with (
-fno-exceptions -fno-rtti -Wall -Wextra -Werror -pedantic -pedantic-errors | /W4 /WX) - Agnostic to compiler changes (no ifdefs for the compiler specific implementations - see FAQ)
- Optimized run-time execution and binary size (see performance)
- Fast compilation times (see compilation times)
- C++20 (gcc-12+, clang-15+, msvc-19.36+)
Hello world (https://godbolt.org/z/oYhh1hfeo)
#include<reflect>enum E { A, B };
structfoo { int a; E b; };
constexprauto f = foo{.a = 42, .b = B};
// reflect::sizestatic_assert(2 == reflect::size(f));
// reflect::type_idstatic_assert(reflect::type_id(f.a) != reflect::type_id(f.b));
// reflect::type_namestatic_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_namestatic_assert("B"sv == reflect::enum_name(f.b));
// reflect::member_namestatic_assert("a"sv == reflect::member_name<0>(f));
static_assert("b"sv == reflect::member_name<1>(f));
// reflect::getstatic_assert(42 == reflect::get<0>(f)); // by indexstatic_assert(B == reflect::get<1>(f));
static_assert(42 == reflect::get<"a">(f)); // by namestatic_assert(B == reflect::get<"b">(f));
// reflect::toconstexprauto t = reflect::to<std::tuple>(f);
static_assert(42 == std::get<0>(t));
static_assert(B == std::get<1>(t));
intmain() {
reflect::for_each([](auto I) {
std::println("{}.{}:{}={} ({}/{}/{})",
reflect::type_name(f), // foo, foo
reflect::member_name<I>(f), // a , breflect::type_name(reflect::get<I>(f)), // int, Eint(reflect::get<I>(f)), // 42 , B
reflect::size_of<I>(f), // 4 , 4
reflect::align_of<I>(f), // 4 , 4
reflect::offset_of<I>(f)); // 0 , 4
}, f);
}
// and more (see API)...- [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)
structfoo { int bar; };
autotype_name(const foo& f) { returnreflect::type_name(f); }type_name(foo const&): //$CXX -O3 -DNDEBUGleardx,[rip+ type_name<foo>]moveax,3rettype_name<foo> .ascii "foo"structfoo { int bar; };
automember_name(const foo& f) { return reflect::member_name<0>(f); }member_name(foo const&): //$CXX -O3 -DNDEBUGleardx,[rip+ member_name<0ul, foo>]moveax,3retmember_name<0ul, foo> .ascii "bar"enumclassE { A, B, };
autoenum_name(const E e) { returnreflect::enum_name(e); }enum_name(E): //$CXX -O3 -DNDEBUG (generates switch)xoreax,eaxxorecx,ecxcmpedi,1 sete clleardx,[rip+ enum_name<0>] cmove rax,rdxtestedi,edileardx,[rip+ enum_name<1>] cmovne rdx,raxmoveax,1 cmovne rax,rcxretenum_name<0ul>: .ascii "A"enum_name<1ul>: .ascii "B"[include] https://raw.githubusercontent.com/qlibs/reflect/main/reflect
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.253stime clang++-17 -x c++ -std=c++20 reflect -c -DNTEST # 0.119s
time clang++-17 -x c++ -std=c++20 reflect -c # 0.322stemplate <classFn, classT>
[[nodiscard]]constexprdecltype(auto) visit(Fn&& fn, T&& t);structfoo { int a; int b; };
static_assert(2 == visit([](auto&&... args) { returnsizeof...(args); }, foo{}));template <classT>
[[nodiscard]]constexprautosize() noexcept -> std::size_t;
template <classT>
[[nodiscard]]constexprautosize(const T&) noexcept -> std::size_t;structfoo { int a; int b; } f;
static_assert(2 == size<foo>());
static_assert(2 == size(f));template <classT>
[[nodiscard]]constexprautotype_name() noexcept -> std::string_view;
template <classT>
[[nodiscard]]constexprautotype_name(const T&) noexcept -> std::string_view;structfoo { int a; int b; };
static_assert(std::string_view{"foo"} == type_name<foo>());
static_assert(std::string_view{"foo"} == type_name(foo{}));template <classT>
[[nodiscard]]constexprautotype_id() noexcept;
template <classT>
[[nodiscard]]constexprautotype_id(const T&) noexcept;structfoo { };
structbar { };
static_assert(type_id(foo{}) == type_id(foo{}));
static_assert(type_id(bar{}) != type_id<foo>());template <classE>
[[nodiscard]]constexprautoto_underlying(E e) noexcept;
template <classE>
[[nodiscard]]constevalautoenum_min(E) { returnREFLECT_ENUM_MIN; }
template <classE>
[[nodiscard]]constevalautoenum_max(E) { returnREFLECT_ENUM_MAX; }
template <classE,
fixed_string unknown = "",
auto Min = enum_min(E{}),
auto Max = enum_max(E{})>
[[nodiscard]]constexprautoenum_name(E e) noexcept -> std::string_view;enumclassEnum { foo = 1, bar = 2 };
static_assert(std::string_view{"foo"} == enum_name(Enum::foo));
static_assert(std::string_view{"bar"} == enum_name(Enum::bar));enumclassEnum { foo = 1, bar = 1024 };
constevalautoenum_min(Enum) { return Enum::foo; }
constevalautoenum_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));template <std::size_t N, classT>
[[nodiscard]]constexprautomember_name() noexcept -> std::string_view;
template <std::size_t N, classT>
[[nodiscard]]constexprautomember_name(const T&) noexcept -> std::string_view;structfoo { 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{}));template <std::size_t N, classT>
[[nodiscard]]constexprdecltype(auto) get(T&& t) noexcept;structfoo { int i; bool b; };
constexprauto f = foo{.i=42, .b=true};
static_assert(42 == get<0>(f));
static_assert(true == get<1>(f));template <classT, fixed_string Name>
concept has_member_name = /*unspecified*/;structfoo { int a; int b; };
static_assert(has_member_name<foo, "a">);
static_assert(has_member_name<foo, "b">);
static_assert(not has_member_name<foo, "c">);template <fixed_string Name, classT>
[[nodiscard]]constexprautoindex_of() noexcept -> std::size_t;
template <fixed_string Name, classT>
[[nodiscard]]constexprautoindex_of(const T&) noexcept -> std::size_t;structfoo { int a; int b; };
static_assert(0 == index_of<"a", foo>());
static_assert(1 == index_of<"b">(foo{}));template <fixed_string Name, classT>
[[nodiscard]]constexprdecltype(auto) get(T&& t) noexcept;structfoo { int i; bool b; };
constexprauto f = foo{.i=42, .b=true};
static_assert(42 == get<"i">(f));
static_assert(true == get<"b">(f));template <fixed_string... Members, classTSrc, classTDst>
constexprautocopy(const TSrc& src, TDst& dst) -> void;structfoo { int a; int b; };
structbar { 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);template <template <class...> classR, classT>
[[nodiscard]]constexprautoto(T&& t);structfoo { int a; int b; };
constexprauto t = to<std::tuple>(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<std::tuple>(f);
std::get<0>(t) *= 10;
f.b = 42;
assert(40 == std::get<0>(t) and40 == f.a);
assert(42 == std::get<1>(t) and42 == f.b);template <classR, classT>
[[nodiscard]]constexprautoto(T&& t);structfoo { int a; int b; };
structbaz { int a{}; int c{}; };
constauto b = to<baz>(foo{.a=4, .b=2});
assert(4 == b.a and0 == b.c);template <std::size_t N, classT>
[[nodiscard]]constexprautosize_of() -> std::size_t;
template <std::size_t N, classT>
[[nodiscard]]constexprautosize_of(const T&) -> std::size_t;
template <fixed_string Name, classT>
[[nodiscard]]constexprautosize_of() -> std::size_t;
template <fixed_string Name, classT>
[[nodiscard]]constexprautosize_of(const T&) -> std::size_t;
template <std::size_t N, classT>
[[nodiscard]]constexprautoalign_of() -> std::size_t;
template <std::size_t N, classT>
[[nodiscard]]constexprautoalign_of(const T&) -> std::size_t;
template <fixed_string Name, classT>
[[nodiscard]]constexprautoalign_of() -> std::size_t;
template <fixed_string Name, classT>
[[nodiscard]]constexprautoalign_of(const T&) -> std::size_t;
template <std::size_t N, classT>
[[nodiscard]]constexprautooffset_of() -> std::size_t;
template <std::size_t N, classT>
[[nodiscard]]constexprautooffset_of(const T&) -> std::size_t;
template <fixed_string Name, classT>
[[nodiscard]]constexprautooffset_of() -> std::size_t;
template <fixed_string Name, classT>
[[nodiscard]]constexprautooffset_of(const T&) -> std::size_t;structfoo { int a; bool b; short c; };
static_assert(4 == size_of<0, foo>());
static_assert(1 == size_of<1, foo>());
static_assert(2 == size_of<"c", foo>());
static_assert(4 == align_of<0, foo>());
static_assert(1 == align_of<1, foo>());
static_assert(2 == align_of<"c", foo>());
static_assert(0 == offset_of<0, foo>());
static_assert(4 == offset_of<1, foo>());
static_assert(6 == offset_of<"c", foo>());template <classT, classFn>
constexprautofor_each(Fn&& fn) -> void;
template <classFn, classT>
constexprautofor_each(Fn&& fn, T&& t) -> void;struct { int a; int b; } f;
reflect::for_each([&f](constauto I) {
std::print("{}:{}={}", member_name<I>(f), get<I>(f)); // prints a:int=4, b:int=2
}, f);Configuration
#defineREFLECT_ENUM_MIN0// Min size for enum name (can be overridden)// For example: `-DREFLECT_ENUM_MIN=-1`
#defineREFLECT_ENUM_MAX128// Max size for enum name (can be overridden)// For example: `-DREFLECT_ENUM_MAX=32`
How does
reflectcompare to https://wg21.link/P2996?
reflectlibrary 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
reflectwork under the hood?There are many different ways to implement reflection.
reflectuses C++20's structure bindings, concepts and source_location to do it. Seevisitimplementation for more details.How can
reflectbe agnostic to compiler changes?
reflectprecomputes required prefixes/postfixes to find required names from thesource_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
reflecttests itself upon include?
reflectruns 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
reflectlibrary?
reflectinclude takes ~.2s (that includes running all tests). The most expensive calls arevisitandenum_to_namewhose 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
-DNTESTis 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/Ga3bc3KKWtemplate <classFn, classT> // requires https://wg21.link/P1061[[nodiscard]]constexprdecltype(auto) visit(Fn&& fn, T&& t) { auto&& [... ts] = std::forward<T>(t); return std::forward<Fn>(fn)(std::forward_like<T>(ts)...); }Similar projects? boost.pfr, glaze, reflect-cpp, magic_enum
- Reflection for C++26 - https://wg21.link/P2996
- Static Reflection - https://wg21.link/P0385