Created
August 1, 2021 17:38
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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