- Single header (https://raw.githubusercontent.com/boost-ext/reflect/main/reflect)
- Minimal API
- Verifies itself upon include (aka run all tests via static_asserts)
- Compiler changes agnostic (no ifdefs for the compiler specific implementations)
- C++20 (gcc-12+, clang-15+, msvc-19.36+)
Hello world (https://godbolt.org/z/7c4hhqf8v)
#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<foo>);
// 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
reflect::for_each([](const auto& member) {
std::print("{}.{}:{}={}\n",
reflect::type_name(f), // --- output ---
member.name, // foo.a:int=42
member.type, // foo.b:E=B
member.value); // --------------
}, f);
}
// and more (see 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>
inline constexpr auto size = /*unspecified*/
struct foo { int a; int b; };
static_assert(2 == size<foo>);
template <class T>
requires std::is_aggregate_v<std::remove_cvref_t<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 <std::size_t Min = REFLECT_ENUM_MIN, std::size_t Max = REFLECT_ENUM_MAX, class E>
requires (std::is_enum_v<E> and Max > Min)
[[nodiscard]] constexpr auto enum_name(const E e = {}) noexcept;
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));
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<class T>
struct member {
std::size_t size_of;
std::size_t alignment_of;
std::size_t offset_of;
std::string_view name;
std::string_view type;
T value;
};
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 foo { int a; int b; };
reflect::for_each([](const auto& member) {
std::print("{}:{}={}", member.name, member.type, member.value); // prints a:int=4, b:int=2
}, foo{.a=4, .b=2});
template <class T, std::size_t Size> struct fixed_string;
static_assert(0u == std::size(fixed_string{""}));
static_assert(fixed_string{""} == fixed_string{""});
static_assert(std::string_view{""} == std::string_view{fixed_string{""}});
static_assert(3u == std::size(fixed_string{"foo"}));
static_assert(std::string_view{"foo"} == std::string_view{fixed_string{"foo"}});
static_assert(fixed_string{"foo"} == fixed_string{"foo"});
constexpr auto debug(auto&&...) -> void; // [debug facility] shows types at compile time
debug(foo{}); // compile-time error: debug(foo) is not defined
Configuration
#define REFLECT 1'0'3 // Current library version (SemVer)
#define REFLECT_ENUM_MIN 0 // Min size for enum name
#define REFLECT_ENUM_MAX 64 // Max size for enum name
-
How
reflect
compares to https://wg21.link/P2996?reflect
library only provides basic reflection primitvies, mostly via hacks and workarounds to deal with lack of the reflection. https://wg21.link/P2996 is a language proprosal with a lot of more features and capabilities. It's like comparing a drop in the ocean to the entire sea! -
How
reflect
works under the hood?There are a many different ways to implement reflection.
reflect
uses C++20's structure bindings, concepts and source_location to do it. Seevisit
implementation for more details. -
How
reflect
can be compiler changes agnostic?reflect
precomputes 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 won't require additional maintanace. -
What does it mean that
reflect
tests itself upon include?reflect
runs all tests (via static_asserts) upon include. If the include compiled it means all tests are passing and the library works correctly on given compiler, enviornment. -
What is compile-time overhead of
reflect
library?reflect
include takes < 20ms (that includes running all tests). The most expensive calls arevisit
andenum_to_name
which timing will depend on the number of reflected elements and/or min/max values provided. There are no recursive template instantiations in the library. -
How to extend number of members to be reflected (default: 64)?
Override
visit
, for example - https://godbolt.org/z/7P7Kfe6Yrtemplate <class Fn, class T> // requires https://wg21.link/P1061 [[nodiscard]] constexpr decltype(auto) visit(Fn&& fn, T&& t) noexcept { auto&& [... ts] = std::forward<T>(t); return std::forward<Fn>(fn)(std::forward<decltype(ts)>(ts)...); }
-
How to integrate with CMake/CPM?
CPMAddPackage( Name reflect GITHUB_REPOSITORY boost-ext/reflect GIT_TAG v1.0.3 ) add_library(reflect INTERFACE) target_include_directories(reflect SYSTEM INTERFACE ${reflect_SOURCE_DIR}) add_library(reflect::reflect ALIAS reflect)
target_link_libraries(${PROJECT_NAME} reflect::reflect);
-
Similar projects?
Disclaimer reflect
is not an official Boost library.