An open API service indexing awesome lists of open source software.

https://github.com/yosh-matsuda/field-reflection

Compile-time reflection for C++ to get field names and types from a struct/class.
https://github.com/yosh-matsuda/field-reflection

cpp cpp20 header-only reflection static-reflection

Last synced: 25 days ago
JSON representation

Compile-time reflection for C++ to get field names and types from a struct/class.

Awesome Lists containing this project

README

          

# field-reflection C++

Compile-time reflection for C++ to get field names and types from a struct/class.

[![CI](https://github.com/yosh-matsuda/field-reflection/actions/workflows/tests.yml/badge.svg)](https://github.com/yosh-matsuda/field-reflection/actions/workflows/tests.yml)

## Features

* compile-time reflection
* header-only single file
* no user-side macros
* no dependencies

## Requirements

C++20 compilers are required to use this library.

* GCC >= 11
* Clang >= 15
* with libc++-16 or later
* MSVC >= 19.37
* clang-cl >= 17

## Usage

```cpp
#include
#include
#include
#include
#include
#include "field_reflection.hpp"

using namespace field_reflection;

struct my_struct
{
int i = 287;
double d = 3.14;
std::string hello = "Hello World";
std::array arr = {1, 2, 3};
std::map map{{"one", 1}, {"two", 2}};
};

// get field names
constexpr auto my_struct_n0 = field_name; // "i"sv
constexpr auto my_struct_n1 = field_name; // "d"sv
constexpr auto my_struct_n2 = field_name; // "hello"sv
constexpr auto my_struct_n3 = field_name; // "arr"sv
constexpr auto my_struct_n4 = field_name; // "map"sv

// get field types
using my_struct_t0 = field_type; // int
using my_struct_t1 = field_type; // double
using my_struct_t2 = field_type; // std::string
using my_struct_t3 = field_type; // std::array
using my_struct_t4 = field_type; // std::map

// get field values with index
auto s = my_struct{};
auto& my_struct_v0 = get_field<0>(s); // s.i
auto& my_struct_v1 = get_field<1>(s); // s.d
auto& my_struct_v2 = get_field<2>(s); // s.hello
auto& my_struct_v3 = get_field<3>(s); // s.arr
auto& my_struct_v4 = get_field<4>(s); // s.map

// visit each field
for_each_field(s, [](std::string_view field, auto& value) {
// i: 287
// d: 3.14
// hello: Hello World
// arr: [1, 2, 3]
// map: {"one": 1, "two": 2}
std::println("{}: {}", field, value);
});
```

## API References

### Concepts

```cpp
template
concept field_countable;
template
concept field_referenceable;
template
concept field_namable;
```

The `field_countable` is a concept that checks if the type `T` is a field-countable struct. Internally, it is equivalent to that `T` is [aggregate type](https://en.cppreference.com/w/cpp/types/is_aggregate) and the number of the field is less than or equal to `100`.

The `field_referenceable` is a concept that checks if a field of the type `T` can be referenced by index. This includes the `field_countable` concept. The implementation of the `field_referenceable` concept is the condition that the `field_countable` type `T` has no base class.

The `field_namable` is a concept that checks if a field name of the type `T` can be obtained by index statically. This includes the `field_referenceable` concept and also requires that the type `T` has a field and (practically) there is no reference type member.

### `field_count`

```cpp
template
constexpr std::size_t field_count;
```

Get the number of fields from the `field_countable` type `T`.

### `field_name`

```cpp
template
constexpr std::string_view field_name;
```

Get the name of the `N`-th field as `std::string_view` from the `field_namable` type `T`.

### `field_type`

```cpp
template
using field_type;
```

Get the type of the `N`-th field from the `field_referenceable` type `T`.

### `get_field`

```cpp
// reference
template
constexpr auto& get_field(T& t) noexcept;

// const reference
template
constexpr const auto& get_field(const T& t) noexcept;

// rvalue reference
template
constexpr auto get_field(T&& t) noexcept;
```

Extracts the `N`-th element from the `field_referenceable` type `T` and returns a reference to it. It behaves like `std::get` for `std::tuple` but returns a lvalue value instead of a rvalue reference.

### `type_name`

```cpp
template
constexpr std::string_view type_name;
```

Get the name of the type `T`.

Example

```cpp
#include
#include
#include
#include // std::exchange
#include

using Token = std::variant;

struct Number {
int value;
};

struct Identifier {
std::string name;
};

template
inline constexpr bool alternative_of = false;

template
inline constexpr bool alternative_of> =
(std::is_same_v || ...);

template
requires alternative_of
struct std::formatter {
constexpr auto parse(auto& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
if (it != ctx.end() and *it != '}') {
throw std::format_error("invalid format");
}
return it;
}

auto format(const T& t, auto& ctx) const -> decltype(ctx.out()) {
auto out = ctx.out();
out = std::format_to(out, "{} {{", field_reflection::type_name);
const char* dlm = "";
field_reflection::for_each_field(
t, [&](std::string_view name, const auto& value) {
std::format_to(
out, "{}\n .{}={}", std::exchange(dlm, ","), name, value);
});
out = std::format_to(out, "\n}}");
return out;
}
};

#include

int main() {
Number num{42};
Identifier ident{"ident"};
std::cout << std::format("{}", num) << std::endl;
std::cout << std::format("{}", ident) << std::endl;
// Expected Output
// ===============
// Number {
// .value=42
// }
// Identifier {
// .name=ident
// }
}
```

🔗[Execution example in Compiler Explorer](https://godbolt.org/z/94fPc895o)

### `for_each_field`, `all_of_field`, `any_of_field`

```cpp
// unary operation
template
void for_each_field(T&& t, Func&& func);
template
bool all_of_field(T&& t, Func&& func);
template
bool any_of_field(T&& t, Func&& func);

// binary operation
template
void for_each_field(T&& t1, T&& t2, Func&& func);
template
bool all_of_field(T&& t1, T&& t2, Func&& func);
template
bool any_of_field(T&& t1, T&& t2, Func&& func);
```

Visits each field of the type `T` and applies the unary or binary operation `func`. The `func` must be a callable object that takes one of the following kinds of arguments:

* Arguments of one or two references to the field for the `field_referenceable` type `T`.
* Arguments of `std::string_view` and one or two references to the field for the `field_namable` type `T`.

The `for_each_field` just applies the `func` and returns `void`, while the `all_of_field` and `any_of_field` return `bool` indicating whether all or any of the `func` returns `true`.

For example, the following code prints the field names and values of the `my_struct` `s`:

```cpp
constexpr auto func = [](std::string_view field, auto& value) {
std::println("{}: {}", field, value);
};
for_each_field(s, func);
```

The above is equivalent to:

```cpp
func("i"sv, s.i);
func("d"sv, s.d);
func("hello"sv, s.hello);
func("arr"sv, s.arr);
func("map"sv, s.map);
```

The first argument in the definition of the `func` can be omitted if it is not needed.

The binary operation version of `for_each_field` is useful for comparing each field of two objects of the same type:

```cpp
constexpr auto func = [](std::string_view field, auto& value1, auto& value2) {
if (value1 != value2) {
std::println("s1 and s2 have a different value: s1.{} = {}, s2.{} = {}",
field, value1, field, value2);
}
};
for_each_field(s1, s2, func);
```

### `to_tuple`

```cpp
template
constexpr std::tuple<...> to_tuple(T&& t);
```

Copy a `field_referenceable` type `T` object and convert it to `std::tuple` where each field has the same type as `T`. For example, `my_struct` object can be converted to object of type `std::tuple, std::map>`.

## Acknowledgments

This project is strongly inspired by the following and stands as

* an alternative to [visit_struct](https://github.com/cbeck88/visit_struct) without macros,
* a reflection library that is a partial reimplementation of [reflect-cpp](https://github.com/getml/reflect-cpp).

The C++20 implementation of the counting field in this library is partially referenced to [Boost.PFR](https://github.com/boostorg/pfr).