-
-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Q: make REFL_AUTO, REFL_TYPE etc. callable from nested namespace #59
Comments
Happy holidays to you as well! |
@veselink1 any chance of addressing this issue? We would like to use your little library in a wider range of projects (beyond our OpenCMW library), notably the new GNU Radio 4.0. Having to define the reflection macros always in the root namespace and not in the nested namespace where the aggregate type is defined is sort of a usability issue and error-prone/hard to explain to novice or occasional C++ developers. At its core, the issue as you mentioned is: namespace library { // library namespace -- cannot touch
template <typename T>
struct my_struct {};
}
// user code -- needs nested namespace
namespace user {
template<>
struct library::my_struct<int> { // error: class template specialization of 'my_struct' not in a namespace enclosing 'library'
int data;
};
}
template<>
struct library::my_struct<double> { // valid
double data;
}; At the same time, Niels Lohmann's Json library uses two similar types of macros Is the difference because refl-cpp defines a templated struct that is being specialised for each user type and the other lib a templated function? 🤔 Could we iterate on this and -- if you like -- maybe find a solution together? |
@veselink1 I may have found one possible solution to the problem above. Plasese see the compiler-explorer example for details. The problem with template specialisation is that it needs to be done in the same and/or root namespace. However, this isn't necessary for function specialisation as commonly used, for example, by overloading the namespace library {
template <typename T, typename... Members>
struct type_info {
using value_type = T;
constexpr static std::size_t N = sizeof...(Members);
constexpr static std::string_view nameSpace = CURRENT_NAMESPACE;
std::string_view className;
std::array<std::string_view, N> memberName;
std::tuple<Members T::*...> memberReference;
};
template <typename T>
consteval static library::type_info<T> typeInfo(const T&) {
return library::type_info<T>{
.className = {"<unknown type>"}};
}
} // namespace library and helper macros as (N.B. here w/o the macro expansions (wanted to keep is KISS)): #define ENABLE_REFLECTION(...) ENABLE_REFLECTION_N(__VA_ARGS__, ENABLE_REFLECTION3, ENABLE_REFLECTION2, ENABLE_REFLECTION1)(__VA_ARGS__)
#define ENABLE_REFLECTION_N(_1, _2, _3, _4, NAME, ...) NAME
#define ENABLE_REFLECTION1(TypeName, Member1) \
consteval static library::type_info<TypeName, decltype(std::declval<TypeName>().Member1)> typeInfo(const TypeName&) { \
return library::type_info<TypeName, decltype(std::declval<TypeName>().Member1)>{ \
.className = {#TypeName}, \
.memberName = { #Member1 }, \
.memberReference = std::make_tuple(&TypeName::Member1) \
}; \
}
// [...] This would allow the following: namespace ns1 {
struct MyClass {
int fieldA = 42;
float fieldB = 3.14f;
};
ENABLE_REFLECTION(MyClass, fieldA, fieldB);
} // namespace ns1
namespace ns2 {
struct MyClass {
int value = 99;
};
ENABLE_REFLECTION(MyClass, value);
} // namespace ns2
// global namespace
struct MyClass {
int age = 99;
std::string name = "pops";
};
ENABLE_REFLECTION(MyClass, age, name);
struct UnknownClass {
int value = 123;
float value2 = 1.23f;
};
template<typename T>
constexpr void print_type_info() {
using namespace library;
T data{};
constexpr auto type_info = typeInfo(data);
fmt::print("{}::{}\n", type_info.nameSpace, type_info.className);
std::apply([&type_info, &data](auto&&... args) {
std::size_t i = 0;
( (fmt::print(" {} = {}\n", type_info.memberName[i++], data.*args)), ...);
}, type_info.memberReference);
fmt::print("\n");
}
int main() {
print_type_info<MyClass>();
print_type_info<UnknownClass>();
print_type_info<ns1::MyClass>();
print_type_info<ns2::MyClass>();
return 0;
} printout:
What do you think? N.B. I played also a bit with structured bindings but this, unfortunately, fails once classes have parents. 🤔 For some weird reason (<->a hole in the C++ std) one can use the brace-initialiser with classes inheritance but not the other way around with the structured bindings. |
Hi @RalphSteinhagen, Thanks for sharing your findings. The approach you've linked to is one that I am aware of and have considered both when writing the original implementation and when you've previously asked about this. Where it breaks down is the inability to have a template method inside of a local class. See this example: https://godbolt.org/z/9YWjGc41P This is something that is used pervasively in the generated type and member descriptors. |
Does one really need templated functions inside the struct? The access could be also a free-standing functions as illustrated in the second code-snippet I posted (albeit in tiny script). The free standing function could then access the tuple as long as it's publically defined: template <std::size_t idx, typename Type>
auto& member(Type&& type) {
return std::get<idx>(to_tuple(std::forward<Type&>(type)));
} The std::tuple<Members T::*...> type_info<T>::memberReference That could work, couldn't it? |
@veselink1 could we iterate on the above proposal? Maybe a direct chat/video/group call would be useful? |
Hi @RalphSteinhagen, I'm considering ways of implementing this, some along the lines of what you suggested, but I do not have a clear timeline just yet. |
Hi @veselink1,
thanks again for time and making refl-cpp public! We gained quite a bit more experience with your library and are happily using it for (de-)serialisation in our little lib.
From a usability point of view: what would it take to make REFL_TYPE callable from a nested namespace?
In the documentation you correctly pointed out that the macros need to be called from within the global namespace.
This becomes a bit unwieldy, a potential source of errors, and less readable in larger and/or structured projects where the structs are declared inside nested namespaces, e.g.:
Ideally one would like to declare the visitor macros right after the structs have been declared to minimise synchronisation errors.
Any idea how this could be extended (other than closing/reopening the other namespaces)? Any help would be much appreciated!
Season greetings and wish you a successful New Year 2022! 🎄 🎁 🎉
The text was updated successfully, but these errors were encountered: