Skip to content
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

is it a way to serialize field names? #90

Closed
denzor200 opened this issue Aug 8, 2021 · 18 comments
Closed

is it a way to serialize field names? #90

denzor200 opened this issue Aug 8, 2021 · 18 comments

Comments

@denzor200
Copy link
Contributor

#ifdef _STR
#  error _STR already defined
#endif
#define _STR(S) BOOST_METAPARSE_STRING(S){}

struct gps_position
{
    field<int, (name=_STR("degrees"))> degrees;
    field<int, (name=_STR("minutes"))> minutes;
    field<float, (name=_STR("seconds"))> seconds;
};

We need to:

  1. field<T, A> template class, this class is just a wrapper to T with std::reference_wrapper-like(??) iface.
  2. name object with overloaded operator=(std::string_view), and this overloaded operator must be constexpr

This short example:
https://godbolt.org/z/nT4bdrnKc
Interface of field_t<T,A> class is not ideal, but this short example shows that structure with named fields are possible.

@denzor200
Copy link
Contributor Author

Also, this approach allows us to to make field with N names.
For example, each field has:

  • default name
  • boost.serialization's name
  • Some orm's name
struct gps_position
{
    field<int, (name=_STR("degrees")), (serialization_name=_STR("sdegrees")), (orm_name=_STR("odegrees"))> degrees;
    field<int, (name=_STR("minutes")), (serialization_name=_STR("sminutes")), (orm_name=_STR("ominutes"))> minutes;
    field<float, (name=_STR("seconds")), (serialization_name=_STR("sseconds")), (orm_name=_STR("oseconds"))> seconds;
};

Also we can bind a C++ keyword as field name:

struct A
{
   field<std::string, (name=_STR("new"))> _new;
};

@denzor200
Copy link
Contributor Author

Continuing the ORM topic.. As a field_t parameter, we can pass any attribute, not just a name:

struct User {
    field<int,                             name=_STR("id"), autoincrement, primary_key>          id;
    field<std::string,                     name=_STR("login"), unique>                           login;
    field<int,                             name=_STR("birth_date")>                              birthDate;
    field<std::unique_ptr<std::string>,    name=_STR("image_url")>                               imageUrl;
    field<int,                             name=_STR("type_id")>                                 typeId;
};

@X-Ryl669
Copy link

How is is better than a macro then ?

struct User {
  F(int, id);
  F(std::string, login);
};

@schaumb
Copy link

schaumb commented Jun 10, 2023

I wrote a demo where gcc and msvc can "extract" the members name without any macro [1], from C++20 standard based on pfr.
With this technique clang will not work (only for constexpr default initializable classes)[2]**.

[1] https://godbolt.org/z/4nGGh6a4f

struct Any {
    Any() {};
};

struct XClass {
    int member1;
    Any this_is_a_name; // not constexpr constructible
    std::reference_wrapper<char> c; // not default constructible
};

int main() {
    static_assert("member1"        == name<0, XClass>());
    static_assert("this_is_a_name" == name<1, XClass>());
    static_assert("c"              == name<2, XClass>());
}

[2] clang is not fully C++20 compliant.
kép


** but there is a method where "constexpr literal types" works on clang too. See my newer comments.

@denzor200
Copy link
Contributor Author

@schaumb Fantastic! Just two questions:

-Isn't there any UB in manipulating pointers of union non active member?
-Using this way will lead to creating a lot of objects with static storage duration. Wouldn't it be worsen for binaryes size?

@apolukhin what do you think about it?

@schaumb
Copy link

schaumb commented Jun 13, 2023

@denzor200

  • UB: It's all made in compile time, so if there is an UB, the compiler will not compile (or it will crash). The union not active member pointers are not UB as far as I know (at least until its value not read or write)
  • I cannot see any object in static storage, only the names: https://godbolt.org/z/xz9n446vG

@X-Ryl669
Copy link

Impressive!

Just a question, this seems to rely on compiler storing the complete type of the union field in __FUNCSIG__ or __PRETTY_FUNC__. Those are not standard, right ?

I don't understand how the union's f member captured from the destructured binding of T into a std::tuple still reference the member's name and not its type. Can you explain this part ?

@schaumb
Copy link

schaumb commented Jun 13, 2023

@X-Ryl669

  • It's rely on not standard macro like you wrote. But it is widely supported:
    • GCC, Clang, MSVC, Apple Clang, EDG eccp, Intel C++, (and other gcc variants; and probably I missed some)

(note: As a fallback RUNTIME library, it can be use typeid(template_struct<member_pointer>).name() which stores the info (at least gcc; MSVC not); probably a demangle function to this name is necessary).

  • The only reference here is at structured binding but the variables weren't read or wrote, only accessed the memory addresses (pointers). Those pointers are copied to the tuple.
    • The tuple not stores type, but constexpr static variable's member addresses. When the <auto* p> templated function requests the __PRETTY_FUNCTION__ with the p value any of member variable address, the text result be like p = (& union_val.f.member1). So we don't query the type of member, but the constexpr variable member location.

@schaumb
Copy link

schaumb commented Jun 19, 2023

I tried to make it work on clang too, and I succeeded:
https://godbolt.org/z/e3ba6Pd7q
Only one caveat, clang only accepts literal types (constexpr initializable types).

If someone creates a lib (or PR) from this "hack", please mention me/my github acc.

@denzor200
Copy link
Contributor Author

@schaumb I think I will create the PR having finished the traveling. I don't know when will it be, maybe in a month. No problem about copyrights, you will be mentioned :)
And thank you for discovering a great way to reach the feature that we were waiting for.

@falemagn
Copy link

falemagn commented Jun 21, 2023 via email

@X-Ryl669
Copy link

X-Ryl669 commented Jun 21, 2023

The code is doing this:

template<class T>
constexpr auto members() {
    // TODO for all 1..N
    auto& [m1, m2, m3] = union_val<T>.f;
    return std::tuple{&m1, &m2, &m3};
}

where union_val<T>.f is a XClass or the structure you want to analyze.
That's instantiated as:

template<>
union union_type<XClass>
{
  inline constexpr ~union_type() noexcept
  {
  }
  char c = {};
  XClass f;
};

template<>
inline constexpr const union_type<XClass> union_val<XClass> = {{}};

It's taking a reference on each member of the structure XClass, not the union, and from this reference, it computes their addresses when constructing the std::tuple. I'm not sure if the default construction of the union makes a c or a f but it's something that quite simple to fix (exchange the order of the field in the union declaration), except that it requires XClass to be default constructible (which should be the case for simple aggregate structures but not in the given example).

It works well also with:

template <typename T>
union union_type {
  constexpr ~union_type() {}
  T f = {};
  char c;
};

but you'll need to make f default constructible.

If I understand correctly, the destructured binding here is perfectly fine, and it's used in PFR also to detect the field type and position/count.

IMHO, the only UB is the PRETTY_FUNC and other special macro that aren't standard compliant. But since there is no standard compliant way to capture a type and convert it to its textual representation, I guess that a "compiler driver" code that's worth using.

@schaumb
Copy link

schaumb commented Jun 21, 2023

[Note] The members function can be made consteval, so it will no effect on runtime. -> No UB on runtime

@schaumb
Copy link

schaumb commented Jun 21, 2023

[Note2] @X-Ryl669 The whole union wrapper thing made because not just constexpr initializable structures members can be reflected.
union_type is unnecessary if we don't use it for this reason.

@X-Ryl669
Copy link

X-Ryl669 commented Jun 21, 2023

Yes, I understood this. If @falemagn wanted no UB while using the union, it could have been specialized by the "is_default_constructible" trait (as an additional template parameter that's partially specialized), so in that case, no access to the non-constructed member would be done.

But, that way, as you said, you can't reflect non-default-constructible structure. Another option would be to declare a template const T & make_reflector<T>() that's never called and store a std::reference_wrapper in the union so you can extract the type that's never constructed, a bit like (not tested):

template <typename T>
union union_type {
constexpr ~union_type() {}
  std::reference_wrapper<T> f;
  char c;
};

template <typename T>
constexpr T & make_reflector<T>();


template<>
inline constexpr const union_type<XClass> union_val<XClass> = {.f = make_reflector<T>{}};

template<class T>
constexpr auto members() {
    // TODO for all 1..N
    auto& [m1, m2, m3] = union_val<T>.f;
    return std::tuple{&m1, &m2, &m3};
}

or even simpler:

template <typename T>
constexpr inline std::reference_wrapper<T> ref_val{make_reflector<T>()};

template<class T>
constexpr auto members(T & f) {
    // TODO for all 1..N
    auto& [m1, m2, m3] = f;
    return std::tuple{&m1, &m2, &m3};
}

// To be called as:
members(ref_val<T>)

Or something in the same vein. As you said, since the code is never called at runtime, I doubt it would be an issue with UB here, even the way you've written it (and a compiler would warn you if it was). I don't know all the tricks that can be used to create a "virtual" instance of a structure without actually creating any real instance, yet, I was never bit by this in SFINAE code, so I would assume it's pretty safe.

@schaumb
Copy link

schaumb commented Jun 22, 2023

Thanks @X-Ryl669 !
This helped me to create an union and ub free solution, and without requiring constexpr initializing:

template <typename T>
extern T obj;

template<class T>
consteval auto members() {
    // TODO for all 1..N
    auto& [m1, m2, m3] = obj<T>;
    return std::tuple{&m1, &m2, &m3};
}

Clang prints warning (undefined-var-template), but that can be ignore

https://godbolt.org/z/oE7o1Mjx9

or other version if name() returns with a const char* : https://godbolt.org/z/WxPqrWE63

@denzor200
Copy link
Contributor Author

Hello everyone! I've posted for review PR based on the "hack" discussed before: #129
Any feedback would be great. Thanks!

@schaumb
Copy link

schaumb commented Nov 22, 2023

This issue can be closed because the get_name functionality is merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants