theniteswhosay / rarecpp Goto Github PK
View Code? Open in Web Editor NEWCreating a simpler, more intuitive means of C++ reflection
License: MIT License
Creating a simpler, more intuitive means of C++ reflection
License: MIT License
Pointers to types need better identification of their traits, e.g. when the type is a pointer to an STL iterable container "true" needs to be captured in the fieldIsStlIterable template parameter for Field same as when it's an STL container type with no pointer; this would be best done with a generalized extractor for the type being pointed to, which just returns the same type if it's not a pointer.
Add Unit Tests
I am trying to use Reflect.h
in R (https://github.com/kongdd/hello.R/blob/master/src/test-reflect.cpp). However, failed in ubuntu-18.04.
Any idea how to solve this issue?
https://github.com/kongdd/hello.R/runs/5064647738?check_suite_focus=true
g++ -std=gnu++17 -I"/opt/R/4.1.2/lib/R/include" -DNDEBUG -I'/home/runner/work/_temp/Library/Rcpp/include' -I'/home/runner/work/_temp/Library/RcppArmadillo/include' -I/usr/local/include -fopenmp -fpic -g -O2 -c RcppExports.cpp -o RcppExports.o
g++ -std=gnu++17 -I"/opt/R/4.1.2/lib/R/include" -DNDEBUG -I'/home/runner/work/_temp/Library/Rcpp/include' -I'/home/runner/work/_temp/Library/RcppArmadillo/include' -I/usr/local/include -fopenmp -fpic -g -O2 -c arr3d.cpp -o arr3d.o
g++ -std=gnu++17 -I"/opt/R/4.1.2/lib/R/include" -DNDEBUG -I'/home/runner/work/_temp/Library/Rcpp/include' -I'/home/runner/work/_temp/Library/RcppArmadillo/include' -I/usr/local/include -fopenmp -fpic -g -O2 -c test-reflect.cpp -o test-reflect.o
In file included from test-reflect.cpp:2:0:
test-reflect.cpp:36:5: in constexpr expansion of ‘ExtendedTypeSupport::TypeName<float>()’
Reflect.h:[92](https://github.com/kongdd/hello.R/runs/5064647738?check_suite_focus=true#step:10:92)5:73: error: the value of ‘__PRETTY_FUNCTION__’ is not usable in a constant expression
static constexpr auto typeStr = ExtendedTypeSupport::TypeName<Type>(); \
^
Reflect.h:43:17: note: in definition of macro ‘ML_E’
#define ML_E(x) x
^
Reflect.h:51:23: note: in expansion of macro ‘DESCRIBE_FIELD’
#define ML_3(f,a,...) f(a) ML_E(ML_2(f,__VA_ARGS__))
^
Reflect.h:46:19: note: in expansion of macro ‘ML_3’
#define ML_C(x,y) x##y
^
Reflect.h:1[95](https://github.com/kongdd/hello.R/runs/5064647738?check_suite_focus=true#step:10:95):25: note: in expansion of macro ‘ML_N’
#define FOR_EACH(f,...) ML_N(COUNT_ARGUMENTS(__VA_ARGS__),f,__VA_ARGS__)
^~~~
Reflect.h:957:5: note: in expansion of macro ‘FOR_EACH’
FOR_EACH(DESCRIBE_FIELD, __VA_ARGS__) \
^~~~~~~~
test-reflect.cpp:36:5: note: in expansion of macro ‘REFLECT’
REFLECT(FuelTank, capacity, currentLevel, tickMarks)
^
Reflect.h:592:16: note: ‘__PRETTY_FUNCTION__’ was not declared ‘constexpr’
view = __PRETTY_FUNCTION__;
Currently when reading in a std::map (or similar pair containing iterable), if the key type is a std::string (or something that amounts to expecting quotes) it bugs out on well-formed JSON, saying it expects an open quote, this is due to Json::Read::String being called twice, once to remove the quotes (which in cases where key isn't a string, allows for ints & complex keys and such to be parsed), then once again when it sees it's reading to a string type.
std::stringstream performance is very poor compared to std::string and std::vector<char> implementations, especially noticeable when scaling up the load, JSON shouldn't be built around streams or should have overloads not built around streams
Hi!
I was looking for some piece of code to have in C++ something similar to setattr / getattr from python, and you library seems to be it.
I've read the readme a couple of times and maybe you could clarify the following questions I have:
a) Can I get the value of an attribute by its name without iterating the fields?
class FuelTank {
public:
float capacity;
float currentLevel;
float tickMarks[2];
REFLECT(FuelTank, capacity, currentLevel, tickMarks)
};
FuelTank fuel_tank;
// this is what I'd like to do
float capacity = fuel_tank.someKindOfGetter("capacity");
fuel_tank.someKindOfSetter("capacity", 10.4);
b) The NOTE
macro seems very nice as well, can I use it to do the following?
class FuelTank {
public:
NOTE(_capacity, "myCustomCapacity")
float _capacity;
float _currentLevel;
float _tickMarks[2];
REFLECT(FuelTank, _capacity, _currentLevel, _tickMarks)
};
FuelTank fuel_tank;
// this is what I'd like to do
float capacity = fuel_tank.someKindOfGetter("myCustomCapacity");
fuel_tank.someKindOfSetter("myCustomCapacity", 10.4);
Thanks in advance,
Santiago
Several places in the RareJson and RareObject mapper code currently require an object to be default constructible to take advantage of them; see what can be done about removing this requirement.
Evaluate whether tuple protocol can be added to RareCpp without major compromises to ergonomics or compile-time.
Feature Goals:
- Provide support for retrieving the following...
- function_pointer
- return_type
- argument_count
- cvref_qualification
- arg<I>::type
- Provide support for dynamically building arguments (given a lambda/function which takes the argument type & index and returns argument value) and calling functions
- Distinguish between...
- Unqualified function (no argument/template types are known)
- Partially-qualified function (some argument/template types are known, but not enough to get a strongly-typed function pointer)
- Fully-qualified function (enough argument/template types are known to get a strongly-typed function pointer)
- Allow for the name of the overloaded functions ("unqualified function") to be supplied in the REFLECT/REFLECT_NOTED macro
- Provide the capability to supply some or all of the argument types/template arguments for a particular overload in a NOTE for a set of overloaded functions; the NOTE should in turn allow separate annotations to be attached to each partially or fully-qualified function variation (similar to Supers)
- Provide enumeration of overloads specified in NOTE, be they partially or fully qualified
- Provide the ability to fully-qualify unqualified or partially-qualified overloads at the point of use
- Provide all the same non-overloaded/non-template features where the user has fully qualified a function in NOTES or at the point of use
Add the capability to reflect private variables from unowned objects (somewhat similar to proxying, but without regard for access-protection level). This form of reflection is expected to be limited to members for which pointers can be formed as it's based on the trick described in https://godbolt.org/z/hcjcjjqhz and will not be able to get many of the little details that the in-class REFLECT and REFLECT_NOTED macros can; all the same it should be possible to get the existing reflection interfaces working and returning reasonable results in a decent number of cases for unowned privates.
The "if constexpr" statement would be incredibly useful, though it would force the whole library up to C++17, consider going for both,
In Put::GenericIterable there are two unguarded dereferences of field.second that can result in reads from nullptrs/uncaught exceptions
Note this does not need any action insofar as RareCpp code changes goes, but for myself/anyone checking project issues I wanted to make a note:
Clang versions < 15.0.0 seem to generally disallow use of constexpr functions in the same class prior to their definition https://godbolt.org/z/8oofKz6z4 . In past versions of RareCpp this has held back upgrading more functions to be able to be used in a constexpr context, however, now that there's a stable release of clang that allows this I will be increasing the constexpr level of RareCpp's field reflection, consequently, many clang users may see errors in clang on code such as the following:
struct CopyConstructorTest
{
CopyConstructorTest(int a, int b) : a(a), b(b) {}
CopyConstructorTest(const CopyConstructorTest & other) { ObjectMapper::map_default(*this, other); }
int a = 0;
int b = 0;
REFLECT(CopyConstructorTest, a, b)
};
warning: inline function <...omitted...> is not defined [-Wundefined-inline]
lld-link: error: undefined symbol <...omitted>
The solution to which shall be either...
struct CopyConstructorTest
{
int a = 0;
int b = 0;
REFLECT(CopyConstructorTest, a, b)
CopyConstructorTest(int a, int b) : a(a), b(b) {}
CopyConstructorTest(const CopyConstructorTest & other) { ObjectMapper::map_default(*this, other); }
};
I have the following DTO:
struct TargetVersionInfoV24Out
{
int32_t major;
int32_t minor;
int32_t build;
int32_t patch;
std::string special_text;
REFLECT(TargetVersionInfoV24Out, Major, Minor, Build, Patch, SpecialText)
} out;
And there is an equivalent gRPC Reply built from a message with the similar fields.
But I cannot use the Proxy
method because:
protoc
)response->set_major(out.major);
response->set_special_text(out.special_text);
So, I would like to create an external reflection from DTO into the ProtoBuf response, How ?
Support for reference types would help greatly for using the library to build low-cost abstractions, and generally would expand possible usage of reflection. The earlier blocker for reference types was that determination of whether a type was static involved getting a pointer to a member, and it's impossible to get a pointer to a reference member (e.g. &ClassType::referenceMember would cause compilation errors).
I believe this problem can be solved with SFINAE, with SFINAE it may be possible to get member pointers/have the above code without generating compiler errors for reference types.
Thanks for your excellent library! I wonder if there are some ways to get the address offset of a field, like the offsetof
macro.
If typeStr can be refactored to not depend on the user providing an explicit type in the REFLECT macro then the REFLECT macro could be greatly simplified.
There's also a big problem with passing types in the REFLECT macro: no commas are allowed in macros, therefore any type that involves a comma (e.g. std::map<int, int>) cannot be passed, at least not without aliasing it first (e.g. using IntMap = std::map<int, int>;) which is quite a burden and junks up the class you're trying to reflect.
What we want back from typeStr is either...
A.) The exact text the user declared the field with (which could indeed be std::map<int, int>)
B.) Text that could be used to declare a perfectly equivalent field
Add a module for binary serialization & deserialization, default behavior for structs should be to read/write the fields of the structure without padding/alignment and with binary field widths the same as the field sizeof's.
Need further exploration on what annotations are needed, but at the least annotations should be usable to change the width of a field's binary representation or ignore a field. Further annotations might include changing the representation of strings, altering the behavior of pointers/references, etc.
Hello,
I am unable to find any documentation or usage for Hierarchical Inheritance. This is my use case (May have some minor typos).
Note(Object)
class Object
{
public:
float f1;
REFLECT(Object, f1)
}
Note(DerivedObject, Super<Object>)
class DerivedObject : public Object
{
public:
float f2;
REFLECT_NOTED(DerivedDerivedObject, f2)
}
Note(DerivedDerivedObject, Super<DerivedObject>)
class DerivedDerivedObject : public DerivedObject
{
public:
float f3;
void PrintInfo()
{
DerivedDerivedObject::Supers::ForEach(*this, [&](auto superInfo, auto& superObj) {
using Super = typename decltype(superInfo)::Type;
Super::Class::ForEachField(superObj, [&](auto& field, auto& value) {
std::cout << " " << field.name << ": " << value << std::endl;
});
});
DerivedDerivedObject::Class::ForEachField(*this, [&](auto& field, auto& value)
{
std::cout << " " << field.name << ": " << value << std::endl;
});
}
REFLECT_NOTED(DerivedDerivedObject, f3)
}
Expected Output:
f1: 1
f2: 2
f3: 3
Actual Output:
f2: 2
f3: 3
Currently, I can get my desired behavior via
Note(DerivedDerivedObject, Super<Object>, Super<DerivedObject>)
But I see this becoming hard to manage in the future, is there any way to access Supers recursively?
It is my understanding that Supers::ForEach calls Supers::ForEachRecursion(...) so I am unsure why this does not access the underlying Object.
Thank you for your support! RareCpp has been a joy to use so far!
As features are finally getting wrapped up ensure everything works on Clang & GCC as well as on visual studios where this was developed, this will most likely primarily be about the LHS/RHS and FOR_EACH macro which involve workarounds for visual studios bugs, will most likely wrap those definitions with workarounds in a precompiler check for VS and put the macro definitions without the workarounds in the else of the precompiler check
Currently there's no early/easy diagnostic for an invalid member in the REFLECT macro (e.g. a member that was misspelled or later removed without updating the macro). Having some sort of diagnostic at some early point (in the macro itself is ideal if it can be added without much overhead, in the APIs otherwise), be it an error, maybe a psedo-warning, is very important and highest priority for RareCpp development.
While there are multiple potential barriers to providing such a diagnostic, the biggest lies in the design of overload reflection which includes reflecting overloads for which arguments are not provided as NOTEs: when you don't know at least one of the overloads argument sets it may well be impossible to check for an overloads existence. This leads to the current situation in which an invalid reflected member gets "recognized" as an overloaded member.
One potential solution is to force opt-in for allowing these sorts of anonymous overloaded members (where the argument types are not provided in notes and can come to be known later) with a feature-macro; I regard feature-macros as a terrible idea for RareCpp in general but the importance of identifying invalid members early & in an intuitive way may take priority. Moreover this feature-macro should be safe to leave on (or push/pop), it would be a uni-directional on switch with proper error messages being present if it wasn't on, it wouldn't cause errors or code-differences to have it be turned on, only remove the default diagnostics for invalid members in the REFLECT macro.
Another barrier - or at least something to measure carefully - is the cost of checking early. Right now the REFLECT macro is basically only template definitions, no template instantiations, checking for the presence of a member would more than likely require instantiating multiple of those templates (check for type and/or pointer_type and/or... something for overloads) - doing so in the macro/in file headers could potentially impact compile times in multiple TUs rather than only the TU where an objects reflected info gets used.
Benchmarking & conformance tests should be applied to the library, probably using https://github.com/miloyip/nativejson-benchmark
Hey, I am looking to iterate through the fields of a struct and query their sizes without needing an object instance. I'm sure that there is method provided but I don't see it.
Currently, I have a working function similar to the following.
template <typename T>
void func() {
T t;
T::Class::ForEachField(t, [&](auto &field, &auto value) {
// sizeof(value)
});
}
Is there anyway to do something like the following instead?
template <typename T>
void func() {
T::Class::ForEachField([&](auto &field) {
// query type sizes e.g. sizeof(field.Type)
});
}
Thanks
Need to implement JSON input (from JSON to objects using REFLECT)
To unlock many potential powers within reflection: annotations need to be given more power.
First, annotations need to be capable of containing strings; not every annotation will need strings, so this should likely be a specialized kind of annotation. Two example uses:
Annotations also need to be reasonable to write alongside the variable and function declarations, and if at all possible automatically pull in the annotations without explicitly placing them in the REFLECT macro. SFINAE may well provide a way of inspecting a structure to find the annotations.
Possible simple example:
struct Point
{
NOTE(latitude) { Json::Name{"lat"} };
double latitude;
NOTE(longitude) { Json::Name{"long"} };
double longitude;
REFLECT(() Point, latitude, longitude)
};
Possible involved example:
struct State
{
int value;
long otherValue;
NOTE(status) { std::tuple {
Json::Name{"code"},
Json::EnumInt{}
} };
Status status;
NOTE(update) { Rest::Endpoint {
Rest::Method::PATCH,
Rest::Url{"/update/{value}/{otherValue}"},
Rest::Param<int>{"value"},
Rest::Param<long>{"otherValue"}
} };
auto update(int value, long otherValue) {
this->value = value;
this->otherValue = otherValue;
this->status = Status::Cached;
return Json::out(*this);
}
REFLECT(() State, value, otherValue, update, status)
};```
While some limitation with regards to reflecting templated classes is expected e.g...
Support could be a lot better than it is at present. Also the current workaround it to use proxy reflection on full specializations, but that's not fully working with JSON, need this to be working as well.
ExtendedTypeSupport::remove_pointer isn't getting the dereferenced constness correct, this may result in JSON input to such values getting ignored, fix this https://godbolt.org/z/oYY347
Also ensure that auto allocation for const shared&unique pointers is attempted in the appropriate const cases only.
Hi, I'm trying to add this library to arduino, but they don't use constexpr
I-m trying to convert it to Macro but I'm failing.
There is a version or something to this matter? Thanks
While stack, queue, and priority_queue would each be useful to provide access to their elements via reflection, by design these containers only provide access to the top element. While it is possible to pop the top element until the data set is empty (and place them on another stack/queue), that involves destroying and recreating the structure, if client code threw an exception partway through traversing the elements the structure would be corrupted.
Catching and continuing the traverse may be an option, but involving exception handling means use of constexpr would be precluded and thus the compile time optimizations would be lost, making it far less worth it to provide them in an accelerator function (especially in the accelerators already working for other types)... perhaps it would be better to identify these special types and force clients to write their own traversals, or to provide an entirely separate accelerator for just these structures which would do the try/destructive-traverse/catch/rebuild without the compile time optimizations.
Currently only reflect objects are accepted as the base type; containers, pairs, tuples, and arrays (possibly among other types) should also be acceptable base types to output/input.
This will also entail having arrays as the root JSON element, which is technically invalid JSON but very widely use and thus necessary here regardless.
There needs to be some mechanism to declare when a reflected class inherits from another reflected class and have accelerators that could loop through inherited fields. Keeping in mind that C++ supports multiple inheritance.
One possible idea would be to change the first argument in REFLECT to have the types inherited from be the LHS, e.g. instead of
REFLECT(MyClass, (B) myBasicField, (R) myObjectField)
We would have...
REFLECT((N) MyClass, (B) myBasicField, (R) myObjectField) // No inheritance
REFLECT((NoInherit) MyClass, (B) myBasicField, (R) myObjectField) // No inheritance
REFLECT((I<MySuperClass>) MyClass, (B) myBasicField, (R) myObjectField) // Singular inheritance
using Supers = I<MyFirstSuperClass, MySecondSuperClass>
REFLECT((Supers) MyClass, (B) myBasicField, (R) myObjectField) // Multiple inheritance
The extra (N) would certainly be a bit annoying here... It might be possible to do
REFLECT((MyClass), (B) myBasicField, (R) myObjectField) // No inheritance
REFLECT((MyClass) I<MySuperClass>, (B) myBasicField, (R) myObjectField) // Singular inheritance
using Supers = I<MyFirstSuperClass, MySecondSuperClass>
REFLECT((MyClass) Supers, (B) myBasicField, (R) myObjectField) // Multiple inheritance
But even though we'd save characters I feel like it causes more confusion than the former. A third option would be to reserve the second parameter of the macro for inheritance, that obviously causes us to lose one potential field/decrease max reflected fields by 1.
REFLECT(MyClass, N, (B) myBasicField, (R) myObjectField) // No inheritance
REFLECT(MyClass, I<MySuperClass>, (B) myBasicField, (R) myObjectField) // Singular inheritance
using Supers = I<MyFirstSuperClass, MySecondSuperClass>
REFLECT(MyClass, Supers, (B) myBasicField, (R) myObjectField) // Multiple inheritance
Add another library to RareCpp for REST - using annotation driven methods as endpoints; e.g. in a controller class you might have an update method
NOTE(update,
Rest::Method::PATCH,
Rest::Url{"/update/{value}/{otherValue}"},
Rest::PathParam<int>{"value"},
Rest::PathParam<long>{"otherValue"})
std::string update(int value, long otherValue) {
this->value = value;
this->otherValue = otherValue;
this->status = Status::Cached;
return "";
}
Preliminary thoughts on the separate pieces:
Rest::Glass: Everything that necessarily appears in client code (method markup, potentially marking as controller, controller registration if not automatic)
Rest::Controller: Controllers contain one or more methods marked up for REST; controllers must be somehow registered to the engine
Rest::Inpoint: A function within a controller which represents a REST endpoint; with annotations defining the http method, URL component, and parameters
Rest::Engine: That to which the driver registers itself and to which controllers get registered, when an endpoint is hit the driver shall send data to the engine, which shall serialize the data as the appropriate C++ types based on the settings in the controller, then call the appropriate Rest::Inpoint function within the controller
Rest::Driver: The generalized template for system-specific networking code as well as common driver helper methods
MyDriver : Rest::Driver: System-specific networking code
Implement annotations...
Json::Name - allowing renaming of a json field or super
Json::SuperFormat - giving the user control over how supers are serialized
Rather than requiring an annotation to signal that a field is reflected, which requires more end-user effort and forces a field to only have one reflected type, create an independent interface which can detect whether a type is reflected, e.g.
is_reflected<T>::value
This is possible with a little sfinae
template <typename T, typename = decltype(T::Class::TotalFields)> static constexpr std::true_type typeHasReflection(int);
template <typename T> static constexpr std::false_type typeHasReflection(unsigned int);
template <typename T> struct is_reflected { static constexpr bool value = decltype(typeHasReflection<T>(0))::value; };
template <typename T> struct is_reflected<const T> { static constexpr bool value = is_reflected<T>::value; };
Perform further exploration on whether the ForEachField/FieldAt methods which operate on an instance of the reflected class can be made constexpr, at a glance MSVC compiler has no issue, though intellisense has a few; Clang has an issue with use of ObjectMapper prior to the REFLECT macro (e.g. if you wrote a copy constructor using ObjectMapper before you wrote the REFLECT macro, that errors out on the constexpr methods, whereas it didn't when they were non-constexpr).
I came across a compile-time serialization use-case today, and I was able to use the non-instanced overloads just fine, but ForEachField would have been better.
Does this library support reflecting Enum class member to string?
For example, I have a enum class Foo
enum class Foo {
kBar,
kBaz,
};
And I want to convert to string
"kBar", "kBaz"
Given a specific type, say int
, how to check whether there exists an integer field at compile time?
Or are there some ways to get a tuple of field types? So I can check types like this.
struct Pos {
float x;
int y;
using MemberTypes = std::tuple<float, int>;
};
constexpr bool has_int_member = [] <typename... Ts> (std::tuple<Ts...>) {
return (std::is_same_v<Ts, int> | ...);
}(Pos::MemberTypes{});
static_assert(has_int_member);
When reading in the state of something and you get a value for a pointer field, what the user wants to happen is ambiguous, I think it would fall under one of the following:
1.) Do nothing, ignore the pointer fields.
2.) Assign the value passed in to the value pointed to. If the value passed in is null, assign nullpointer, if the value passed in is non-null, and the pointer is null, throw a nullpointer exception.
3.) Assign the value passed in to the value pointed to. If the value passed in is null, assign nullpointer, if the value passed in is non-null, and the pointer is null, auto-allocate the value using "new".
4.) Assign the value(s) passed in to the dynamic array pointed to. If only null is passed in, assign nullpointer, if an array is passed in, and the pointer is null, throw a nullpointer exception.
5.) Assign the value(s) passed in to the dynamic array pointed to. If only null is passed in, assign nullpointer, if an array is passed in, and the pointer is null, auto-allocate the value using "new".
This option set could be described for a pointer by the following structure...
{
bool assignable;
bool autoAllocate;
size_t* dynamicArraySize; // If nullpointer, this is not a dynamic array
}
Though more thought still needs to go into how this would be implemented.
STL iterables (like arrays/lists, that can become JSON arrays) and iterable maps (key value pairs that can become JSON maps) need handling.
As I expand the header libraries available on top of reflection, the individual libraries will need some way of succinctly and uniquely referring to them. Right now you'd have to say something like "The JSON library built on top of RandomAccessReflection", which is very clunky.
I'm thinking to call the larger project RareCpp ("Random Access Reflection & Extensions in C++") - then the individual libraries can be referred to as RareCpp[Library Name], e.g.
RareJson should support opt-in (with annotations) flattening of super classes and nested objects.
e.g.
struct Parent { int a; ... }
struct Child : Parent { int b; ... }
Which today when writing child would come out something like...
{
"b": 12345,
"parent": {
"a": 12345
}
}
Should be possible to instead have written like...
{
"a": 12345,
"b": 12345
}
Some of the challenges identified earlier were as follows:
1.) Flattening should potentially apply recursively, pulling the hierarchy of ancestor classes into sub-classes if directed to do so
2.) Data-hiding and naming collisions need to be accounted for
3.) Open questions of whose field clusters unknown fields belong to
4.) There's no reason flattening couldn't also apply to regular fields of certain types, such as nested reflected objects
5.) There's a huge amount of logic changes related to pretty print and affixing that this would necessitate - you can no longer rely on analysis of the current object to tell if something is the first field
Should be able to reflect and invoke member and static functions .
It may be wise to remove the lambda guards and accelerators from field and place them in another class that extracts the template arguments from Field and then builds the lambda guards and accelerators using them - this could help avoid generating giant chunks of code for each field for each class using REFLECT - unless they actually use it.
An ObjectMapper simply maps between two similar objects - if the fields are all the same between two reflected objects it should be able to assign one to the other in a single statement without mentioning any fields explicitly and without any assignment overloads.
If field names differ but the types do not, it should be trivially easy to define a mapping between said fields.
If a function is needed to map between two objects it should be easy to provide one, and take advantage (or disable) automatic mapping between fields with the same name/type,
With the current version you're limited to 20 reflected fields, should be expanded as much as possible.
Currently I solve the problem of reading types known at runtime time with "Json::Generic"s - relying on polymorphism - but I could potentially (though not necessarily, will need performance testing before and after) cut a lot of memory usage and processing overhead using a type index (or type hash or whatnot) and some sort of union'd type.
In this example I'm using tuple, which is of course not union'd and thus not addressing memory usage, but it demonstrates dynamically retrieving the type from a pack of template parameters. https://godbolt.org/z/z1o818
Generics are also currently ~800 lines of code with tons of repetition for the pure virtual methods, getting this metric down would also be nice if possible.
Drawing near a major version upgrade, overhauling & organizing the core reflection semantics. Current code: https://github.com/TheNitesWhoSay/RareCpp/tree/feature/reflect-idiom-experiment
The main impetus for the changes rests with a few core reflection capabilities...
1.) Obtain field values or field metadata as a parameter pack
2.) Get and set fields by field names
3.) Be able to iterate all fields in constexpr and non-constexpr contexts
4.) Be able to access all fields by index (be it a constexpr index or runtime index) in constexpr and non-constexpr contexts
5.) Be able to access all annotation values for all annotatable declarations (classes, variables, super-classes) in constexpr and non-constexpr contexts
6.) Better support for function/method/callable reflection
7.) Have additional options for reflection not based on macros
Generally speaking, I want to minimize use of macros, the REFLECT macro is of course necessary to achieve the goals of the simplest possible reflection, but I wish to keep what the REFLECT macro expands to as minimal as possible; doing so helps keep objects clean, minimize what needs to be paid for at compile time, and maximize the maintainability and flexibility of the library.
With this in mind I've come up with new REFLECT and REFLECT_NOTED macros (REFLECT_EMPTY was successfully eliminated, you can now use REFLECT_NOTED with no fields). The way the macros work now is to provide field classes which include visitors to themselves, and to provide parameter-pack access to fields, among a few other things. Generally speaking, you won't want to access T::Class (or class_t<T>) directly anymore, rather the new syntax is Reflect<T> .
Reflect<T>:: should then provide you, in an intellisense/IDE-autocomplete friendly way, what you need, in particular...
Reflect<T>
::Fields - access to all field information (be they data or callable fields)
::Notes - access to class-level annotations (not field-level annotations)
::Supers - access to all super-class information
Looking a little deeper, ::Fields contains the following...
Reflect<T>
::Fields
::Total - the total number of fields
::FilteredCount<Filter, FilterArgs...> - the total number of fields with the given filter (e.g. IsData, IsFunction) applied::Field<Index> - access to field metadata class via constexpr index
::field<Index> - access to field metadata class instance via constexpr index::Pack(function(fields...)) - calls the provided function/lambda with the metadata class instances for all fields as the arguments
::PackValues(function(staticValues...)) - calls the provided function/lambda with the static values (member pointers in place of any instance fields) of the reflected type
::PackValues(t, function(values...)) - calls the provided function/lambda with the values of every field, given the instance of the reflected object t(Callbacks, 20 variations total, see table)
::ForEach
::ForEachField
::ForEachValue
::At
::FieldAt
::ValueAt
Name | Arguments | Static | Instanced | Static Filtered | Instanced Filtered |
---|---|---|---|---|---|
ForEach | field, value | ForEach(function) | ForEach(t, function) | ForEach<Filter, Args…>(function) | ForEach<Filter, Args…>(t, function) |
ForEachField | field | ForEachField(function) | - | ForEachField<Filter, Args…>(function) | - |
ForEachValue | value | ForEachValue(function) | ForEachValue(t, function) | ForEachValue<Filter, Args…>(function) | ForEachValue<Filter, Args…>(t, function) |
At | field, value | At(index, function) | At(index, t, function) | At<Filter, Args…>(index, function) | At<Filter, Args…>(index, t, function) |
FieldAt | field | FieldAt(index, function) | - | At<Filter, Args…>(index, function) | - |
ValueAt | value | ValueAt(index, function) | ValueAt(index, t, function) | ValueAt<Filter, Args…>(index, function) | ValueAt<Filter, Args…>(index, t, function) |
This exhaustive set of field accessors should satisfy (1), (3) and (4).
Looking a little deeper at ::Supers , it contains the following...
Reflect<T>
::Supers
::Total - the total number of reflected superclasses
::SuperInfo - access to the super metadata class via constexpr index
::ForEach(function) - iterate all super metadata classes
::ForEach(t, function) - iterate all super-class instances given an instance of the reflected class, t
::At(index, function) - access super-class metadata at the given index
::At(t, index, function) - access super-class instance at the given index, given an instance of the reflected class t
And finally looking a little deeper at ::Notes, it contains the following...
Reflect<T>
::Notes
::ForEach(function)
::ForEach(function)
::ForEach(function) - for each specialization of 'Of'
::Has
::HasSpecialization
::Get()
::Get()
While I perhaps haven't showed as much here, the implementation of these does satisfy (5), and the mere existence of the specializable class Reflect<T> enables (7) going forward.
The prioritized to-do to get this entire version out is as follows...
1.) Analyze performance differences with the previous release of RareCpp, both runtime performance and compile-time performance; identify glaring problems, if any, and fix
2.) Polish up ::Supers and ::Notes, consider whether additional overloads or more detailed filters (as was done with fields) are warranted.
3.) Finish an implementation for capability (2) - get and set fields by name, see #96
4.) Finish at least a prototype implementation for capability (6) - function/method/callable reflection, try to ensure another major version change won't be required for future support, see some of the work done in https://godbolt.org/z/Pc7984hcx
5.) Better & more organized example libraries
6.) Write git wikis for the project/reduce dependency on readmes
Maps or containers of pairs with the key being an object currently serialize the object and make that the key, which works but is not ideal, using an array of objects with fields key and value would work better in these cases.
{
"myMap": [
{
"key": { ... }
"value": { ... }
},
{
"key": { ... }
"value": { ... }
}
]
}
It should be possible to specify a surrogate object for JSON I/O which shall be related to the object that shouldn't be I/O'd directly via a RareObjectMapper mapping.
e.g. NOTE(MyType, Json::MappedBy) as a class or field annotation; perhaps find some way to signal that a default surrogate exists for JSON (this is tricky, as I've found no way to aggregate a list of reflected objects; so sadly this may require a specialization in the global scope or some such mechanism.
Recent Visual Studios update broke multiple pieces of the framework, needs a quick resolution.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.