Skip to content

Instantly share code, notes, and snippets.

@jtacoma
Created August 1, 2021 17:38
Show Gist options
  • Save jtacoma/7b000f825190b23f26c1292375c1b1d6 to your computer and use it in GitHub Desktop.
Save jtacoma/7b000f825190b23f26c1292375c1b1d6 to your computer and use it in GitHub Desktop.
A way to implement introspection for C++ types allowing easier decoupling from data format and serialization libraries
// Copyright 2021 Google LLC
// SPDX-License-Identifier: Apache-2.0
#include <cstddef> // ptrdiff_t
#include <string>
#include <tuple>
#include <utility>
namespace introspect {
template <typename T>
struct field {
std::string name;
const T* ptr;
field(std::string_view name, const T* ptr) : name(name), ptr(ptr) {}
};
template <typename Object, typename... Fields>
class fields {
fields() = delete;
fields(const fields&) = delete;
fields(fields&&) = default;
template <typename T>
struct meta {
std::string name;
std::ptrdiff_t offset;
using type = T;
meta(std::string_view n, const Object* zero, const T* something) : name(n) {
offset =
static_cast<const std::byte*>(static_cast<const void*>(something)) -
static_cast<const std::byte*>(static_cast<const void*>(zero));
}
const type& in(const Object* zero) const {
return *static_cast<const type*>(static_cast<const void*>(
static_cast<const std::byte*>(static_cast<const void*>(zero)) +
offset));
}
type& in(Object* zero) const {
return *static_cast<type*>(static_cast<void*>(
static_cast<std::byte*>(static_cast<void*>(zero)) + offset));
}
};
std::tuple<meta<Fields>...> meta_;
using index_sequence = std::index_sequence_for<Fields...>;
public:
fields(const Object* z, field<Fields>... args)
: meta_(meta<Fields>(args.name, z, args.ptr)...) {}
template <typename Visit>
void for_each(Visit v, const Object& object) const {
[&]<size_t... Index>(std::index_sequence<Index...>) {
(v(std::get<Index>(meta_).name, std::get<Index>(meta_).in(&object)), ...);
}
(std::index_sequence_for<Fields...>());
}
template <typename Visit>
void for_each(Visit v, Object& object) const {
[&]<size_t... Index>(std::index_sequence<Index...>) {
(v(std::get<Index>(meta_).name, std::get<Index>(meta_).in(&object)), ...);
}
(std::index_sequence_for<Fields...>());
}
};
template <typename T>
concept static_introspectable = requires {
T::fields();
};
template <typename T>
concept instance_introspectable = requires(T a) {
a.fields();
}
&&!static_introspectable<T>;
template <typename T>
struct traits;
template <static_introspectable T>
struct traits<T> {
static const auto& fields(const T& object) {
static const auto cached = T::fields();
return cached;
}
};
template <instance_introspectable T>
struct traits<T> {
static const auto fields(const T& object) { return object.fields(); }
};
template <typename F, typename T>
void walk(F visit, const T& object) {
traits<T>::fields(object).for_each(visit, object);
};
} // namespace introspect
#include <cassert>
#include <iostream>
#include <vector>
int main() {
struct fields_as_method {
int field1;
std::string field2;
auto fields() const {
std::cout << "fields_as_method: creating fields" << std::endl;
using introspect::field;
return introspect::fields{this, field{"field1", &field1},
field{"field2", &field2}};
}
};
struct fields_as_static_method {
int field1;
std::string field2;
static auto fields() {
std::cout << "fields_as_static_method: creating fields" << std::endl;
using introspect::field;
static auto zero = fields_as_static_method{};
return introspect::fields{&zero, field{"field1", &zero.field1},
field{"field2", &zero.field2}};
}
};
auto test_introspect = [](auto object) {
introspect::walk(
[&](std::string_view name, const auto& value) {
std::cout << name << " = " << value << std::endl;
},
object);
};
test_introspect(fields_as_method{42, "foo"});
test_introspect(fields_as_method{42, "foo"});
test_introspect(fields_as_static_method{42, "foo"});
test_introspect(fields_as_static_method{42, "foo"});
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment