qlibs / reflect

C++20 Static Reflection library
306 stars 16 forks source link
cpp20 meta-programming reflection

// <!-- // The MIT License (MIT) // // Copyright (c) 2024 Kris Jusiak kris@jusiak.net // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //

if 0

// --> Overview / Examples / API / FAQ

REFLECT: C++20 Static Reflection library

MIT Licence Version Build Try 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 };
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<std::tuple>(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<I>(f),             // a  , b
        reflect::type_name(reflect::get<I>(f)), // int, E
        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)

struct foo { int bar; };
auto type_name(const foo& f) { return reflect::type_name(f); }
type_name(foo const&): // $CXX -O3 -DNDEBUG
  lea rdx, [rip + type_name<foo>]
  mov eax, 3
  ret

type_name<foo>
  .ascii  "foo"
struct foo { int bar; };
auto member_name(const foo& f) { return reflect::member_name<0>(f); }
member_name(foo const&): // $CXX -O3 -DNDEBUG
  lea rdx, [rip + member_name<0ul, foo>]
  mov eax, 3
  ret

member_name<0ul, foo>
  .ascii  "bar"
enum class E { A, B, };
auto enum_name(const E e) { return reflect::enum_name(e); }
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

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 <class Fn, class T> requires std::is_aggregate_v<std::remove_cvref_t<T>>
[[nodiscard]] constexpr auto visit(Fn&& fn, T&& t) noexcept;
struct foo { int a; int b; };
static_assert(2 == visit([](auto&&... args) { return sizeof...(args); }, foo{}));
template<class T> requires std::is_aggregate_v<T>
[[nodiscard]] constexpr auto size() -> std::size_t;

template<class T> requires std::is_aggregate_v<T>
[[nodiscard]] constexpr auto size(const T&) -> std::size_t;
struct foo { int a; int b; } f;
static_assert(2 == size<foo>());
static_assert(2 == size(f));
template <class T> [[nodiscard]] constexpr auto type_name() noexcept;
template <class T> [[nodiscard]] constexpr auto type_name(const T&) noexcept;
struct foo { int a; int b; };
static_assert(std::string_view{"foo"} == type_name<foo>());
static_assert(std::string_view{"foo"} == type_name(foo{}));
template <class T> [[nodiscard]] constexpr auto type_id() noexcept;
template <class T> [[nodiscard]] constexpr auto type_id(T&&) noexcept;
struct foo { };
struct bar { };
static_assert(type_id(foo{}) == type_id(foo{}));
static_assert(type_id(bar{}) != type_id<foo>());
template<class E>
[[nodiscard]] constexpr auto to_underlying(const E e) noexcept;

template<class E> requires std::is_enum_v<E>
consteval auto enum_min(const E = {}) { return REFLECT_ENUM_MIN; }

template<class E> requires std::is_enum_v<E>
consteval auto enum_max(const E = {}) { return REFLECT_ENUM_MAX; }

template<class E,
         fixed_string unknown = "",
         auto Min = enum_min(E{}),
         auto Max = enum_max(E{})>
  requires (std::is_enum_v<E> and Max > Min)
[[nodiscard]] constexpr auto enum_name(const E e) noexcept -> std::string_view {
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));
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));
template <std::size_t N, class T>
  requires (std::is_aggregate_v<T> and N < size<T>())
[[nodiscard]] constexpr auto member_name(const T& = {}) noexcept;
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{}));
template<std::size_t N, class T>
  requires (std::is_aggregate_v<std::remove_cvref_t<T>> and
            N < size<std::remove_cvref_t<T>>())
[[nodiscard]] constexpr decltype(auto) get(T&& t) noexcept;
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));
template <class T, fixed_string Name> requires std::is_aggregate_v<T>
concept has_member_name = /*unspecified*/
struct foo { 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, class T> requires has_member_name<T, Name>
constexpr decltype(auto) get(T&& t) noexcept;
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));
template<fixed_string... Members, class TSrc, class TDst>
  requires (std::is_aggregate_v<TSrc> and std::is_aggregate_v<TDst>)
constexpr auto copy(const TSrc& src, TDst& dst) noexcept -> void;
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);
template<template<class...> class R, class T>
  requires std::is_aggregate_v<std::remove_cvref_t<T>>
[[nodiscard]] constexpr auto to(T&& t) noexcept;
struct foo { int a; int b; };

constexpr auto 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) and 40 == f.a);
assert(42 == std::get<1>(t) and 42 == f.b);
template<class R, class T>
[[nodiscard]] constexpr auto to(T&& t);
struct foo { int a; int b; };
struct baz { int a{}; int c{}; };

const auto b = to<baz>(foo{.a=4, .b=2});
assert(4 == b.a and 0 == b.c);
template<std::size_t N, class T> requires std::is_aggregate_v<T>
[[nodiscard]] constexpr auto size_of() -> std::size_t;

template<std::size_t N, class T> requires std::is_aggregate_v<T>
[[nodiscard]] constexpr auto size_of(T&&) -> std::size_t;

template<std::size_t N, class T> requires std::is_aggregate_v<T>
[[nodiscard]] constexpr auto align_of() -> std::size_t;

template<std::size_t N, class T> requires std::is_aggregate_v<T>
[[nodiscard]] constexpr auto align_of(T&&) -> std::size_t;

template<std::size_t N, class T> requires std::is_aggregate_v<T>
[[nodiscard]] constexpr auto offset_of() -> std::size_t;

template<std::size_t N, class T> requires std::is_aggregate_v<T>
[[nodiscard]] constexpr auto offset_of(T&&) -> std::size_t;
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>());
template<class T, class Fn>
  requires std::is_aggregate_v<std::remove_cvref_t<T>>
constexpr auto for_each(Fn&& fn) -> void;

template<class Fn, class T>
  requires std::is_aggregate_v<std::remove_cvref_t<T>>
constexpr auto for_each(Fn&& fn, T&& t) -> void;
struct { int a; int b; } f;

reflect::for_each([&f](const auto I) {
  std::print("{}:{}={}", member_name<I>(f), get<I>(f)); // prints a:int=4, b:int=2
}, f);

Configuration

#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