Coder Social home page Coder Social logo

theniteswhosay / rarecpp Goto Github PK

View Code? Open in Web Editor NEW
120.0 120.0 6.0 3.02 MB

Creating a simpler, more intuitive means of C++ reflection

License: MIT License

C++ 93.20% Python 4.50% CMake 0.81% Shell 0.14% C 0.70% Starlark 0.52% Batchfile 0.03% HTML 0.04% SCSS 0.06%
cplusplus cplusplus-17 cplusplus-library cpp cpp-library cpp17 json rare reflection reflection-library

rarecpp's People

Contributors

theniteswhosay avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

rarecpp's Issues

Pointer Sub-Type Trait Identification

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.

compile failed in ubuntu-18.04

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__;

String Map Key Bug

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.

Poor StringStream Performance

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

getting and setting values by string

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

Allow For Non-Default Constructible Objects

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.

Tuple Protocol

Evaluate whether tuple protocol can be added to RareCpp without major compromises to ergonomics or compile-time.

Advanced Function Support

Feature Goals:

  • For non-overloaded/non-template functions...
  • 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
  • For overloaded/templated 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

Private Reflection

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.

if constexpr and C++ 17

The "if constexpr" statement would be incredibly useful, though it would force the whole library up to C++17, consider going for both,

Nullptr in Json::Generic output

In Put::GenericIterable there are two unguarded dereferences of field.second that can result in reads from nullptrs/uncaught exceptions

Clang - inline function not defined

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...

  • upgrade to Clang 15.0.0 or newer, or...
  • re-order your structure such that the reflection macro precedes uses of reflection (direct or indirect) e.g.
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); }
};

[Question] How to map DTO to a C++ gRPC/ProtoBuf Object Because it is final and has accessor functions!)

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:

  • the ProtoBuf class is created as final (via protoc)
  • The access to the fields has to be via accessor functions e.g.:
    • response->set_major(out.major);
    • etc. then
    • 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

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.

How to get the offset of a field?

Thanks for your excellent library! I wonder if there are some ways to get the address offset of a field, like the offsetof macro.

REFLECT Macro Simplification & typeStr updates

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

RareBinary

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.

Hierarchal Inheritance

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!

Clang & GCC Support

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

Missing Diagnostics On Invalid Members

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.

Iterate through fields and query types without an instance

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

JSON Input

Need to implement JSON input (from JSON to objects using REFLECT)

Advanced Annotations

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:

  • Change the name of the field in JSON without changing the field name in the struct nor writing customizers (which requires more advanced template techniques) for fields.
  • Declare a URL (or portion of a URL) that results in a specific function call and myriad parameters and options

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)
};```

Improve Templates & Proxy-Reflection JSON

While some limitation with regards to reflecting templated classes is expected e.g...

  • Two partial specializations could have different fields, and thus reflecting the primary template alone can't capture all fields
  • Template classes that aren't full specializations are not a type; it may be impossible to reflect templates that aren't full specializations to the same extent as regular classes

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.

Fix Pointer Const Correctness

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.

Compatibility with Arduino

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

STL stack, queue, and priority_queue

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.

Json Flexible Base Type

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.

Reflect Inheritance

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

RareRest

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

Json::Name Annotation

Implement annotations...

Json::Name - allowing renaming of a json field or super

Json::SuperFormat - giving the user control over how supers are serialized

  • Nested (default): puts as a nested object "__SuperType": {...} where SuperType is the name of the super class, if Json::Name is provided, that is used in place of __SuperType, e.g. "MySuper": {...}
  • Flat: puts all super fields as though they were fields in the sub-class
  • PrefixedFlat: puts all super fields as though they were fields in the sub-class ("__SuperType"), e.g. "__SuperTypeMyField", if Json::Name is provided, that is used as the prefix e.g. "myPrefixMyField"

Automate Detecting Reflection

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; };

Increase Constexpr

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.

How to check whether there exists a field of specific type?

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);

Advanced Pointer Handling

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 Containers

STL iterables (like arrays/lists, that can become JSON arrays) and iterable maps (key value pairs that can become JSON maps) need handling.

Project Rebranding

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.

  • RareCppReflection
  • RareCppJson
  • RareCppXml
  • RareCppMapper
  • RareCppORM
  • RareCppREST

Json Flattening

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

Consider Moving Lambda Guards and Accelerators from Field

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.

RareObjectMapper

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,

Field Limits

With the current version you're limited to 20 reflected fields, should be expanded as much as possible.

Potential for Runtime Json Improvements

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.

RareCpp 2.0.0

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

Improve Map/Pair Representation & Support Tuples

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": { ... }
    }
  ]
}

Json MappedBy

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.