stephenberry / glaze

Extremely fast, in memory, JSON and interface library for modern C++
MIT License
1.17k stars 118 forks source link

Reference or incorporate simple_enum support #874

Open arturbac opened 7 months ago

arturbac commented 7 months ago

I asked You on other discussion for help You answered only to first question suggesting meta So i took of the constraint if constexpr (write_json_invocable<Opts, T, Ctx, B, IX>) to see what will fail. And it fails because of ambiguity even when having meta for partial specializations So for write and read(similar) I think You are missing glaze is missing this below What is interesting when I change to bounded enum so more constrained enum a subset of normal enum it works out of the box .. But for normal enum You need that fixes below : (I dont need them but for glaze I think it would be nice to respect own meta<> customisations by users

      template <class T>
         requires(std::is_enum_v<std::decay_t<T>> && !glaze_enum_t<T> 
         && !meta<std::decay_t<T>>::custom_write // <----- glaze is missing this
         )
      struct to_json<T>
      {
         template <auto Opts, class... Args>
         GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, Args&&... args) noexcept
         {
            write<json>::op<Opts>(static_cast<std::underlying_type_t<std::decay_t<T>>>(value), ctx,
                                  std::forward<Args>(args)...);
         }
      };

Do You agree ?

I took off constraint to check here

     template <>
      struct write<json>
      {
         template <auto Opts, class T, is_context Ctx, class B, class IX>
         GLZ_ALWAYS_INLINE static void op(T&& value, Ctx&& ctx, B&& b, IX&& ix) noexcept
         {
            // if constexpr (write_json_invocable<Opts, T, Ctx, B, IX>) {
               to_json<std::remove_cvref_t<T>>::template op<Opts>(std::forward<T>(value), std::forward<Ctx>(ctx),
                                                                  std::forward<B>(b), std::forward<IX>(ix));
            // }
            // else {
               // static_assert(false_v<std::remove_cvref_t<T>>, "Glaze metadata is probably needed for your type");
            // }
         }
      };

I am going to add galze support for simple enum so I will not need to use glz::enuemrate as I will use simple_enum::enum_view for automatic enumerations for all bounded_enums at once And simple_enum with galze can be symbiotically working and galze support would be then included in simple enum

glaze_json_enum_name.hpp

glaze_enum_name_ut.cc

It is up to You if You want to fix this, You can close issue from my point of view, just for Your information I am exploiting this as I need this automatic enumeration for json rpc with enumerations without bolder plate defining enum by enum by all enumerations glz::enuemration()

arturbac commented 7 months ago

I was wrong I need this fix, it just ignores my specialization of subset without fix ..

arturbac commented 7 months ago

same for read fix, then everything works

      template <class T>
         requires(std::is_enum_v<T> && !glaze_enum_t<T> && !meta<std::decay_t<T>>::custom_read)
      struct from_json<T>
      {
stephenberry commented 7 months ago

Thanks for finding this missing constraint. I'll try to do this soon, or if you submit a pull request I would be happy to merge it.

arturbac commented 7 months ago

I am working on it, need to write a check it meta is specialized and is set to true, easy job

stephenberry commented 7 months ago

Thanks for sharing your support for simple_enum in glaze. I think I will reference your library and the glaze support initially in documentation, so I'll keep this issue open until then. I haven't fully decided what enumeration support should look like in the context of future reflection and numerical handling in glaze, but I'll certainly be using your library as I consider design.

arturbac commented 7 months ago

If You want to fix it then bunch of info as alone I will not solve issue with partial spec it requires more knowlegde on glaze. As I mentioned there is still problem when You want to specialize only partially enumerations like with anything like any enums with my_class meta annottated

tempalte<enum_concept T>
struct my_class {};
tempalte<>
struct my_class<this_enum_type> { static cosntexpr bool value= true; };
template<typename T>
concept custom_serialized = requires {
    { T::value } -> std::convertible_to<bool>;
} && T::value;

before, I used enumerate_no_reflect for names for schema which was returning just schema with string and not pairs of string view name/enum value

But this was not working with json rpc call serialization and deserialization, i got error on try to use string on integer field. so schema was defined but no enum used as rpc_calls So I went back to battlefield, figured out that glz::enumerate cannot be used with std::apply so I wrote code that is returning expected result (doesn't matter how)

static constexpr auto value
    = simple_enum::convert_to_glz_tuple(simple_enum::make_glaze_tuple(color_values, color_names));

as enumerate is returning for manual definition

    auto valuea = glz::enumerate(
      "Red",
      Color::Red,  //
      "Green",
      Color::Green,  //
      "Blue",
      Color::Blue  //
    );

But i need enums that are not bounded to be processed as usual not by me. I found only working workaround is by definining them by myslef with double meta and double from to specializations

namespace glz
  {
template<simple_enum::enum_concept enumeration_type>
  requires simple_enum::bounded_enum<enumeration_type>
struct meta<enumeration_type>
  {
  static constexpr bool custom_write = true;
  static constexpr bool custom_read = true;

  static constexpr auto color_values{simple_enum::enum_names_array<enumeration_type>};
  static constexpr auto color_names{simple_enum::enum_values_array<enumeration_type>};
  static constexpr std::string_view name = simple_enum::enumeration_name_v<enumeration_type>;
  static constexpr auto value
    = simple_enum::convert_to_glz_tuple(simple_enum::make_glaze_tuple(color_values, color_names));
  };

template<simple_enum::enum_concept enumeration_type>
  requires(!simple_enum::bounded_enum<enumeration_type>)
struct meta<enumeration_type>
  {
  static constexpr bool custom_write = true;
  static constexpr bool custom_read = true;
  };
  }  // namespace glz

template<simple_enum::enum_concept enumeration_type>
struct from_json<enumeration_type> {
  template<auto Opts>
    requires simple_enum::bounded_enum<enumeration_type>
  static void op(enumeration_type & arg, is_context auto && ctx, auto &&... args);
  template<auto Opts>
    requires(!simple_enum::bounded_enum<enumeration_type>)
  static void op(enumeration_type & arg, is_context auto && ctx, auto &&... args);

  same for from_json

So when this is defined this way then this code of rpc call works

std::string schema = glz::write_json_schema<test_data_t>();
expect(false) << schema;
    glz::rpc::server<glz::rpc::method<"foo", test_data_t, test_data_t>> server;
    glz::rpc::client<glz::rpc::method<"foo", test_data_t, test_data_t>> client;

    server.on<"foo">([](test_data_t const & params) { return test_data_t{.enum_field = test_enum_e::baz}; });
    std::string uuid{"42"};
    auto [request_str, inserted] = client.request<"foo">(
      uuid,
      test_data_t{.enum_field = test_enum_e::bar},
      [](glz::expected<test_data_t, glz::rpc::error> value, glz::rpc::id_t id) -> void
      {
        // Access to value and/or id
      }
    );
    expect(false) << request_str;
    std::string response = server.call(request_str);
    expect(false) << response;

and return expected results with this approach but is uggly/hacky solution to overcome glaze problem .. I shouldn't specialize !bounded_enum

{"type":["object"],"properties":{"enum_field":{"$ref":"#/$defs/test_enum_e"}},"additionalProperties":false,"$defs":{"test_enum_e":{"type":["string"],"oneOf":[{"const":"foo"},{"const":"bar"},{"const":"baz"}]}}}

{"jsonrpc":"2.0","method":"foo","params":{"enum_field":"bar"},"id":"42"}

{"jsonrpc":"2.0","result":{"enum_field":"baz"},"id":"42"}
arturbac commented 7 months ago

An dbtw I had to consrtaint glaze_value_t too .. will do PR

stephenberry commented 7 months ago

Thanks for this detailed description of the issues you've encountered. I'll look into this in detail when I get a chance and try to refactor glaze so that it's easier to work with.

arturbac commented 7 months ago

Let me know if You need anything to examine related to enumerations, I can branch simple enum glaze support and test it and try to help

stephenberry commented 7 months ago

Sure thing, thanks!

dbraeckelmann commented 6 months ago

Integrated simple_enum into my setup and couldn't be happier, nice work @arturbac ! With these newer concept and reflection based approces C++ really begins to look quite fresh again without all the macro clutter and deep template expansion error messages of the olden days (we migrated our fairly large codabase from nlohmann_json + magic_enum to glaze + simple_enum just now ❤️).

arturbac commented 6 months ago

@stephenberry There is only one question left for future support of this integration I cannot use glz::enumerate for

template<simple_enum::bounded_enum enumeration_type>
struct meta<enumeration_type>
{
  static constexpr auto value = ..

as it doesn't work with std::apply So I had to write my own version

using glz::detail::Enum and glz::tuplet::tuple

What do You think is it going to be stable such approach for Your future chanegs or we should work on glz::enumerate ? As You can see in code I have at compile time 2 arrays of names and enum values

simple_enum::enum_names_array<enumeration_type> simple_enum::enum_values_array<enumeration_type>

that need to be converted to exactly same result as glz::enuemrate does for by hand definition

enumerate("Red", Color::Red, "Green", Color::Green, "Blue", Color::Blue //
   );
arturbac commented 3 months ago

@stephenberry ?

stephenberry commented 3 months ago

@arturbac, so with v3.0.0 Glaze now has an internal refl_info<T> with a compile time array of keys (string_views) and another compile time array of values (when using glz::enumerate). This should match much better with the layout of simple_enum

You can use the shorthand glz::refl<EnumType>.keys to access all keys and glz::refl<EnumType>.values for all values.

Let me know if you get simple_enum working with glz::enumerate.

Thanks!

arturbac commented 3 months ago

But it will not work with glz 2.x., I have to check if simple enum still works with glz 3.x

dbraeckelmann commented 3 months ago

Yes, simple-enum still works for me with glaze v3.1.0

arturbac commented 3 months ago

Yes, simple-enum still works for me with glaze v3.1.0

Nope it doesn't, at least tests of simple enum with glaze::json::rpc doesn't compile even with clang.

/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:104:40: error: no matching function for call to 'get_name'
  104 |          static constexpr auto alias = get_name<get<I>(meta_v<V>)>();
      |                                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:126:25: note: in instantiation of template class 'glz::detail::get_name_alloc<2, test_enum_e>' requested here
  126 |          return detail::get_name_alloc<I, V>::value;
      |                         ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:167:24: note: in instantiation of function template specialization 'glz::get_key_element<test_enum_e, 2UL>' requested here
  167 |             ((res[I] = get_key_element<T, value_indices[I]>()), ...);
      |                        ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:166:10: note: in instantiation of function template specialization 'glz::refl_info<test_enum_e>::(anonymous class)::operator()()::(anonymous class)::operator()<0UL, 1UL, 2UL>' requested here
  166 |          [&]<size_t... I>(std::index_sequence<I...>) { //
      |          ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:164:39: note: while substituting into a lambda expression here
  164 |       static constexpr auto keys = [] {
      |                                       ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:223:26: note: in instantiation of template class 'glz::refl_info<test_enum_e>' requested here
  223 |    constexpr auto refl = refl_info<T>{};
      |                          ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/json/schema.hpp:328:39: note: in instantiation of variable template specialization 'glz::refl' requested here
  328 |             static constexpr auto N = refl<T>.N;
      |                                       ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/json/schema.hpp:568:51: note: in instantiation of function template specialization 'glz::detail::to_json_schema<test_enum_e>::op<opts{10, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0}, glz::detail::schematic, std::map<std::string_view, glz::detail::schematic, std::less<void>>>' requested here
  568 |                   to_json_schema<val_t>::template op<Opts>(def, defs);
      |                                                   ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/json/schema.hpp:584:57: note: in instantiation of function template specialization 'glz::detail::to_json_schema<test_data_t>::op<opts{10, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0}, glz::detail::schematic, std::map<std::string_view, glz::detail::schematic, std::less<void>>>' requested here
  584 |       detail::to_json_schema<std::decay_t<T>>::template op<Opts>(s, *s.defs);

I can't put so often so much time to work on this integration from scratch. I need something we will agree, some interface/api that will maintain longer compatibility or at least will be easy to fix/upgrade. This is something I use at production but we can spend significant amount on minor feature used in project so at production we will be stuck for a long time probably with glaze 2.x.

arturbac commented 3 months ago

All I need is anything to use convert/declare below meta with 2 arrays of keys and values guaranteed to work in future, so instead of my own simple_enum::detail::convert_to_glz_enum, some glz::convert_to_glz_enum

I provide stable guaranteed arrays .. simple_enum::enum_names_array, and simple_enum::enum_values_array

This is still working with latest 2.9.5

template<simple_enum::bounded_enum enumeration_type>
struct meta<enumeration_type>
  {
  static constexpr bool custom_write = true;
  static constexpr bool custom_read = true;

  static constexpr std::string_view name = simple_enum::enumeration_name_v<enumeration_type>;
  static constexpr auto value = simple_enum::detail::convert_to_glz_enum(
    simple_enum::detail::
      make_glaze_tuple(simple_enum::enum_names_array<enumeration_type>, simple_enum::enum_values_array<enumeration_type>)
  );
  };
arturbac commented 3 months ago

It fails on glz::write_json_schema and I don't really even understand why, probably lack of constraints when I look at error produced makes this impossible for fast resolution.

changing simple_enum::detail::convert_to_glz_enum to glz::enumerate produces same error so probably at least new enumerate will work for such code

enum struct test_enum_e
  {
  foo,
  bar,
  baz
  };

consteval auto adl_enum_bounds(test_enum_e)
  {
  using enum test_enum_e;
  return simple_enum::adl_info{foo, baz};
  }
struct test_data_t
  {
  test_enum_e enum_field;
  };

auto schemares{glz::write_json_schema<test_data_t>()};
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:104:40: error: no matching function for call to 'get_name'
  104 |          static constexpr auto alias = get_name<get<I>(meta_v<V>)>();
      |                                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:126:25: note: in instantiation of template class 'glz::detail::get_name_alloc<2, test_enum_e>' requested here
  126 |          return detail::get_name_alloc<I, V>::value;
      |                         ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:167:24: note: in instantiation of function template specialization 'glz::get_key_element<test_enum_e, 2UL>' requested here
  167 |             ((res[I] = get_key_element<T, value_indices[I]>()), ...);
      |                        ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:166:10: note: in instantiation of function template specialization 'glz::refl_info<test_enum_e>::(anonymous class)::operator()()::(anonymous class)::operator()<0UL, 1UL, 2UL>' requested here
  166 |          [&]<size_t... I>(std::index_sequence<I...>) { //
      |          ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:164:39: note: while substituting into a lambda expression here
  164 |       static constexpr auto keys = [] {
      |                                       ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/core/refl.hpp:223:26: note: in instantiation of template class 'glz::refl_info<test_enum_e>' requested here
  223 |    constexpr auto refl = refl_info<T>{};
      |                          ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/json/schema.hpp:328:39: note: in instantiation of variable template specialization 'glz::refl' requested here
  328 |             static constexpr auto N = refl<T>.N;
      |                                       ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/json/schema.hpp:568:51: note: in instantiation of function template specialization 'glz::detail::to_json_schema<test_enum_e>::op<opts{10, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0}, glz::detail::schematic, std::map<std::string_view, glz::detail::schematic, std::less<void>>>' requested here
  568 |                   to_json_schema<val_t>::template op<Opts>(def, defs);
      |                                                   ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/json/schema.hpp:584:57: note: in instantiation of function template specialization 'glz::detail::to_json_schema<test_data_t>::op<opts{10, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0}, glz::detail::schematic, std::map<std::string_view, glz::detail::schematic, std::less<void>>>' requested here
  584 |       detail::to_json_schema<std::decay_t<T>>::template op<Opts>(s, *s.defs);
      |                                                         ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/json/schema.hpp:592:28: note: in instantiation of function template specialization 'glz::write_json_schema<test_data_t, opts{10, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0}, std::string &>' requested here
  592 |       const error_ctx ec = write_json_schema<T, Opts>(buffer);
      |                            ^
/home/artur/projects/simple_enum/tests/glaze_enum_name_ut.cc:95:25: note: in instantiation of function template specialization 'glz::write_json_schema<test_data_t, opts{10, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0}>' requested here
   95 |     auto schemares{glz::write_json_schema<test_data_t>()};
      |                         ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/reflection/get_name.hpp:172:31: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'P'
  172 |    consteval std::string_view get_name()
      |                               ^
/home/artur/projects/simple_enum/build/clang-debug-llvm/_deps/glaze-src/include/glaze/reflection/get_name.hpp:196:19: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'E'
  196 |    consteval auto get_name()
      |                   ^
stephenberry commented 3 months ago

Thanks for the additional info. What's changed in version 3 is that the value in glz::meta is no longer a tuple of pairs (tuples of 2 arguments) of names and enums. It is now a single tuple of all names and enum values. This would actually make your code for make_glaze_tuple cleaner.

The tuple for value should look like: {"blue", enum_type::blue, "red", enum_type::red}

I'm thinking an API in glz::meta that accepts an array of keys would make interfaces with libraries like simple_enum simpler. But, I need to devote some time to really dig into this.

arturbac commented 3 months ago

The tuple for value should look like: {"blue", enum_type::blue, "red", enum_type::red}

IMO It complicates this as I mentioned I already have separate keys and values arrays.

I'm thinking an API in glz::meta that accepts an array of keys would make interfaces with libraries like simple_enum simpler. But, I need to devote some time to really dig into this.

Would be nice, as for now I have to write another separate implementation for 3.x with interleaving arrays and maintain both 2.x and 3.x compat interfaces. This op of interleaving doesn't make sense it will just slow down compilation.

Anyway will do what necessary but would be nice to have portable interface/api in glaze as for now it looks like another unstable solution of interleaving keys,values. But If glz::enumarate will work that way accepting interleaved keys and values and will be guaranteed to work in future version this is all I need.

arturbac commented 3 months ago

BTW I need any version/feature macro to test to provide simple_enum working with 2.x and 3.x of glaze ...

arturbac commented 3 months ago

FYI:

template<simple_enum::bounded_enum enumeration_type>
struct meta<enumeration_type>
  {
  static constexpr bool custom_write = true;
  static constexpr bool custom_read = true;

  static constexpr std::string_view name = simple_enum::enumeration_name_v<enumeration_type>;
  static constexpr auto value = simple_enum::detail::tuple_to_enumerate(
    simple_enum::detail::
      interleave(simple_enum::enum_names_array<enumeration_type>, simple_enum::enum_values_array<enumeration_type>)
  );
  };

so this is compile time unfriendly

namespace simple_enum::inline v0_7
  {
  template<typename Array1, typename Array2, std::size_t... I>
  constexpr auto interleave_impl(Array1 const & a1, Array2 const & a2, std::index_sequence<I...>)
    {
    return std::tuple_cat(std::make_tuple(std::get<I>(a1), std::get<I>(a2))...);
    }
  template<typename Array1, typename Array2>
  constexpr auto interleave(Array1 const & a1, Array2 const & a2)
    {
    return detail::interleave_impl(a1, a2, std::make_index_sequence<std::tuple_size_v<Array1>>{});
    }

  template <typename tuple_t, std::size_t... is>
  constexpr auto tuple_to_enumerate_impl(tuple_t&& tuple, std::index_sequence<is...>) {
      return glz::enumerate(std::get<is>(std::forward<tuple_t>(tuple))...);
  }

  template <typename tuple_t>
  constexpr auto tuple_to_enumerate(tuple_t&& tuple) {
      constexpr auto tuple_size = std::tuple_size_v<std::remove_reference_t<tuple_t>>;
      return detail::tuple_to_enumerate_impl(std::forward<tuple_t>(tuple), 
                                             std::make_index_sequence<tuple_size>{});
  }

  }  // namespace detail
  }  // namespace simple_enum::inline v0_7
arturbac commented 3 months ago

Simplified, looks like it is working, IMHO interleave_and_enumerate should be in glaze not in simple enum ... named as glz::enumerate overload .. So You can maintain in the future any internal changes.

namespace glz
  {
template<simple_enum::bounded_enum enumeration_type>
struct meta<enumeration_type>
  {
  static constexpr bool custom_write = true;
  static constexpr bool custom_read = true;

  static constexpr std::string_view name = simple_enum::enumeration_name_v<enumeration_type>;
  static constexpr auto value = simple_enum::detail::
    interleave_and_enumerate(simple_enum::enum_names_array<enumeration_type>, simple_enum::enum_values_array<enumeration_type>);
  };

namespace simple_enum::inline v0_7::detail
  {
    template<typename array1_t, typename array2_t, std::size_t... is>
  constexpr auto interleave_and_enumerate_impl(array1_t const & a1, array2_t const & a2, std::index_sequence<is...>)
    {
    return glz::enumerate(std::get<is>(a1)..., std::get<is>(a2)...);
    }

  template<typename array1_t, typename array2_t>
  constexpr auto interleave_and_enumerate(array1_t const & a1, array2_t const & a2)
    {
    static_assert(std::tuple_size_v<array1_t> == std::tuple_size_v<array2_t>, "Arrays must have the same size");
    return detail::interleave_and_enumerate_impl(a1, a2, std::make_index_sequence<std::tuple_size_v<array1_t>>{});
    }
}
arturbac commented 3 months ago

Summary

stephenberry commented 3 months ago

This op of interleaving doesn't make sense it will just slow down compilation.

Agreed, I don't think we need interleave_and_enumerate. I just need to add support for a keys array in glz::meta. I can then use a concept to check if this field exists, and if it does then I don't use the interleaved approach, but rather keys and values.

I'll plan to add support for this soon.

As mentioned version macro is a MUST ... I cant for now provide support for 3.x with braking support for 2.x

Definitely, I'll add a feature test macro once we figure out the path forward.

Thanks for bringing all this up. There were a lot of things affected by version 3 changes, and while it is for the better, it's going to take a bit of time to get all the side effects worked out. Thanks for your help!

stephenberry commented 3 months ago

Another question: How should feature test macros work as we move towards C++20 modules? Since we can't export macros from C++20 modules.

arturbac commented 3 months ago
  1. IMHO I can already use directly glz::enumerate without interleaving, need to work on this as I can skip producing arrays at all.
    • I'll provide option for explicit simple_enum cmake configuration against glz 2.x, <3.0..3.1), >=3.2, with default to use auto detect based on Your feature test macro.
    • I am not going to use modules at production as there is not solved chicken and egg problem in code parsing libraries like libclang. You need code to be compilable to generate module files, and You need them to get module definition and intellisense suggestions when at same time writing this code ..
    • But I'll provide probably support for simple_enum as module as this makes sense when simple_enum is used as external dependency in projects.
arturbac commented 3 months ago

FYI: This code is wrong, I can't call glz::enumerate with std::index_sequence, because it calls Enum detail::Enum{tuplet::tuple{args...}}; so result is producing multiple calss to glz::enumerate. I still need to produce Enum{tuple by myself, so problably You approach by checking for keys,values array would be the BEST as I will not have to use glz::detail internals and risk again incompatibility

arturbac commented 3 months ago

This time it is ok, implemented taking Your information All i need to change for now is make_glaze_tuple_impl in simple_enum.

#if GLZ_2_x
  template<typename Array1, typename Array2, std::size_t... I>
  constexpr auto make_glaze_tuple_impl(Array1 const & a1, Array2 const & a2, std::index_sequence<I...>)
    {
    return std::make_tuple(make_glz_enum_tuple(a1[I], a2[I])...);
    }
#elif GLZ_3_1_x
  template<typename Array1, typename Array2, std::size_t... I>
  constexpr auto make_glaze_tuple_impl(Array1 const & a1, Array2 const & a2, std::index_sequence<I...>)
    {
    return std::tuple_cat( std::make_tuple(a1[I], a2[I])...);
    }
#else //GLZ_3_2x
 // here we will go with You approach i think with declaring in meta keys, values arrays
#endif
arturbac commented 3 months ago

So I can't avoid using std::tuple and tuple_cat as I need to iterate over values of source arrays from simple enum. I can improve it by removing mid arrays using directly enum_value_at_index, enum_name_at_index from this PR, but if we are going with declaring arrays at glz::meta then this op doesn't have sense.

arturbac commented 3 months ago

I have ready PR for 2.x , 3.1 . All we need to do is make best choice for future with glz 3.2 that would be maintained inside glaze without any changes to simple_enum I already have many github workflow test variations .. would be nice to end adding them with glaze 3.2 ;-)

stephenberry commented 3 months ago

Thanks so much for your work on this. I will prioritize this soon, but I have a few other pressing things to work on first.

arturbac commented 3 months ago

I was thinking yesterday that better than keys value arrays at glz::Meta would be providing 3 additional consteval static member functions in glz::Meta specialization.

namespace glz
  {
template<simple_enum::bounded_enum enumeration_type>
struct meta<enumeration_type>
  {
static consteval auto size() -> size_t; // number of enumeration elements
static consteval auto keys(size_t ix) string_view ;
static consteval auto values(size_t ix) -> enumeration;

static constexpr bool custom_write = true;
static constexpr bool custom_read = true;

static constexpr std::string_view name;
}}

because it allows avoid at compile time generating temporary arrays, and is more universal, I can return values directly from source of information. When someone will have array of keys and values and no matter of form separate or interleaved he would be able to return them too with those functions. You can write concept checking for such meta layout and use it instead of expecting static constexpr auto value with glz::Enum

arturbac commented 3 months ago

For now I just merged in simple_enum support as is for 2.x and 3.1. In future when You provide universal api with proposed functions or arrays I will use it.

With such layout of meta consteval functions You can use code from my implementation

  template<typename... Args>
  constexpr auto convert_to_glz_enum(std::tuple<Args...> const & stdTuple)
    {
    return glz::detail::Enum{
      std::apply([](auto &&... args) { return glz::tuplet::tuple<std::decay_t<Args>...>{args...}; }, stdTuple)
    };
    }

  template<typename enumeration_type, std::size_t... ix>
  constexpr auto glaze_tuple_pairs(std::index_sequence<ix...>)
    {
    return std::tuple_cat(std::make_tuple(
      meta<enumeration_type>::keys(ix), meta<enumeration_type>::values(ix)
    )...);
    }

  template<typename enumeration_type>
  constexpr auto make_glaze_tuple()
    {
    constexpr auto size = meta<enumeration_type>::size();
    return convert_to_glz_enum(glaze_tuple_pairs<enumeration_type>(std::make_index_sequence<size>{}));
    }
arturbac commented 3 months ago

In regard of modules feature test macro question: Do You know how stl is handling feature test macros with modules ? there are plenty of them and not everything is already implemented and supported for c++20 and c++23. Most of them is defined in headers and not predefined by compilator especially for c++ library and not language itself.

stephenberry commented 3 months ago

So, recently I added the GLZ_ENUM macro to make internal Glaze enumerations compile really fast.

This uses ADL lookup of a nameof function and another free function enum_names to handle enumeration reflection.

This approach completely bypasses glz::meta and could be used by simple_enum as well.

stephenberry commented 3 months ago

You can search for where Glaze uses std::is_enum_v for examples of usage.

I still have to add back compile time hash maps for the parsing side. But, I plan to do that soon after improving the new hashing mechanics.

arturbac commented 3 months ago

So You taint every enum with special macro def enum struct EnumType : uint32_t instead of normal enumeration definition. then declare constexpr decltype(auto) enum_names(T&&) noexcept returning array of names and constexpr std::string_view nameof(EnumType value) noexcept

I don't see where universal approach would be here as it bypasses any public glz api and uses internal knowledge of glz. First of all if someone uses GLZ_ENUM he doesn't need simple_enum at all with glz.

I would like to stick to public API of glz and use meta<> If You want in meta such functions sure I can do it


namespace glz
  {
template<simple_enum::bounded_enum enumeration_type>
struct meta<enumeration_type>
  {
static constexpr decltype(auto) enum_names() noexcept;
static constexpr auto nameof(enumeration_type value) noexcept -> std::string_view;

static constexpr bool custom_write = true;
static constexpr bool custom_read = true;

static constexpr std::string_view name;
}}
stephenberry commented 3 months ago

This macro is not intended for general use. It is to make the internals of Glaze build faster. What is general is the nameof and enum_names functions, which could be used generally for any enumeration. But, I realize now that this approach doesn't work well with simple_enum and agree that the glz::meta approach you proposed is better.

stephenberry commented 3 months ago

I would be curious what your opinion is of the new conjure_enum? What I appreciate is that it appears to work with both scoped and unscoped enums.

I'm still going to make a public reflection API that simple_enum can use, but I think we have the tools to now build full pure reflection for enums directly into Glaze without the user needing to edit their enums or add any additional code.

arturbac commented 3 months ago

simple enum works with scoped and unscoped enums too all I do is to boundary them to make process of reflection efficient and well defined (magic enum goes out of scope of enumeration which is UB). I am not going to score other projects, but they mention performance and don't not provide any single comparison of such claim.

stephenberry commented 3 months ago

What I mean is that the user doesn't have to add boundaries, which is useful for working with third party libraries.

Yeah, I only briefly looked at the code, so I'm curious how it would actually perform.

arturbac commented 3 months ago

What I mean is that the user doesn't have to add boundaries,

So they are probably going over/under enum range which is UB and for unscoped enum with clang even produces compiler error/warning. There is no way to find enum bounds with c++20 other than linear scan above and under or declaring them. Anyway c++26 will hopefully render all such libraries (including mine) useless.

stephenberry commented 3 months ago

Thanks for explaining the limitation. Yeah, C++26 should be great, but until then we get to make fun hacks.

arturbac commented 3 months ago

I wrote simple enum not to compete with other but for:

later It improved with:

those 2 features, glz integration, generic error declaration with performance are for me most important, enum_name and enum_cast , and others are minor features serving only those more important features. In my production code I don't even use simple_enum::enum_name directly as i don't have to, it is done by std::formatters of generic error category or glz::from_json and to_json specializations.

stephenberry commented 3 months ago

Yeah, I like simple_enum's approach and handling error categories is great!

stephenberry commented 3 months ago

Added feature testing macros and constexpr bools in #1191

arturbac commented 3 months ago

1) So this actually only allows checking if approach for glaze 3 here should be used ? and doesn't add alternative glz::meta layout ?

2) When You will work on new meta layout I would ask You to think if You really need array of names()

static constexpr decltype(auto) enum_names() noexcept -> array<string_view>;
static constexpr auto nameof(enumeration_type value) noexcept -> std::string_view;

and maybe something like this would be enough as generating constexpr array of string_views will always cost a lot during compilation a) option A is to use indexing

static consteval auto size() -> size_t; // number of enumeration elements
static consteval auto name(size_t ix) string_view ;
static constexpr auto nameof(enumeration_type value) noexcept -> std::string_view;

b) option b is to allow return array of some meta info with convertible only to string_view enum_names() -> array where T is only covertible_to I for example have some array of meta info with custom element as this is faster during compilation than using array of string_views (I optimised this with -ftime-trace)

anders-wind commented 1 month ago

We are having problems with upgrading to glaze 3 due to exactly the has_nameof concept.

https://godbolt.org/z/TTYso3vMq <- minimally reproducible failing compilation.

We are using the https://github.com/Neargye/nameof library which defines a namespace nameof and therefore causes the has_nameof to fail:

/opt/compiler-explorer/libs/glaze/trunk/include/glaze/reflection/enum_macro.hpp:16:16: error: expected primary-expression before '(' token
   16 |          nameof(T{})

I don't know what the approach should be to solve this.