Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

315 Commits

Repository files navigation

// Overview / Examples / API / FAQ / Resources

reflect: C++20 Static Reflection library

MIT LicenceVersionBuildTry it online

https://en.wikipedia.org/wiki/Reflective_programming

Features

Requirements

Overview

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)...

Examples

Performance

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"

Compilation times

[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.253s
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

template <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`

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

    template <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

Resources

License

About

C++20 Static Reflection library

Topics

Resources

Stars

518 stars

Watchers

10 watching

Forks

Releases

Used by

Contributors

Languages