Commit 46cbef17 authored by bbguimaraes's avatar bbguimaraes
Browse files

reflection

parent 76641895
......@@ -10,6 +10,7 @@ SUBDIRS = \
list \
packet_reader \
popcount \
reflection \
time \
yaml
......
......@@ -18,5 +18,7 @@ Repository for code experiments. Mostly C/++.
- [`list`](./list): linked lists.
- [`packet_reader`](./packet_reader): structured data packet reader.
- [`popcount`](./popcount): set bit counter.
- [`reflection`](./reflection): basic structure reflection using C++17
structured bindings.
- [`time`](./time): simple time measurement and reporting program.
- [`yaml`](./yaml): YAML converter utility.
......@@ -40,6 +40,7 @@ AC_CONFIG_FILES([
list/Makefile
packet_reader/Makefile
popcount/Makefile
reflection/Makefile
time/Makefile
yaml/Makefile])
AC_OUTPUT
AUTOMAKE_OPTIONS = subdir-objects
AM_CPPFLAGS = -I@srcdir@/..
AM_CXXFLAGS = -std=c++2a -O2 -Wall -Wextra -pedantic
TESTS = $(check_PROGRAMS)
if ENABLE_TESTS
check_PROGRAMS = soa
endif
noinst_HEADERS = \
detail/fields.hpp \
detail/soa.hpp \
fields.hpp \
soa.hpp \
utils.hpp
check_CFLAGS = -fPIC -fsanitize=address,undefined
check_CXXFLAGS = $(check_CFLAGS)
soa_CXXFLAGS = $(AM_CXXFLAGS) $(TEST_DEPS_CFLAGS) $(check_CXXFLAGS)
soa_LDFLAGS = $(AM_LDFLAGS) $(TEST_DEPS_LIBS)
soa_SOURCES = \
detail/fields.cpp \
detail/soa.cpp \
fields.cpp \
soa.cpp \
utils.cpp
reflection
==========
Basic reflection of structures using C++17 structured bindings. Similar in
spirit to `magic_get`/[Boost.PFR](https://boost.org/libs/pfr) but a completely
independent (and simplified) implementation.
```cpp
namespace refl = codex::refl;
struct E { float p, v, a; const char *n; };
// n.b. no other declarations necessary
static_assert(refl::field_count<E>() == 4);
using T = refl::field_tuple_t<E>;
static_assert(std::tuple_size_v<T> == 4);
static_assert(std::same_as<std::tuple_element_t<0, T>, float&>);
static_assert(std::same_as<std::tuple_element_t<1, T>, float&>);
static_assert(std::same_as<std::tuple_element_t<2, T>, float&>);
static_assert(std::same_as<std::tuple_element_t<3, T>, const char*&>);
void f(E *e) {
auto t = refl::field_tuple(*e);
std::apply([](const auto &...x) { (..., (std::cout << x << '\n')); }, t);
std::get<1>(t) = 42.0f;
}
```
SOA
---
Container with an interface similar to `std::vector` but where each field of the
structure is transparently stored in its own contiguous array (i.e. a
structure-of-arrays, instead of an array-of-structures).
```cpp
refl::SOA<E> v = {};
// Insert using "aggregate constructor".
v.push_back({.p = 0, .v = 1, .a = 2, .n = "e0"});
// Insert default, set values using indexed "aggregate constructor".
v.push_back();
v.set(1, {.p = 3, .v = 4, .a = 5, .n = "e1"});
// Interface similar to std::vector.
std::printf("size: %zu\n", v.size());
const auto e0 = v[0];
std::printf("e0: %g %g %g %s\n", e0.p, e0.v, e0.a, e0.n);
// Access to contiguous field storage.
const auto f1 = v.field<1>();
static_assert(std::ranges::contiguous_range<decltype(f1)>);
std::printf("\nv:");
for(const auto &x : f1)
std::printf(" %g", x);
```
A second template parameter can be used to specify the underlying storage
implementation for each field to another contiguous container (the default is
`std::vector<T>`). Its `for_field` member is instantiated with the index and
type of each field.
```cpp
struct descriptor {
template<std::size_t, typename T>
using for_field = boost::container::static_vector<T, 1024>;
};
using SOA = refl::SOA<E, descriptor>;
```
#include "fields.hpp"
#ifndef CODEX_REFLECTION_FIELDS_DETAIL_HPP
#define CODEX_REFLECTION_FIELDS_DETAIL_HPP
#include <cstdio>
#include <tuple>
#include <utility>
namespace codex::refl::detail {
/**
* Structure which can be ostensibly converted to any other.
* Used in the implementation of \ref codex::refl::field_count, where it is
* placed in an aggregate construction such as `T{to_any<…>{}...}`. The
* conversion operation, much like `std::declval`, is purely declarative and
* never implemented.
*/
template<auto>
struct to_any {
template<typename T> constexpr operator T(void);
};
/**
* Implementation of creating a `std::tuple` from the fields of a `struct`.
* Each explicit specialization, generated with the macro below, binds the
* fields of a `struct` with \p N fields.
*/
template<std::size_t N> struct field_tuple_impl;
// Could potentially be implemented in a more concise manner with auxiliary
// infrastructure such as Boost.Preprocessor.
#define X(n, ...) \
template<> \
struct field_tuple_impl<n> { \
decltype(auto) operator()(auto &&x) { \
auto &&[__VA_ARGS__] = std::forward<decltype(x)>(x); \
return std::tie(__VA_ARGS__); \
} \
};
X(1, _0)
X(2, _0, _1)
X(3, _0, _1, _2)
X(4, _0, _1, _2, _3)
#undef X
}
#endif
#include "soa.hpp"
#ifndef CODEX_REFLECTION_SOA_DETAIL_HPP
#define CODEX_REFLECTION_SOA_DETAIL_HPP
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <vector>
#include "../fields.hpp"
#include "utils.hpp"
namespace codex::refl::detail {
/**
* Ultimate class for the storage of each field.
* \tparam S The storage descriptor.
* \tparam T The type of the field.
* \tparam I Index of the type in the containing structure.
*/
template<typename S, std::size_t I, typename T>
class field_storage : public S::for_field<I, T> {
protected:
static constexpr auto index = I;
};
template<typename S, typename Is, typename Ts> struct storage_impl;
/**
* Storage for all fields of a structure.
* \tparam S Storage descriptor.
* \tparam Is Indices for each field of the structure.
* \tparam Ts Types for each field of the structure.
* \see storage
*/
template<typename S, std::size_t ...Is, typename ...Ts>
class storage_impl<S, std::index_sequence<Is...>, std::tuple<Ts...>>
: public field_storage<S, Is, std::decay_t<Ts>>...
{
private:
/** Tuple with (value/decayed) types of each field. */
using tuple_type = std::tuple<std::decay_t<Ts>...>;
/** (Value/decayed) type of field with index \p I. */
template<std::size_t I>
using value_type = std::tuple_element_t<I, tuple_type>;
/**
* Storage type of field with index \p I.
* Used to cast this object to one of its bases to get access to the field
* storage.
*/
template<std::size_t I>
using field_storage_type = field_storage<S, I, std::decay_t<value_type<I>>>;
public:
/** Storage for field with index \p I. */
template<std::size_t I>
auto field(void) -> field_storage_type<I>& { return *this; }
template<std::size_t I>
auto field(void) const -> const field_storage_type<I>& { return *this; }
/**
* Applies \p f to each field storage.
* \param f
* Callable which takes a field storage (i.e. \ref field_storage
* instantiated with <tt><I, T></tt>) as a parameter. The argument type
* can be used to detect the field type and index.
*/
void for_each(auto &&f) { (..., CODEX_FWD(f)(this->field<Is>())); }
void for_each(auto &&f) const { (..., CODEX_FWD(f)(this->field<Is>())); }
/**
* Applies \p f to each field of the element with index \p i.
* \param f
* Callable which takes two parameters: an \ref index_constant
* containing the field index and a (mutable) reference to the field
* value in its corresponding storage.
*/
void for_each_i(std::size_t i, auto &&f);
void for_each_i(std::size_t i, auto &&f) const;
};
template<typename S, std::size_t ...Is, typename ...Ts>
void storage_impl<
S, std::index_sequence<Is...>, std::tuple<Ts...>
>::for_each_i(std::size_t i, auto &&f) {
return this->for_each([i, &f]<typename V>(V &v) {
CODEX_FWD(f)(index_constant<V::index>{}, v[i]);
});
}
template<typename S, std::size_t ...Is, typename ...Ts>
void storage_impl<
S, std::index_sequence<Is...>, std::tuple<Ts...>
>::for_each_i(std::size_t i, auto &&f) const {
return this->for_each([i, &f]<typename V>(V &v) {
CODEX_FWD(f)(index_constant<V::index>{}, v[i]);
});
}
/**
* Storage for all fields of a structure.
* Convenient interface to instantiate \ref storage_impl for a type \p T and
* storage descriptor \p S.
*/
template<typename T, typename S>
using storage = storage_impl<
S, std::make_index_sequence<refl::field_count<T>()>,
refl::field_tuple_t<T>>;
/** Default storage descriptor. Stores field values in `std::vector`s. */
struct storage_descriptor {
template<std::size_t, typename T> using for_field = std::vector<T>;
};
}
#endif
#include "fields.hpp"
#ifndef CODEX_REFLECTION_FIELDS_HPP
#define CODEX_REFLECTION_FIELDS_HPP
#include "detail/fields.hpp"
namespace codex::refl {
/**
* Number of fields in the `struct` \p T.
* This is the specialization that is always instantiable. It is the converse
* of the `requires` clause in the other member of the overload set, i.e.
* `struct` \p T cannot be constructed with \p N fields. Because the search is
* done linearly from zero, the structure must have `N - 1` fields.
*/
template<typename T, std::size_t ...N>
constexpr std::size_t field_count(void) {
return sizeof...(N) - 1;
}
/**
* Number of fields in the `struct` \p T.
* Instantiable if it is possible to construct `struct` \p T with \p N fields.
* \ref codex::refl::detail::to_any is used so that the types of the fields are
* disregarded. The `requires` clause will hold for values of \p N from zero to
* the number of fields in \p T.
*/
template<typename T, std::size_t ...N>
requires(requires { T{detail::to_any<N>{}...}; })
constexpr std::size_t field_count(void) {
return field_count<T, N..., sizeof...(N)>();
}
/** Constructs a tuple of references to each field of \p T, in order. */
template<typename T>
auto field_tuple(T &&t) {
constexpr auto n = field_count<std::decay_t<T>>();
return detail::field_tuple_impl<n>{}(std::forward<T>(t));
}
/** Alias of the tuple type returned by \ref field_tuple for type \p T. */
template<typename T>
using field_tuple_t = decltype(field_tuple(std::declval<T>()));
}
#endif
#include <iostream>
#include <ranges>
#include "soa.hpp"
struct E { float p, v, a; const char *n; };
using SOA = codex::refl::SOA<E>;
using T = codex::refl::field_tuple_t<E>;
static_assert(std::same_as<T, decltype(codex::refl::field_tuple(E{}))>);
static_assert(codex::refl::field_count<E>() == 4);
static_assert(std::tuple_size_v<T> == 4);
static_assert(std::same_as<std::tuple_element_t<0, T>, float&>);
static_assert(std::same_as<std::tuple_element_t<1, T>, float&>);
static_assert(std::same_as<std::tuple_element_t<2, T>, float&>);
static_assert(std::same_as<std::tuple_element_t<3, T>, const char*&>);
static_assert(std::is_same_v<SOA::field_type<0>, float>);
static_assert(std::is_same_v<SOA::field_type<1>, float>);
static_assert(std::is_same_v<SOA::field_type<2>, float>);
static_assert(std::is_same_v<SOA::field_type<3>, const char*>);
static_assert(std::ranges::contiguous_range<decltype(SOA{}.field<0>())>);
static_assert(std::ranges::contiguous_range<decltype(SOA{}.field<1>())>);
static_assert(std::ranges::contiguous_range<decltype(SOA{}.field<2>())>);
static_assert(std::ranges::contiguous_range<decltype(SOA{}.field<3>())>);
void f(E *e) {
auto t = codex::refl::field_tuple(*e);
std::apply([](const auto &...x) { (..., (std::cout << x << '\n')); }, t);
std::get<1>(t) = 42.0f;
}
int main(void) {
SOA v = {};
v.push_back({.p = 0, .v = 1, .a = 2, .n = "e0"});
v.push_back({.p = 3, .v = 4, .a = 5, .n = "e1"});
v.push_back({.p = 6, .v = 7, .a = 8, .n = "e2"});
v.push_back();
v.set(3, {.p = 9, .v = 10, .a = 11, .n = "e3"});
std::printf("size: %zu\n", v.size());
const auto e0 = v[0];
std::printf("e0: %g %g %g %s\n", e0.p, e0.v, e0.a, e0.n);
const auto f0 = v.field<0>();
const auto f1 = v.field<1>();
const auto f2 = v.field<2>();
const auto f3 = v.field<3>();
std::printf("p:");
for(const auto &x : f0)
std::printf(" %g", x);
std::printf("\nv:");
for(const auto &x : f1)
std::printf(" %g", x);
std::printf("\na:");
for(const auto &x : f2)
std::printf(" %g", x);
std::printf("\nn:");
for(const auto &x : f3)
std::printf(" %s", x);
}
#ifndef CODEX_REFLECTION_SOA_HPP
#define CODEX_REFLECTION_SOA_HPP
#include <span>
#include "detail/soa.hpp"
#include "fields.hpp"
namespace codex::refl {
/**
* Transparently stores \p T objects as contiguous arrays of each field.
* The storage is a "transposition" of a `T[]`: a structure of arrays instead of
* an array of structures. This allows each field array to be processed
* sequentially (e.g. in SIMD fashion).
*
* An interface similar to `std::vector` is also provided to add/set/remove
* elements of type \p T as if that were the actual storage. Access methods are
* also provided, although they return values that are manufactured on demand,
* not references as is the case for `std::vector`.
*/
template<typename T, typename S = detail::storage_descriptor>
class SOA : detail::storage<T, S> {
using base_type = detail::storage<T, S>;
public:
/** (Value/decayed) type of field with index \p I. */
// Should be `base_type::value_type`, but that generates an ICE(!?).
template<std::size_t I>
using field_type =
std::decay_t<std::tuple_element_t<I, field_tuple_t<T>>>;
// Container interface.
std::size_t size(void) const { return this->field<0>().size(); }
void push_back(T &&t = {});
// Idiosyncratic container interface.
/**
* Indexed access to an element.
* The returned value (n.b.: not a reference) is manufactured from the
* values of each field gathered from the storage.
*/
T operator[](std::size_t i) const;
/** Stores the value of each field of \p t. */
void set(std::size_t i, const T &t);
/** Stores the value of each field of \p t. */
void set(std::size_t i, T &&t);
/** Provides access to the contiguous storage of field \p I. */
template<std::size_t I> std::span<field_type<I>> field(void);
template<std::size_t I> std::span<const field_type<I>> field(void) const;
};
template<typename T, typename S>
T SOA<T, S>::operator[](std::size_t i) const {
T ret = {};
this->for_each_i(i, [t = field_tuple(ret)](auto fi, auto &x) {
std::get<fi()>(t) = x;
});
return ret;
}
template<typename T, typename S>
void SOA<T, S>::push_back(T &&t) {
using detail::field_storage;
this->for_each([&t]<typename F, std::size_t I>(field_storage<S, I, F> &v) {
v.push_back(std::get<I>(field_tuple(CODEX_FWD(t))));
});
}
template<typename T, typename S>
void SOA<T, S>::set(std::size_t i, const T &t) {
this->for_each_i(i, [&t](auto vi, auto &x) {
x = std::get<vi()>(field_tuple(t));
});
}
template<typename T, typename S>
void SOA<T, S>::set(std::size_t i, T &&t) {
this->for_each_i(i, [&t](auto vi, auto &x) {
x = std::get<vi()>(field_tuple(std::move(t)));
});
}
template<typename T, typename S>
template<std::size_t I>
auto SOA<T, S>::field(void) -> std::span<field_type<I>> {
using F = field_type<I>;
return *static_cast<detail::field_storage<S, I, F>*>(this);
}
template<typename T, typename S>
template<std::size_t I>
auto SOA<T, S>::field(void) const -> std::span<const field_type<I>> {
using F = field_type<I>;
return *static_cast<const detail::field_storage<S, I, F>*>(this);
}
}
#endif
#include "utils.hpp"
#ifndef CODEX_REFLECTION_UTILS_HPP
#define CODEX_REFLECTION_UTILS_HPP
#include <cstddef>
#include <type_traits>
#include <utility>
#define CODEX_FWD(x) std::forward<decltype(x)>(x)
namespace codex::refl {
/** Alias for an `integral_constant` of `size_t`. */
template<std::size_t I>
using index_constant = std::integral_constant<std::size_t, I>;
}
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment