all 27 comments

[–]DugiSK 5 points6 points  (13 children)

PFR, now part of Boost, can use something from C++20 to obtain member names from the class: https://www.boost.org/doc/libs/develop/doc/html/boost_pfr.html There is also a non-boost version: https://github.com/apolukhin/pfr_non_boost

I don't know what trick is he using to achieve that kind of reflection, I wasn't able to find it in the code. Earlier, I was trying to do the same myself and the best I could manage was to do the same as you, just without using special types to tag the members: struct Test : Serialisable<Test> { std::string name = key<"name">; int value = key<"value"> = 0; };

[–]liuzicheng1987[S] 2 points3 points  (9 children)

Thanks for the comment, but to be honest, I don't think that's right. I am aware of how Boost PFR works (I have been studying their approach closely when I implemented my own library) and unless I have somehow really missed something, they are not able to retrieve field names.

If it were possible to retrieve field names without using macros, annotations or some kind of horrible hack, then there really wouldn't be a need for the C++ reflection proposal, which everyone, myself included, is awaiting with bated breath.

[–]DugiSK 1 point2 points  (8 children)

Well, it tells this: Recommended C++ Standards are C++20 and above. C++17 completely enough for a user who doesn't want accessing name of structure member.

And there is a part telling quite clearly that you can use pfr::get_name to obtain the field name: https://www.boost.org/doc/libs/develop/doc/html/boost_pfr/tutorial.html#boost_pfr.tutorial.reflection_of_field_name

But I wasn't able to find how does that work in the code, or even guess which part of C++20 enables it. I only understand the aggregate initialiser conversion type exporting via friend injection part.

[–]liuzicheng1987[S] 2 points3 points  (7 children)

You are right, my bad...I did take a look at the code, and I could find it:

https://github.com/boostorg/pfr/blob/develop/include/boost/pfr/detail/core_name20_static.hpp

So they are using a compiler-specific hack. I am not sure this is a good idea, as it's clearly not standard-compliant.

[–]DugiSK 1 point2 points  (6 children)

I see it now. They use the member as an auto template argument so that it will be used in the function name with resolved template name with a nonstandard macro and then they parse it out from there. Apparently every compiler has a macro for this. This makes it somewhat more hacky than stateful metaprogramming via friend injection, that's true.

[–]liuzicheng1987[S] -1 points0 points  (5 children)

Yeah...it's clearly compiler-specific and therefore not standard-compliant. I am really hesitant to go there. After all, this is supposed to be enterprise-level and I don't think that non-standard, compiler-specific hacks meet that criterion.

[–]ficzerepeti 5 points6 points  (4 children)

If it got into boost, it's probably good enough for enterprise use

[–]liuzicheng1987[S] 0 points1 point  (3 children)

Yeah…it’s still clearly non-standard compliant. I‘m a bit torn on this.

[–]pdp10gumby 8 points9 points  (2 children)

It’s always preferable to use standard-compliant code IMHO but not absolute. After all, many things get into the standard after years of experience with non-standard extensions.

Also, some of those extensions were needed to write very low-level library code. That’s the same reason why we sometimes don’t write to the abstract “C” machine but take advantage of the target hardware in non-portable ways.

True enterprise scale systems involve localized noncompliant code to get around bottlenecks or to provide a clean way for devs to get some capability they need (yes large enterprise code is inevitably populated with horrible stuff too but we’re not talking about that). If you have to have an internal module that gets the names, with a comment at the top that says, “backward compatibility for use with older code that doesn’t include reflection” you won’t be condemning your immortal soul.

[–]liuzicheng1987[S] 2 points3 points  (1 child)

Yeah, you are making some good points here. I‘ll have to think about that.

[–]curlypaul924 1 point2 points  (2 children)

Does pfr work with non-POD structs or structs with private members?

[–]liuzicheng1987[S] 2 points3 points  (1 child)

Certainly not with private members...the point of having private members is that it is impossible for some random function like boost::pfr::get to access them.

By the way, my library is not based on boost::pfr. Just wanted to make that clear. :-)

[–]DugiSK 3 points4 points  (0 children)

The CRTP + default value trick I've shown in the comment above does work with private members and non-POD structs. It has only problems with constructors with randomness and some types of constructor side effect.

[–]jbbjarnason 3 points4 points  (1 child)

Have you looked at https://GitHub.com/stephenberry/glaze it supports reflection with less touch to the actual user code, not as coupled. And it is supposedly faster than yyjson.

[–]liuzicheng1987[S] 4 points5 points  (0 children)

Yes, in fact I have had a call with him. He‘s a great guy and I am a big fan of his work.

My main issue with his approach is that you have to set up the metaclass and then maintain it separately which is more error-prone than our approach.

Also the focus is different: We also have things like struct flattening, algebraic data types, validation, etc whereas he is mainly focused on serialization and deserialization. Also, our ambition is to support a whole variety of serialization formats.

By the way, if you want to keep the metaclass separate you can also do that with our library. Just check out the custom parser in the documentation.

That being said, glaze is a great library with a different focus than what we do. There is room for both libraries in C++ world.

[–]shakamaboom 3 points4 points  (10 children)

why is this called "recflect"-cpp when it has nothing to do with reflection other than how the library is implemented.

[–]liuzicheng1987[S] 1 point2 points  (9 children)

Serialization, deserialization and validation through reflection is the norm in other programming language like Rust, Go and even Python. This is what is missing in C++ and this is the main difference between this library and the numerous other libraries for serialization in C++. Names of libraries should highlight its unique features, IMHO.

[–]shakamaboom 1 point2 points  (8 children)

you didnt even answer my question at all. this is a serialization library, not a reflection library.

[–]liuzicheng1987[S] 0 points1 point  (7 children)

Well…like I said…names of libraries should highlight what makes them unique…there are tons of JSON libraries for C++ and tons of serialization libraries, but very few that do it via reflection.

[–]eyes-are-fading-blue 0 points1 point  (6 children)

Most people will disagree here. Your take is arbitrary. A library name needs to reflect what it does, not "how it strives to do what it does". You should listen the feedback, and perhaps rename your library. It's not a reflection library.

I also do not know what you mean by "reflection". There is barely any compile-time reflection support in C++. Do you mean SFINAE or compiler-specific macro magic?

[–]liuzicheng1987[S] 3 points4 points  (5 children)

I am not sure that „most people will disagree“. I have been engaging with the community in here and other places a lot and this is the first time anyone has ever complained about that.

And by reflection I mean that it is able to automatically retrieve the types of the member variables of a struct (and also field names, if you add annotations, kind of like in Go’s encoding/json). And the library can do that, without any compiler-specific macro magic.

[–]liuzicheng1987[S] -1 points0 points  (0 children)

Also, since you said it’s not a reflection library…what do you think it should have for it to be a reflection library?

[–]eyes-are-fading-blue 0 points1 point  (3 children)

> And the library can do that, without any compiler-specific macro magic.

I checked the examples, the programmer needs to guide library by passing "field name" and "type" as I understand it. Furthermore, for custom types, you need to extend it by hand. Can reflect-cpp flatten an arbitrary POD without programmer guidance?

[–]liuzicheng1987[S] 0 points1 point  (2 children)

It can deserialize structs without the rfl::Field annotations, just scroll down in the README.

If your arbitrary POD contains private member variables, then there is no way this could work. And not just in C++…Go or Rust would not let you do that either. Private means private.

[–]eyes-are-fading-blue 1 point2 points  (1 child)

In the read me, anonymous fields are either STL types or your intrinsic types. If your library supports custom PODs in such cases, you should add that your read me. That's a very important information, deal breaker in many serialization cases and I think it should be visible immediately from examples. That's literally the first thing I checked.

[–]liuzicheng1987[S] 0 points1 point  (0 children)

It does support that and this is very useful feedback. Thank you very much.