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

C++ module interface #1586

Open
msqr1 opened this issue Feb 1, 2025 · 46 comments
Open

C++ module interface #1586

msqr1 opened this issue Feb 1, 2025 · 46 comments

Comments

@msqr1
Copy link

msqr1 commented Feb 1, 2025

I see your project using C++23, it doesn't have a C++ module support yet, and I could provide one using https://github.com/msqr1/importizer ? I would be happy to do this if you want!

@stephenberry
Copy link
Owner

I'd love to get a branch working with module support. Would it work across GCC, MSVC, and Clang? If you want to try and submit a pull request I'd love to see how much progress can be made. I do expect we might need to submit some bug reports because this is a large and complex project.

FYI, right now some of the Glaze tests do not build with the latest MSVC due to a recent compiler bug they introduced in 17.2. You can see #1448. We're pending the release of a fix.

@arturbac
Copy link
Contributor

arturbac commented Feb 2, 2025

It can easy be done with better quality by hand than any auto converter to work with clang and gcc with CMake. C++ part is easy to do so. msvc AFIK does not have fully working modules at this time. CMake 3.31 works ok with modules.
Worst is there is no support for code completion in various ide for imported modules so I am sceptic for using modules at production in large project and lose all ide code completion support. I makes sense to use modules for ex release build but this is not a case I would optimise for myself, rather I would expect optimising development for which it will not yet work in IDE.

such case works ok on clang

shared lib
my_library.cxx

module ;
#include <string>

export module my_library;

export void do_something();

export template<typename T>
struct MyTemplate
  {
  T value;
  };

export struct MyStruct
  {
  int data;
  std::string str;  
  };

my_library.cpp

module;

#include <print>

module my_library;

void do_something() { std::print("Doing something!\n"); }
add_library(my_library SHARED)


target_sources(
  my_library
  PRIVATE src/my_library.cpp
  PUBLIC FILE_SET my_module_interface TYPE CXX_MODULES FILES
         "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/my_library.cxx>"
)

set_target_properties(
  my_library
  PROPERTIES CXX_STANDARD 23
             CXX_STANDARD_REQUIRED YES
             CXX_EXTENSIONS OFF
             CXX_SCAN_FOR_MODULES YES)

and using of module

import my_library;

int main()
  {
  do_something();

  MyTemplate<int> temp{42};

  MyStruct struct_ex{123, ""};

  return 0;
  }
cmake_minimum_required(VERSION 3.31)
project(my_project CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)


add_subdirectory(my_library)

add_executable(my_executable src/main.cpp)
target_link_libraries(my_executable PRIVATE my_library)

install(TARGETS my_executable RUNTIME DESTINATION bin)
install(TARGETS my_library LIBRARY DESTINATION lib)

@arturbac
Copy link
Contributor

arturbac commented Feb 2, 2025

More complicated it becomes in cmake configuration to maintain backward compatibility and option either to declare interface header only library or module library
example, I am experimenting with simple_enum

option(SIMPLE_ENUM_ENABLE_MODULE "Export c++ module" ON)
if( SIMPLE_ENUM_ENABLE_MODULE)
  add_library(simple_enum)
  target_sources(
      simple_enum
      PRIVATE FILE_SET HEADERS BASE_DIRS
              $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
      PUBLIC FILE_SET CXX_MODULES FILES
             "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/module/simple_enum.cxx>"
    )
  set_target_properties(
    simple_enum
    PROPERTIES CXX_STANDARD 23
               CXX_STANDARD_REQUIRED YES
               CXX_EXTENSIONS OFF
               CXX_SCAN_FOR_MODULES YES)
else()
  add_library(simple_enum INTERFACE)
  target_sources(
    simple_enum
    INTERFACE FILE_SET
              HEADERS
              BASE_DIRS
              $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
              $<INSTALL_INTERFACE:include>)

  target_compile_features(simple_enum INTERFACE cxx_std_23)
endif()
if(SIMPLE_ENUM_OPT_IN_STATIC_ASSERTS)
  target_compile_definitions(simple_enum INTERFACE SIMPLE_ENUM_OPT_IN_STATIC_ASSERTS=1)
endif()

@arturbac
Copy link
Contributor

arturbac commented Feb 2, 2025

BTW It is not about 'converting' into modules but to add optional module support , so additional c++ files declaring exports that can be ignored with normal pure include interface
EDIT:
And it is not about adding export at the top of includes and export everything including detail namespace, when someone does that it misses the point of using modules to export only public interface.
For example I add file exporting selected elements of simple_enum only, skipping all private type and data.

module;
#include <simple_enum/simple_enum.hpp>
#include <simple_enum/enum_index.hpp>
#include <simple_enum/enum_cast.hpp>
#include <simple_enum/generic_error_category.hpp>
#include <simple_enum/generic_error_category_impl.hpp>
#include <simple_enum/ranges_views.hpp>
#include <simple_enum/std_format.hpp>

export module simple_enum;

export namespace simple_enum
  {
using simple_enum::v0_8::enum_concept;
using simple_enum::v0_8::bounded_enum;

using simple_enum::v0_8::adl_info;
using simple_enum::v0_8::info;
using simple_enum::v0_8::enum_name;
using simple_enum::v0_8::enum_name_t;

using simple_enum::v0_8::enum_index_error;
using simple_enum::v0_8::enum_index_t;
using simple_enum::v0_8::enum_index;
using simple_enum::v0_8::consteval_enum_index;

using simple_enum::v0_8::enum_cast_error;
using simple_enum::v0_8::enum_cast_t;
using simple_enum::v0_8::enum_cast;
[..]
  }  // namespace simple_enum

@msqr1
Copy link
Author

msqr1 commented Feb 2, 2025

Don't worry, importizer aims to reduce much trivial work only, of course we still have to do some fixing and cleaning up ourselves, it's not as smart as a human :(.
The selling point is that the tool can do this kind of modularization for backward compatibility, optionally switching to modules with a single macro definition: https://github.com/msqr1/importizer/blob/main/Example.md#transitional-compilation-example . So you can still keep literally everything as-is.

In addition, it is not wrapping the header interface to export things, it is literally going to modularize it into a hybrid interface, supporting the global module migration.

But yeah, I do agree that linting is a bit annoying right now.

@arturbac
Copy link
Contributor

arturbac commented Feb 2, 2025

IMHO mixing module and non module code with conditional macro definitions is low quality code hard to maintain and at the end difficult to read.

EDIT:
From last talk about module support is to not create files with *m mdoule extensions, just pure normal cc, cpp, cxx because AFIR there are already some problems with support for that in compilers.

@arturbac
Copy link
Contributor

arturbac commented Feb 2, 2025

Btw for modules support You need to have 3.28

cmake_minimum_required(VERSION 3.28..3.31)

@msqr1
Copy link
Author

msqr1 commented Feb 2, 2025

Or you can just modularize it completely and maintain both the header ones and the module one. That would be tiring and repeating lots of code, though IMHO. Or you can just wrap it, but that wouldn't help with the global module migration :(.

About maintainability, I find that aside from the section on top of the file, everything else is the same, isn't it? At the end of the day, let's try and see how Stephen feels about it.

Also, the tool provides many options, including toggling the output extension. See more in the setting section of the README.

@arturbac
Copy link
Contributor

arturbac commented Feb 3, 2025

I will give You and idea if You are a dev of this tool and maybe interested in that ..

With cmake You can get targets and its sources etc..
With libclang You can use AST to examine parsed code.
IMHO valuable would be a tool generating module file exporting everything for specified namespace and including proper incldues for given target from ccmake and it sources as below

module;
// include deps required for exported type
export module my_name;
// scanned output of types, classes, functions, and variables.

That would be very valuable tool for ppl with large projects, because You would be able to understand the result and maintain it.

@arturbac
Copy link
Contributor

arturbac commented Feb 3, 2025

In ideal world we would liek to just convert our projects into module versions , but we ca not and we will not be able to do so for a long time .. IMHO separate files with explicit content being exported is better to maintain that conditional macros everywhere in code ..

@arturbac
Copy link
Contributor

arturbac commented Feb 3, 2025

Or you can just modularize it completely and maintain both the header ones and the module one.

This is not feasible to maintain and not rational double the possibility of introducing bugs.

@arturbac
Copy link
Contributor

arturbac commented Feb 3, 2025

Btw the decision is in Stephan's hands, I have nothing to do with it except to advise.

@msqr1
Copy link
Author

msqr1 commented Feb 3, 2025

Yeah, I am also planning for that, not only modularizing, but also wrapping the header intetface into a module. Right now I have only modularization (because I want to help with the migration), so I didn't implement wrapping yet, but it is also a good option to have.

Although I feel like this is done by hand really easily, just do some #include, and a big export block, right? Actual modularizing is way harder, that's why I made the tool.

IMHO separate files with explicit content being exported is better to maintain that conditional macros everywhere in code

This is right, but the hybrid form doesn't have conditional macros everywhere. It's only in the top section, there is no more conditional macros down below.

Also, we shouldn't be discussing extension to importizer here, can you make an issue there and we'll continue?

@arturbac
Copy link
Contributor

arturbac commented Feb 3, 2025

This is right, but the hybrid form doesn't have conditional macros everywhere. It's only in the top section, there is no more conditional macros down below.

What about 90% of most incldues content in detail namespaces ? export everything ?

@msqr1
Copy link
Author

msqr1 commented Feb 3, 2025

There is an EXPORT macro (like assert) that is defined to be export when modules are on, and nothing when it's off. This is done in a separate file (no pollution) that is included automatically, so you can just place the EXPORT before whatever you want and be done! It's just a minimal replacement for the export keyword. Don't worry, it will get just a bit dirty at the top and no more.

A lot of people are probably confused like you, so maybe I should add an FAQ section 😆.

@arturbac
Copy link
Contributor

arturbac commented Feb 3, 2025

@stephenberry I finished module support in simple_enum except glaze dedicated header ..
So I wonder how to introduce module support for glaze in simple_enum with this glaze dedicated header in simple enum.
I would export it as separate module ie simple_enum.glaze but ..

That requires some version of glaze to be used for exporting empty/hidden content module, and then this restricts users to use that version of glaze ..
So making module for this make an dependency injection early
I am not 100% sure but it will look like in the future but maybe something like that ..

module;
import glaze;
#define SIMPLE_ENUM_SKIP_INCLUDING_GLAZE // for below header skipping inclusion of glaze headers
#include <simple_enum/glaze_json_enum_name.hpp> // for declaring from/to specializations

export simple_enum.glaze;
// everything hidden

@arturbac
Copy link
Contributor

arturbac commented Feb 3, 2025

There is an EXPORT macro (like assert)

A lot of people are probably confused like you, so maybe I should add an FAQ section 😆.

I am sorry but I didn't pay much attention to Your project because I don't use macros 99% of the time.
When there is some solution offered to me with macros I usually just ignore it ...
Only in rare cases when there is no other option to use pure language i use macros but not in such case as whole project with conditionals and macro expansion.

@arturbac
Copy link
Contributor

arturbac commented Feb 3, 2025

From my module simple enum experiment with modules

  • clang-20 libc++, libstdc++14 - everything is perfectly OK
  • clang-19 libc++ - OK
  • clang-19 libstdc++14 - FAILS in stdc++ ranges g++-v14/ranges:9468:11: error: type alias template redefinition with different types
  • gcc-14 - FAILS with some nonsense error: failed to read compiled module: Bad file data
  • msvc - even does not support c++23 static operator()

IMHO it is a long road to stable modules and it is not yet worth work except for clang witch is in good shape so far.

@stephenberry
Copy link
Owner

Thanks for all the helpful thoughts and notes here. I've done some module experimentation and was waiting for better GCC support before continuing, but I am eager to see what others are able to acheive.

Some thoughts:

  • When moving Glaze to C++20 modules I want to move to pure modules. Glaze only depends on the standard library, so there is no need for an intermediate approach that mixes headers and modules.
    Glaze will still need to be used with a header include approach, but if the user wants Glaze as a module, #include should not exist within the Glaze module. This means that import std must be supported for any compiler that we want to use for modular Glaze. We are waiting on GCC 15 for import std. But, I would be happy getting modules with Glaze just working on Clang for now.
  • Using macros to conditionally create a module in C++ is not valid and MSVC will issue warnings. So, I think the current approach of importizer is incorrect (@msqr1 do you not see these warnings with MSVC?).

@msqr1
Copy link
Author

msqr1 commented Feb 3, 2025

When moving Glaze to C++20 modules I want to move to pure modules. Glaze only depends on the standard library, so there is no need for an intermediate approach that mixes headers and modules.

I have a configuration for that in importizer

Glaze will still need to be used with a header include approach, but if the user wants Glaze as a module, #include should not exist within the Glaze module. This means that import std must be supported for any compiler that we want to use for modular Glaze.

Compiler support for import std is not very good right now TBH. Importizer does have settings to turn STL includes into import std/std.compat

Using macros to conditionally create a module in C++ is not valid and MSVC will issue warnings. So, I think the current approach of importizer is incorrect (@msqr1 do you not see these warnings with MSVC?).

I am only working on clang right now, importizer is really new. Btw, could you show me the warnings? I don't have MSVC on me :(.
I checked the C++ standard, doing so is fine, it's just weird MSVC.
On my machine, clang is fine, no warning, you can even keep the same CMakeLists.txt to compile in header mode.

If you still want a header and the module, you can still wrap it in a big module interface

@stephenberry
Copy link
Owner

Thanks for your response! Your project seems really neat. I to dig up my module testing work. For now I'll trust your expertise because you have a lot more experience with this than me.

@msqr1
Copy link
Author

msqr1 commented Feb 3, 2025

I have a problem. When modularizing the include directory, some of the files use pragma once, and some of them uses include guard (include/glaze/dragonbox). And that is confusing the tool. Do you know why this is the case, and can I normalize it to just pragma once or just include guard? @stephenberry

@stephenberry
Copy link
Owner

You can change it to just use pragma once.

@msqr1
Copy link
Author

msqr1 commented Feb 4, 2025

@stephenberry I was compiling the source code with clang(++) with -v output:

Debian clang version 19.1.7 (++20250114103228+cd708029e0b2-1~exp1~20250114103334.78)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/lib/llvm-19/bin

(I was trying to test with fuzzing because my gcc doesn't support libfuzzer) when I got a bunch of errors. There is no modification made to the source.

[0/2] Re-checking globbed directories...
[1/4] Building CXX object tests/json_test/CMakeFiles/json_test.dir/json_test.cpp.o
FAILED: tests/json_test/CMakeFiles/json_test.dir/json_test.cpp.o 
/usr/bin/clang++-19 -DGLZ_USE_AVX2 -Iglaze-modularized/include -isystem glaze-modularized/build/_deps/ut-src/include -fno-exceptions -fno-rtti -Wall -Wextra -pedantic -ftime-trace -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined -Wno-missing-braces -mavx2 -std=gnu++2b -MD -MT tests/json_test/CMakeFiles/json_test.dir/json_test.cpp.o -MF tests/json_test/CMakeFiles/json_test.dir/json_test.cpp.o.d -o tests/json_test/CMakeFiles/json_test.dir/json_test.cpp.o -c glaze-modularized/tests/json_test/json_test.cpp
In file included from glaze-modularized/tests/json_test/json_test.cpp:26:
In file included from glaze-modularized/include/glaze/api/impl.hpp:24:
In file included from glaze-modularized/include/glaze/glaze.hpp:41:
In file included from glaze-modularized/include/glaze/json.hpp:6:
glaze-modularized/include/glaze/json/escape_unicode.hpp:242:22: error: constexpr variable 'escaped' must be initialized by a constant expression
  242 |       constexpr auto escaped = []() constexpr {
      |                      ^         ~~~~~~~~~~~~~~~~
  243 |          constexpr auto len = detail::escaped_length(Str.sv());
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  244 |          std::array<char, len + 1> result; // + 1 for null character
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  245 |          const auto escaped = detail::escape_json_string(Str.sv(), len);
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  246 |          for (size_t i = 0; i < len; ++i) {
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  247 |             result[i] = escaped[i];
      |             ~~~~~~~~~~~~~~~~~~~~~~~
  248 |          }
      |          ~
  249 |          result[len] = '\0';
      |          ~~~~~~~~~~~~~~~~~~~
  250 |          return result;
      |          ~~~~~~~~~~~~~~
  251 |       }();
      |       ~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:241:78: note: while substituting into a lambda expression here
  241 |    inline constexpr auto escape_unicode = []() constexpr -> std::string_view {
      |                                                                              ^
glaze-modularized/tests/json_test/json_test.cpp:4490:58: note: in instantiation of variable template specialization 'glz::escape_unicode' requested here
 4490 |    static constexpr auto value = object("ᇿ", &T::text, escape_unicode<"ᇿ">, &T::text);
      |                                                        ^
/usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/basic_string.h:356:10: note: assignment to member '_M_local_buf' of union with no active member is not allowed in a constant expression
  356 |             __c = _CharT();
      |                 ^
/usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/basic_string.h:519:2: note: in call to 'this->_M_use_local_data()'
  519 |         _M_use_local_data();
      |         ^~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:124:19: note: in call to 'basic_string()'
  124 |       std::string output;
      |                   ^~~~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:245:31: note: in call to 'escape_json_string({3, &<template param string_literal<4>{{-31, -121, -65, 0}}>.value[0]}, 6)'
  245 |          const auto escaped = detail::escape_json_string(Str.sv(), len);
      |                               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:242:32: note: in call to '[]() {
    constexpr auto len = detail::escaped_length(string_literal<4>{{-31, -121, -65, 0}}.sv());
    std::array<char, len + 1> result;
    const auto escaped = detail::escape_json_string(string_literal<4>{{-31, -121, -65, 0}}.sv(), len);
    for (size_t i = 0; i < len; ++i) {
        result[i] = escaped[i];
    }
    result[len] = '\x00';
    return result;
}.operator()()'
  242 |       constexpr auto escaped = []() constexpr {
      |                                ^~~~~~~~~~~~~~~~
  243 |          constexpr auto len = detail::escaped_length(Str.sv());
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  244 |          std::array<char, len + 1> result; // + 1 for null character
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  245 |          const auto escaped = detail::escape_json_string(Str.sv(), len);
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  246 |          for (size_t i = 0; i < len; ++i) {
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  247 |             result[i] = escaped[i];
      |             ~~~~~~~~~~~~~~~~~~~~~~~
  248 |          }
      |          ~
  249 |          result[len] = '\0';
      |          ~~~~~~~~~~~~~~~~~~~
  250 |          return result;
      |          ~~~~~~~~~~~~~~
  251 |       }();
      |       ~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:254:39: error: non-type template argument is not a constant expression
  254 |       auto& arr = detail::make_static<escaped>::value;
      |                                       ^~~~~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:254:39: note: initializer of 'escaped' is not a constant expression
glaze-modularized/include/glaze/json/escape_unicode.hpp:254:39: note: in call to 'array(escaped)'
  254 |       auto& arr = detail::make_static<escaped>::value;
      |                                       ^~~~~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:242:22: note: declared here
  242 |       constexpr auto escaped = []() constexpr {
      |                      ^
glaze-modularized/include/glaze/json/escape_unicode.hpp:241:26: error: constexpr variable 'escape_unicode<string_literal<4>{{-31, -121, -65, 0}}>' must be initialized by a constant expression
  241 |    inline constexpr auto escape_unicode = []() constexpr -> std::string_view {
      |                          ^                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  242 |       constexpr auto escaped = []() constexpr {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  243 |          constexpr auto len = detail::escaped_length(Str.sv());
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  244 |          std::array<char, len + 1> result; // + 1 for null character
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  245 |          const auto escaped = detail::escape_json_string(Str.sv(), len);
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  246 |          for (size_t i = 0; i < len; ++i) {
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  247 |             result[i] = escaped[i];
      |             ~~~~~~~~~~~~~~~~~~~~~~~
  248 |          }
      |          ~
  249 |          result[len] = '\0';
      |          ~~~~~~~~~~~~~~~~~~~
  250 |          return result;
      |          ~~~~~~~~~~~~~~
  251 |       }();
      |       ~~~~
  252 | 
  253 |       // make_static here required for GCC 12, in the future just make escaped static
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  254 |       auto& arr = detail::make_static<escaped>::value;
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  255 |       return {arr.data(), arr.size() - 1};
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  256 |    }();
      |    ~~~
glaze-modularized/tests/json_test/json_test.cpp:4490:58: note: in instantiation of variable template specialization 'glz::escape_unicode' requested here
 4490 |    static constexpr auto value = object("ᇿ", &T::text, escape_unicode<"ᇿ">, &T::text);
      |                                                        ^
/usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/basic_string.h:356:10: note: assignment to member '_M_local_buf' of union with no active member is not allowed in a constant expression
  356 |             __c = _CharT();
      |                 ^
/usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/basic_string.h:519:2: note: in call to 'this->_M_use_local_data()'
  519 |         _M_use_local_data();
      |         ^~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:124:19: note: in call to 'basic_string()'
  124 |       std::string output;
      |                   ^~~~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:245:31: note: in call to 'escape_json_string({3, &<template param string_literal<4>{{-31, -121, -65, 0}}>.value[0]}, 6)'
  245 |          const auto escaped = detail::escape_json_string(Str.sv(), len);
      |                               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:242:32: note: in call to '[]() {
    constexpr auto len = detail::escaped_length(string_literal<4>{{-31, -121, -65, 0}}.sv());
    std::array<char, len + 1> result;
    const auto escaped = detail::escape_json_string(string_literal<4>{{-31, -121, -65, 0}}.sv(), len);
    for (size_t i = 0; i < len; ++i) {
        result[i] = escaped[i];
    }
    result[len] = '\x00';
    return result;
}.operator()()'
  242 |       constexpr auto escaped = []() constexpr {
      |                                ^~~~~~~~~~~~~~~~
  243 |          constexpr auto len = detail::escaped_length(Str.sv());
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  244 |          std::array<char, len + 1> result; // + 1 for null character
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  245 |          const auto escaped = detail::escape_json_string(Str.sv(), len);
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  246 |          for (size_t i = 0; i < len; ++i) {
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  247 |             result[i] = escaped[i];
      |             ~~~~~~~~~~~~~~~~~~~~~~~
  248 |          }
      |          ~
  249 |          result[len] = '\0';
      |          ~~~~~~~~~~~~~~~~~~~
  250 |          return result;
      |          ~~~~~~~~~~~~~~
  251 |       }();
      |       ~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:241:43: note: in call to '[]() -> std::string_view {
    constexpr auto escaped = []() {
        constexpr auto len = detail::escaped_length(string_literal<4>{{-31, -121, -65, 0}}.sv());
        std::array<char, len + 1> result;
        const auto escaped = detail::escape_json_string(string_literal<4>{{-31, -121, -65, 0}}.sv(), len);
        for (size_t i = 0; i < len; ++i) {
            result[i] = escaped[i];
        }
        result[len] = '\x00';
        return result;
    }();
    auto &arr;
    return {<recovery-expr>().data(), <recovery-expr>().size() - 1};
}.operator()()'
  241 |    inline constexpr auto escape_unicode = []() constexpr -> std::string_view {
      |                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  242 |       constexpr auto escaped = []() constexpr {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  243 |          constexpr auto len = detail::escaped_length(Str.sv());
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  244 |          std::array<char, len + 1> result; // + 1 for null character
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  245 |          const auto escaped = detail::escape_json_string(Str.sv(), len);
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  246 |          for (size_t i = 0; i < len; ++i) {
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  247 |             result[i] = escaped[i];
      |             ~~~~~~~~~~~~~~~~~~~~~~~
  248 |          }
      |          ~
  249 |          result[len] = '\0';
      |          ~~~~~~~~~~~~~~~~~~~
  250 |          return result;
      |          ~~~~~~~~~~~~~~
  251 |       }();
      |       ~~~~
  252 | 
  253 |       // make_static here required for GCC 12, in the future just make escaped static
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  254 |       auto& arr = detail::make_static<escaped>::value;
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  255 |       return {arr.data(), arr.size() - 1};
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  256 |    }();
      |    ~~~
glaze-modularized/tests/json_test/json_test.cpp:4490:26: error: constexpr variable 'value' must be initialized by a constant expression
 4490 |    static constexpr auto value = object("ᇿ", &T::text, escape_unicode<"ᇿ">, &T::text);
      |                          ^       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/common.hpp:538:35: note: initializer of 'escape_unicode<string_literal<4>{{-31, -121, -65, 0}}>' is not a constant expression
  538 |       return detail::Object{tuple{std::forward<Args>(args)...}};
      |                                   ^
glaze-modularized/include/glaze/core/common.hpp:538:35: note: in call to 'basic_string_view(escape_unicode)'
  538 |       return detail::Object{tuple{std::forward<Args>(args)...}};
      |                                   ^~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/tests/json_test/json_test.cpp:4490:34: note: in call to 'object<const char (&)[4], std::basic_string<char> question_escaped_t::*, const std::basic_string_view<char> &, std::basic_string<char> question_escaped_t::*>("\341\207\277", &T::text, escape_unicode, &T::text)'
 4490 |    static constexpr auto value = object("ᇿ", &T::text, escape_unicode<"ᇿ">, &T::text);
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:241:26: note: declared here
  241 |    inline constexpr auto escape_unicode = []() constexpr -> std::string_view {
      |                          ^
glaze-modularized/tests/json_test/json_test.cpp:4493:15: error: static assertion expression is not an integral constant expression
 4493 | static_assert(glz::escape_unicode<"ᇿ"> == R"(\u11FF)");
      |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/tests/json_test/json_test.cpp:4493:15: note: initializer of 'escape_unicode<string_literal<4>{{-31, -121, -65, 0}}>' is not a constant expression
glaze-modularized/tests/json_test/json_test.cpp:4493:15: note: in call to 'basic_string_view(escape_unicode)'
 4493 | static_assert(glz::escape_unicode<"ᇿ"> == R"(\u11FF)");
      |               ^~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/json/escape_unicode.hpp:241:26: note: declared here
  241 |    inline constexpr auto escape_unicode = []() constexpr -> std::string_view {
      |                          ^
In file included from glaze-modularized/tests/json_test/json_test.cpp:26:
In file included from glaze-modularized/include/glaze/api/impl.hpp:6:
In file included from glaze-modularized/include/glaze/api/api.hpp:12:
In file included from glaze-modularized/include/glaze/api/std/string.hpp:9:
glaze-modularized/include/glaze/core/meta.hpp:117:36: error: constexpr variable 'meta_wrapper_v<question_escaped_t>' must be initialized by a constant expression
  117 |    inline constexpr decltype(auto) meta_wrapper_v = [] {
      |                                    ^                ~~~~
  118 |       if constexpr (detail::local_meta_t<T>) {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  119 |          return T::glaze::value;
      |          ~~~~~~~~~~~~~~~~~~~~~~~
  120 |       }
      |       ~
  121 |       else if constexpr (detail::global_meta_t<T>) {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  122 |          return meta<T>::value;
      |          ~~~~~~~~~~~~~~~~~~~~~~
  123 |       }
      |       ~
  124 |       else {
      |       ~~~~~~
  125 |          return empty{};
      |          ~~~~~~~~~~~~~~~
  126 |       }
      |       ~
  127 |    }();
      |    ~~~
glaze-modularized/include/glaze/core/meta.hpp:156:58: note: in instantiation of variable template specialization 'glz::meta_wrapper_v' requested here
  156 |    using meta_wrapper_t = decay_keep_volatile_t<decltype(meta_wrapper_v<std::decay_t<T>>)>;
      |                                                          ^
glaze-modularized/include/glaze/core/common.hpp:381:65: note: in instantiation of template type alias 'meta_wrapper_t' requested here
  381 |       concept glaze_array_t = glaze_t<T> && is_specialization_v<meta_wrapper_t<T>, Array>;
      |                                                                 ^
glaze-modularized/include/glaze/core/common.hpp:381:45: note: while substituting template arguments into constraint expression here
  381 |       concept glaze_array_t = glaze_t<T> && is_specialization_v<meta_wrapper_t<T>, Array>;
      |                                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/common.hpp:395:26: note: while checking the satisfaction of concept 'glaze_array_t<question_escaped_t>' requested here
  395 |          glaze_t<T> && !(glaze_array_t<T> || glaze_object_t<T> || glaze_enum_t<T> || meta_keys<T> || glaze_flags_t<T>);
      |                          ^~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/common.hpp:395:24: note: while substituting template arguments into constraint expression here
  395 |          glaze_t<T> && !(glaze_array_t<T> || glaze_object_t<T> || glaze_enum_t<T> || meta_keys<T> || glaze_flags_t<T>);
      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/json/read.hpp:126:19: note: (skipping 6 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all)
  126 |          requires(glaze_value_t<T> && !custom_read<T>)
      |                   ^
glaze-modularized/include/glaze/core/opts.hpp:345:34: note: while substituting template arguments into constraint expression here
  345 |    concept read_json_supported = requires { detail::from<JSON, std::remove_cvref_t<T>>{}; };
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/json/read.hpp:2738:14: note: while checking the satisfaction of concept 'read_json_supported<question_escaped_t>' requested here
 2738 |    template <read_json_supported T, is_buffer Buffer>
      |              ^
glaze-modularized/include/glaze/json/read.hpp:2738:14: note: while substituting template arguments into constraint expression here
 2738 |    template <read_json_supported T, is_buffer Buffer>
      |              ^~~~~~~~~~~~~~~~~~~
glaze-modularized/tests/json_test/json_test.cpp:4534:18: note: while checking constraint satisfaction for template 'read_json<question_escaped_t, std::basic_string<char> &>' required here
 4534 |       expect(not glz::read_json(obj, str));
      |                  ^~~
glaze-modularized/tests/json_test/json_test.cpp:4534:18: note: in instantiation of function template specialization 'glz::read_json<question_escaped_t, std::basic_string<char> &>' requested here
glaze-modularized/include/glaze/core/meta.hpp:122:17: note: initializer of 'value' is not a constant expression
  122 |          return meta<T>::value;
      |                 ^
glaze-modularized/include/glaze/core/meta.hpp:122:17: note: in call to 'Object(value)'
  122 |          return meta<T>::value;
      |                 ^~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/meta.hpp:117:53: note: in call to '[] {
    if (detail::local_meta_t<question_escaped_t>) {
    } else if (detail::global_meta_t<question_escaped_t>) {
        return meta<question_escaped_t>::value;
    } else {
    }
}.operator()()'
  117 |    inline constexpr decltype(auto) meta_wrapper_v = [] {
      |                                                     ^~~~
  118 |       if constexpr (detail::local_meta_t<T>) {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  119 |          return T::glaze::value;
      |          ~~~~~~~~~~~~~~~~~~~~~~~
  120 |       }
      |       ~
  121 |       else if constexpr (detail::global_meta_t<T>) {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  122 |          return meta<T>::value;
      |          ~~~~~~~~~~~~~~~~~~~~~~
  123 |       }
      |       ~
  124 |       else {
      |       ~~~~~~
  125 |          return empty{};
      |          ~~~~~~~~~~~~~~~
  126 |       }
      |       ~
  127 |    }();
      |    ~~~
glaze-modularized/tests/json_test/json_test.cpp:4490:26: note: declared here
 4490 |    static constexpr auto value = object("ᇿ", &T::text, escape_unicode<"ᇿ">, &T::text);
      |                          ^
In file included from glaze-modularized/tests/json_test/json_test.cpp:26:
In file included from glaze-modularized/include/glaze/api/impl.hpp:6:
In file included from glaze-modularized/include/glaze/api/api.hpp:12:
In file included from glaze-modularized/include/glaze/api/std/string.hpp:9:
glaze-modularized/include/glaze/core/meta.hpp:143:26: error: constexpr variable 'meta_v<question_escaped_t>' must be initialized by a constant expression
  143 |    inline constexpr auto meta_v = []() -> decltype(auto) {
      |                          ^        ~~~~~~~~~~~~~~~~~~~~~~~~
  144 |       if constexpr (detail::meta_keys<T>) {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  145 |          return meta_wrapper_v<decay_keep_volatile_t<T>>;
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  146 |       }
      |       ~
  147 |       else {
      |       ~~~~~~
  148 |          return meta_wrapper_v<decay_keep_volatile_t<T>>.value;
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  149 |       }
      |       ~
  150 |    }();
      |    ~~~
glaze-modularized/include/glaze/core/meta.hpp:153:50: note: in instantiation of variable template specialization 'glz::meta_v' requested here
  153 |    using meta_t = decay_keep_volatile_t<decltype(meta_v<T>)>;
      |                                                  ^
glaze-modularized/include/glaze/core/reflect.hpp:90:30: note: in instantiation of template type alias 'meta_t' requested here
   90 |                (tuple_size_v<meta_t<T>> == 0))
      |                              ^
glaze-modularized/include/glaze/core/reflect.hpp:90:17: note: while substituting template arguments into constraint expression here
   90 |                (tuple_size_v<meta_t<T>> == 0))
      |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/json/read.hpp:1787:49: note: while checking constraint satisfaction for class template partial specialization 'reflect<question_escaped_t>' required here
 1787 |             static constexpr auto num_members = reflect<T>::size;
      |                                                 ^~~~~~~
glaze-modularized/include/glaze/json/read.hpp:1787:49: note: during template argument deduction for class template partial specialization 'reflect<T>' [with T = question_escaped_t]
glaze-modularized/include/glaze/json/read.hpp:1787:49: note: in instantiation of template class 'glz::reflect<question_escaped_t>' requested here
glaze-modularized/include/glaze/json/read.hpp:74:40: note: in instantiation of function template specialization 'glz::detail::from<10, question_escaped_t>::op<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 32}, string_literal<1>{""}, question_escaped_t &, glz::context &, const char *&, const char *&>' requested here
   74 |                from<JSON, V>::template op<Opts>(std::forward<T>(value), std::forward<Ctx>(ctx), std::forward<It0>(it),
      |                                        ^
glaze-modularized/include/glaze/core/read.hpp:60:46: note: in instantiation of function template specialization 'glz::detail::read<10>::op<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 32}, question_escaped_t &, glz::context &, const char *&, const char *&>' requested here
   60 |          detail::read<Opts.format>::template op<is_padded_on<Opts>()>(value, ctx, it, end);
      |                                              ^
glaze-modularized/include/glaze/json/read.hpp:2742:14: note: in instantiation of function template specialization 'glz::read<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0}, question_escaped_t, std::basic_string<char> &, glz::context &>' requested here
 2742 |       return read<opts{}>(value, std::forward<Buffer>(buffer), ctx);
      |              ^
glaze-modularized/tests/json_test/json_test.cpp:4534:23: note: in instantiation of function template specialization 'glz::read_json<question_escaped_t, std::basic_string<char> &>' requested here
 4534 |       expect(not glz::read_json(obj, str));
      |                       ^
glaze-modularized/include/glaze/core/meta.hpp:148:17: note: initializer of 'meta_wrapper_v<question_escaped_t>' is not a constant expression
  148 |          return meta_wrapper_v<decay_keep_volatile_t<T>>.value;
      |                 ^
glaze-modularized/include/glaze/core/meta.hpp:148:17: note: in call to 'tuple(meta_wrapper_v.value)'
  148 |          return meta_wrapper_v<decay_keep_volatile_t<T>>.value;
      |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/meta.hpp:143:35: note: in call to '[]() -> glz::tuple<const char *, std::basic_string<char> question_escaped_t::*, std::basic_string_view<char>, std::basic_string<char> question_escaped_t::*> {
    if (detail::meta_keys<question_escaped_t>) {
    } else {
        return meta_wrapper_v<decay_keep_volatile_t<question_escaped_t>>.value;
    }
}.operator()()'
  143 |    inline constexpr auto meta_v = []() -> decltype(auto) {
      |                                   ^~~~~~~~~~~~~~~~~~~~~~~~
  144 |       if constexpr (detail::meta_keys<T>) {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  145 |          return meta_wrapper_v<decay_keep_volatile_t<T>>;
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  146 |       }
      |       ~
  147 |       else {
      |       ~~~~~~
  148 |          return meta_wrapper_v<decay_keep_volatile_t<T>>.value;
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  149 |       }
      |       ~
  150 |    }();
      |    ~~~
glaze-modularized/include/glaze/core/meta.hpp:117:36: note: declared here
  117 |    inline constexpr decltype(auto) meta_wrapper_v = [] {
      |                                    ^
In file included from glaze-modularized/tests/json_test/json_test.cpp:26:
In file included from glaze-modularized/include/glaze/api/impl.hpp:6:
In file included from glaze-modularized/include/glaze/api/api.hpp:13:
In file included from glaze-modularized/include/glaze/api/trait.hpp:11:
glaze-modularized/include/glaze/core/reflect.hpp:110:29: error: constexpr variable 'values' must be initialized by a constant expression
  110 |       static constexpr auto values = [] {
      |                             ^        ~~~~
  111 |          return [&]<size_t... I>(std::index_sequence<I...>) { //
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  112 |             return tuple{get<value_indices[I]>(meta_v<T>)...}; //
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  113 |          }(std::make_index_sequence<value_indices.size()>{}); //
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  114 |       }();
      |       ~~~
glaze-modularized/include/glaze/json/read.hpp:1787:49: note: in instantiation of template class 'glz::reflect<question_escaped_t>' requested here
 1787 |             static constexpr auto num_members = reflect<T>::size;
      |                                                 ^
glaze-modularized/include/glaze/json/read.hpp:74:40: note: in instantiation of function template specialization 'glz::detail::from<10, question_escaped_t>::op<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 32}, string_literal<1>{""}, question_escaped_t &, glz::context &, const char *&, const char *&>' requested here
   74 |                from<JSON, V>::template op<Opts>(std::forward<T>(value), std::forward<Ctx>(ctx), std::forward<It0>(it),
      |                                        ^
glaze-modularized/include/glaze/core/read.hpp:60:46: note: in instantiation of function template specialization 'glz::detail::read<10>::op<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 32}, question_escaped_t &, glz::context &, const char *&, const char *&>' requested here
   60 |          detail::read<Opts.format>::template op<is_padded_on<Opts>()>(value, ctx, it, end);
      |                                              ^
glaze-modularized/include/glaze/json/read.hpp:2742:14: note: in instantiation of function template specialization 'glz::read<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0}, question_escaped_t, std::basic_string<char> &, glz::context &>' requested here
 2742 |       return read<opts{}>(value, std::forward<Buffer>(buffer), ctx);
      |              ^
glaze-modularized/tests/json_test/json_test.cpp:4534:23: note: in instantiation of function template specialization 'glz::read_json<question_escaped_t, std::basic_string<char> &>' requested here
 4534 |       expect(not glz::read_json(obj, str));
      |                       ^
glaze-modularized/include/glaze/tuplet/tuple.hpp:435:14: note: initializer of 'meta_v<question_escaped_t>' is not a constant expression
  435 |       return static_cast<Tup&&>(tup)[tuplet::tag<I>()];
      |              ^
glaze-modularized/include/glaze/core/reflect.hpp:112:26: note: in call to 'get<1UL, const glz::tuple<const char *, std::basic_string<char> question_escaped_t::*, std::basic_string_view<char>, std::basic_string<char> question_escaped_t::*> &>(meta_v)'
  112 |             return tuple{get<value_indices[I]>(meta_v<T>)...}; //
      |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:111:17: note: in call to '[&]<size_t ...I>(std::index_sequence<I...>) {
    return tuple{get<value_indices[I]>(meta_v<question_escaped_t>)...};
}.operator()<0UL, 1UL>({})'
  111 |          return [&]<size_t... I>(std::index_sequence<I...>) { //
      |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  112 |             return tuple{get<value_indices[I]>(meta_v<T>)...}; //
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  113 |          }(std::make_index_sequence<value_indices.size()>{}); //
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:110:38: note: in call to '[] {
    return [&]<size_t ...I>(std::index_sequence<I...>) {
        return tuple{get<value_indices[I]>(meta_v<question_escaped_t>)...};
    }(std::make_index_sequence<value_indices.size()>{});
}.operator()()'
  110 |       static constexpr auto values = [] {
      |                                      ^~~~
  111 |          return [&]<size_t... I>(std::index_sequence<I...>) { //
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  112 |             return tuple{get<value_indices[I]>(meta_v<T>)...}; //
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  113 |          }(std::make_index_sequence<value_indices.size()>{}); //
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  114 |       }();
      |       ~~~
glaze-modularized/include/glaze/core/meta.hpp:143:26: note: declared here
  143 |    inline constexpr auto meta_v = []() -> decltype(auto) {
      |                          ^
In file included from glaze-modularized/tests/json_test/json_test.cpp:26:
In file included from glaze-modularized/include/glaze/api/impl.hpp:6:
In file included from glaze-modularized/include/glaze/api/api.hpp:13:
In file included from glaze-modularized/include/glaze/api/trait.hpp:11:
glaze-modularized/include/glaze/core/reflect.hpp:118:29: error: constexpr variable 'keys' must be initialized by a constant expression
  118 |       static constexpr auto keys = [] {
      |                             ^      ~~~~
  119 |          std::array<sv, size> res{};
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  120 |          [&]<size_t... I>(std::index_sequence<I...>) { //
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  121 |             ((res[I] = get_key_element<T, value_indices[I]>()), ...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  122 |          }(std::make_index_sequence<value_indices.size()>{});
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  123 |          return res;
      |          ~~~~~~~~~~~
  124 |       }();
      |       ~~~
glaze-modularized/include/glaze/tuplet/tuple.hpp:435:14: note: initializer of 'meta_v<question_escaped_t>' is not a constant expression
  435 |       return static_cast<Tup&&>(tup)[tuplet::tag<I>()];
      |              ^
glaze-modularized/include/glaze/core/reflect.hpp:75:17: note: in call to 'get<0UL, const glz::tuple<const char *, std::basic_string<char> question_escaped_t::*, std::basic_string_view<char>, std::basic_string<char> question_escaped_t::*> &>(meta_v)'
   75 |          return get<I - 1>(meta_v<V>);
      |                 ^~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:121:24: note: in call to 'get_key_element<question_escaped_t, 1UL>()'
  121 |             ((res[I] = get_key_element<T, value_indices[I]>()), ...);
      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:120:10: note: in call to '[&]<size_t ...I>(std::index_sequence<I...>) {
    ((res[I] = get_key_element<question_escaped_t, value_indices[I]>()) , ...);
}.operator()<0UL, 1UL>({})'
  120 |          [&]<size_t... I>(std::index_sequence<I...>) { //
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  121 |             ((res[I] = get_key_element<T, value_indices[I]>()), ...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  122 |          }(std::make_index_sequence<value_indices.size()>{});
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:118:36: note: in call to '[] {
    std::array<sv, size> res{};
    [&]<size_t ...I>(std::index_sequence<I...>) {
        ((res[I] = get_key_element<question_escaped_t, value_indices[I]>()) , ...);
    }(std::make_index_sequence<value_indices.size()>{});
    return res;
}.operator()()'
  118 |       static constexpr auto keys = [] {
      |                                    ^~~~
  119 |          std::array<sv, size> res{};
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  120 |          [&]<size_t... I>(std::index_sequence<I...>) { //
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  121 |             ((res[I] = get_key_element<T, value_indices[I]>()), ...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  122 |          }(std::make_index_sequence<value_indices.size()>{});
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  123 |          return res;
      |          ~~~~~~~~~~~
  124 |       }();
      |       ~~~
glaze-modularized/include/glaze/core/meta.hpp:143:26: note: declared here
  143 |    inline constexpr auto meta_v = []() -> decltype(auto) {
      |                          ^
In file included from glaze-modularized/tests/json_test/json_test.cpp:26:
In file included from glaze-modularized/include/glaze/api/impl.hpp:6:
In file included from glaze-modularized/include/glaze/api/api.hpp:13:
In file included from glaze-modularized/include/glaze/api/trait.hpp:11:
glaze-modularized/include/glaze/core/reflect.hpp:118:36: error: call to immediate function 'glz::reflect<question_escaped_t>::(anonymous class)::operator()' is not a constant expression
  118 |       static constexpr auto keys = [] {
      |                                    ^
glaze-modularized/include/glaze/tuplet/tuple.hpp:435:14: note: initializer of 'meta_v<question_escaped_t>' is not a constant expression
  435 |       return static_cast<Tup&&>(tup)[tuplet::tag<I>()];
      |              ^
glaze-modularized/include/glaze/core/reflect.hpp:75:17: note: in call to 'get<0UL, const glz::tuple<const char *, std::basic_string<char> question_escaped_t::*, std::basic_string_view<char>, std::basic_string<char> question_escaped_t::*> &>(meta_v)'
   75 |          return get<I - 1>(meta_v<V>);
      |                 ^~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:121:24: note: in call to 'get_key_element<question_escaped_t, 1UL>()'
  121 |             ((res[I] = get_key_element<T, value_indices[I]>()), ...);
      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:120:10: note: in call to '[&]<size_t ...I>(std::index_sequence<I...>) {
    ((res[I] = get_key_element<question_escaped_t, value_indices[I]>()) , ...);
}.operator()<0UL, 1UL>({})'
  120 |          [&]<size_t... I>(std::index_sequence<I...>) { //
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  121 |             ((res[I] = get_key_element<T, value_indices[I]>()), ...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  122 |          }(std::make_index_sequence<value_indices.size()>{});
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:118:36: note: in call to '[] {
    std::array<sv, size> res{};
    [&]<size_t ...I>(std::index_sequence<I...>) {
        ((res[I] = get_key_element<question_escaped_t, value_indices[I]>()) , ...);
    }(std::make_index_sequence<value_indices.size()>{});
    return res;
}.operator()()'
  118 |       static constexpr auto keys = [] {
      |                                    ^~~~
  119 |          std::array<sv, size> res{};
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  120 |          [&]<size_t... I>(std::index_sequence<I...>) { //
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  121 |             ((res[I] = get_key_element<T, value_indices[I]>()), ...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  122 |          }(std::make_index_sequence<value_indices.size()>{});
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  123 |          return res;
      |          ~~~~~~~~~~~
  124 |       }();
      |       ~~~
glaze-modularized/include/glaze/core/meta.hpp:143:26: note: declared here
  143 |    inline constexpr auto meta_v = []() -> decltype(auto) {
      |                          ^
In file included from glaze-modularized/tests/json_test/json_test.cpp:26:
In file included from glaze-modularized/include/glaze/api/impl.hpp:6:
In file included from glaze-modularized/include/glaze/api/api.hpp:13:
In file included from glaze-modularized/include/glaze/api/trait.hpp:11:
glaze-modularized/include/glaze/core/reflect.hpp:1286:19: error: constexpr variable 'keys_info<question_escaped_t>' must be initialized by a constant expression
 1286 |    constexpr auto keys_info = make_keys_info(reflect<T>::keys);
      |                   ^           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:1293:35: note: in instantiation of variable template specialization 'glz::detail::keys_info' requested here
 1293 |          constexpr auto& k_info = keys_info<T>;
      |                                   ^
glaze-modularized/include/glaze/core/reflect.hpp:1289:34: note: while substituting into a lambda expression here
 1289 |    constexpr auto hash_info = [] {
      |                                  ^
glaze-modularized/include/glaze/json/read.hpp:1913:41: note: in instantiation of variable template specialization 'glz::detail::hash_info' requested here
 1913 |                      static_assert(bool(hash_info<T>.type));
      |                                         ^
glaze-modularized/include/glaze/json/read.hpp:74:40: note: in instantiation of function template specialization 'glz::detail::from<10, question_escaped_t>::op<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 32}, string_literal<1>{""}, question_escaped_t &, glz::context &, const char *&, const char *&>' requested here
   74 |                from<JSON, V>::template op<Opts>(std::forward<T>(value), std::forward<Ctx>(ctx), std::forward<It0>(it),
      |                                        ^
glaze-modularized/include/glaze/core/read.hpp:60:46: note: in instantiation of function template specialization 'glz::detail::read<10>::op<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 32}, question_escaped_t &, glz::context &, const char *&, const char *&>' requested here
   60 |          detail::read<Opts.format>::template op<is_padded_on<Opts>()>(value, ctx, it, end);
      |                                              ^
glaze-modularized/include/glaze/json/read.hpp:2742:14: note: in instantiation of function template specialization 'glz::read<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0}, question_escaped_t, std::basic_string<char> &, glz::context &>' requested here
 2742 |       return read<opts{}>(value, std::forward<Buffer>(buffer), ctx);
      |              ^
glaze-modularized/tests/json_test/json_test.cpp:4534:23: note: in instantiation of function template specialization 'glz::read_json<question_escaped_t, std::basic_string<char> &>' requested here
 4534 |       expect(not glz::read_json(obj, str));
      |                       ^
glaze-modularized/include/glaze/core/reflect.hpp:1052:25: note: initializer of 'keys' is not a constant expression
 1052 |          const auto n = keys[i].size();
      |                         ^
glaze-modularized/include/glaze/core/reflect.hpp:1286:31: note: in call to 'make_keys_info<2UL>(keys)'
 1286 |    constexpr auto keys_info = make_keys_info(reflect<T>::keys);
      |                               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:118:29: note: declared here
  118 |       static constexpr auto keys = [] {
      |                             ^
glaze-modularized/include/glaze/core/reflect.hpp:1299:24: error: constexpr if condition is not a constant expression
 1299 |          if constexpr (type == single_element) {
      |                        ^~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:1289:34: note: while substituting into a lambda expression here
 1289 |    constexpr auto hash_info = [] {
      |                                  ^
glaze-modularized/include/glaze/json/read.hpp:1913:41: note: in instantiation of variable template specialization 'glz::detail::hash_info' requested here
 1913 |                      static_assert(bool(hash_info<T>.type));
      |                                         ^
glaze-modularized/include/glaze/json/read.hpp:74:40: note: in instantiation of function template specialization 'glz::detail::from<10, question_escaped_t>::op<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 32}, string_literal<1>{""}, question_escaped_t &, glz::context &, const char *&, const char *&>' requested here
   74 |                from<JSON, V>::template op<Opts>(std::forward<T>(value), std::forward<Ctx>(ctx), std::forward<It0>(it),
      |                                        ^
glaze-modularized/include/glaze/core/read.hpp:60:46: note: in instantiation of function template specialization 'glz::detail::read<10>::op<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 32}, question_escaped_t &, glz::context &, const char *&, const char *&>' requested here
   60 |          detail::read<Opts.format>::template op<is_padded_on<Opts>()>(value, ctx, it, end);
      |                                              ^
glaze-modularized/include/glaze/json/read.hpp:2742:14: note: in instantiation of function template specialization 'glz::read<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0}, question_escaped_t, std::basic_string<char> &, glz::context &>' requested here
 2742 |       return read<opts{}>(value, std::forward<Buffer>(buffer), ctx);
      |              ^
glaze-modularized/tests/json_test/json_test.cpp:4534:23: note: in instantiation of function template specialization 'glz::read_json<question_escaped_t, std::basic_string<char> &>' requested here
 4534 |       expect(not glz::read_json(obj, str));
      |                       ^
glaze-modularized/include/glaze/core/reflect.hpp:1299:24: note: initializer of 'keys_info<question_escaped_t>' is not a constant expression
 1299 |          if constexpr (type == single_element) {
      |                        ^
glaze-modularized/include/glaze/core/reflect.hpp:1286:19: note: declared here
 1286 |    constexpr auto keys_info = make_keys_info(reflect<T>::keys);
      |                   ^
glaze-modularized/include/glaze/core/reflect.hpp:1305:29: error: constexpr if condition is not a constant expression
 1305 |          else if constexpr (type == mod4) {
      |                             ^~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:1305:29: note: initializer of 'keys_info<question_escaped_t>' is not a constant expression
glaze-modularized/include/glaze/core/reflect.hpp:1286:19: note: declared here
 1286 |    constexpr auto keys_info = make_keys_info(reflect<T>::keys);
      |                   ^
glaze-modularized/include/glaze/core/reflect.hpp:1311:29: error: constexpr if condition is not a constant expression
 1311 |          else if constexpr (type == xor_mod4) {
      |                             ^~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:1311:29: note: initializer of 'keys_info<question_escaped_t>' is not a constant expression
glaze-modularized/include/glaze/core/reflect.hpp:1286:19: note: declared here
 1286 |    constexpr auto keys_info = make_keys_info(reflect<T>::keys);
      |                   ^
glaze-modularized/include/glaze/core/reflect.hpp:1317:29: error: constexpr if condition is not a constant expression
 1317 |          else if constexpr (type == minus_mod4) {
      |                             ^~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:1317:29: note: initializer of 'keys_info<question_escaped_t>' is not a constant expression
glaze-modularized/include/glaze/core/reflect.hpp:1286:19: note: declared here
 1286 |    constexpr auto keys_info = make_keys_info(reflect<T>::keys);
      |                   ^
glaze-modularized/include/glaze/core/reflect.hpp:1323:29: error: constexpr if condition is not a constant expression
 1323 |          else if constexpr (type == three_element_unique_index) {
      |                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:1323:29: note: initializer of 'keys_info<question_escaped_t>' is not a constant expression
glaze-modularized/include/glaze/core/reflect.hpp:1286:19: note: declared here
 1286 |    constexpr auto keys_info = make_keys_info(reflect<T>::keys);
      |                   ^
glaze-modularized/include/glaze/core/reflect.hpp:1331:29: error: constexpr if condition is not a constant expression
 1331 |          else if constexpr (type == front_hash) {
      |                             ^~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:1331:29: note: initializer of 'keys_info<question_escaped_t>' is not a constant expression
glaze-modularized/include/glaze/core/reflect.hpp:1286:19: note: declared here
 1286 |    constexpr auto keys_info = make_keys_info(reflect<T>::keys);
      |                   ^
glaze-modularized/include/glaze/core/reflect.hpp:1372:13: error: return type 'hash_info_t<[...], bsize aka 2>' must match previous return type 'hash_info_t<[...], bucket_size(single_element, N) aka 0>' when lambda expression has unspecified explicit return type
 1372 |             return info;
      |             ^
In file included from glaze-modularized/tests/json_test/json_test.cpp:26:
In file included from glaze-modularized/include/glaze/api/impl.hpp:24:
In file included from glaze-modularized/include/glaze/glaze.hpp:35:
In file included from glaze-modularized/include/glaze/beve.hpp:7:
In file included from glaze-modularized/include/glaze/beve/ptr.hpp:8:
In file included from glaze-modularized/include/glaze/core/ptr.hpp:9:
In file included from glaze-modularized/include/glaze/json/json_ptr.hpp:11:
glaze-modularized/include/glaze/json/read.hpp:1922:64: error: call to consteval function 'glz::detail::contains_tag<question_escaped_t, string_literal<1>{""}>' is not a constant expression
 1922 |                      if constexpr (not tag.sv().empty() && not contains_tag<T, tag>()) {
      |                                                                ^
glaze-modularized/include/glaze/json/read.hpp:74:40: note: in instantiation of function template specialization 'glz::detail::from<10, question_escaped_t>::op<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 32}, string_literal<1>{""}, question_escaped_t &, glz::context &, const char *&, const char *&>' requested here
   74 |                from<JSON, V>::template op<Opts>(std::forward<T>(value), std::forward<Ctx>(ctx), std::forward<It0>(it),
      |                                        ^
glaze-modularized/include/glaze/core/read.hpp:60:46: note: in instantiation of function template specialization 'glz::detail::read<10>::op<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 32}, question_escaped_t &, glz::context &, const char *&, const char *&>' requested here
   60 |          detail::read<Opts.format>::template op<is_padded_on<Opts>()>(value, ctx, it, end);
      |                                              ^
glaze-modularized/include/glaze/json/read.hpp:2742:14: note: in instantiation of function template specialization 'glz::read<opts{10, 1, 0, 1, 1, 1, 0, 0, 32, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0}, question_escaped_t, std::basic_string<char> &, glz::context &>' requested here
 2742 |       return read<opts{}>(value, std::forward<Buffer>(buffer), ctx);
      |              ^
glaze-modularized/tests/json_test/json_test.cpp:4534:23: note: in instantiation of function template specialization 'glz::read_json<question_escaped_t, std::basic_string<char> &>' requested here
 4534 |       expect(not glz::read_json(obj, str));
      |                       ^
glaze-modularized/include/glaze/json/read.hpp:1772:38: note: initializer of 'keys' is not a constant expression
 1772 |          for (size_t i = 0; i < keys.size(); ++i) {
      |                                      ^
glaze-modularized/include/glaze/json/read.hpp:1922:64: note: in call to 'contains_tag<question_escaped_t, string_literal<1>{""}>()'
 1922 |                      if constexpr (not tag.sv().empty() && not contains_tag<T, tag>()) {
      |                                                                ^~~~~~~~~~~~~~~~~~~~~~
glaze-modularized/include/glaze/core/reflect.hpp:118:29: note: declared here
  118 |       static constexpr auto keys = [] {
      |                             ^
19 errors generated.

What did I miss here? Or is this an actual bug?

@msqr1
Copy link
Author

msqr1 commented Feb 4, 2025

Also, are you sure you want to convert standard includes to import std/std.compat? It is not that well supported: https://en.cppreference.com/w/cpp/compiler_support#C.2B.2B23_library_features:~:text=Standard%20Library%20Modules

If you still insist, we can bootstrap it with a generated, fake std module.
Or we can just still use standard includes in the GMF. @stephenberry

@arturbac
Copy link
Contributor

arturbac commented Feb 4, 2025

: note: assignment to member '_M_local_buf' of union with no active member is not allowed in a constant expression

short answer AFIR, gnu team in libstdc++ is allowed to break any c++ rules, clang team does not support breaking rules. so compiling constexpr clang + libstdc++ is tricky. I was bullied on the r/cpp for questioning libstdc++ std::string design.

@arturbac
Copy link
Contributor

arturbac commented Feb 4, 2025

Also, are you sure you want to convert standard includes to import std/std.compat?

what's the point of that ? it does not change anything on user of glaze side. If it exists in system then it may speedup development of glaze when used but ...
Do not expect that clang will be able to use libstdc++ g++ build modules.

@stephenberry
Copy link
Owner

To get more out of the compile time benefits of modules using import std/std.compat gives better performance in my experience. But, if the support isn't there I'm okay with an approach that uses #include. I haven't done enough testing to understand the current differences in support between approaches (especially with Clang), but I found with MSVC import std/std.compat was much more stable than trying to mix modules and #include.

@stephenberry
Copy link
Owner

@msqr1 Your Clang compilation error is probably because you need to build with -stdlib=libc++. Using the default GCC implementation currently has compile time bugs.

@ChuanqiXu9
Copy link

ChuanqiXu9 commented Feb 5, 2025

Thanks for all the helpful thoughts and notes here. I've done some module experimentation and was waiting for better GCC support before continuing, but I am eager to see what others are able to acheive.

Some thoughts:

  • When moving Glaze to C++20 modules I want to move to pure modules. Glaze only depends on the standard library, so there is no need for an intermediate approach that mixes headers and modules.
    Glaze will still need to be used with a header include approach, but if the user wants Glaze as a module, #include should not exist within the Glaze module. This means that import std must be supported for any compiler that we want to use for modular Glaze. We are waiting on GCC 15 for import std. But, I would be happy getting modules with Glaze just working on Clang for now.

A big +1 to this. It will be best to support pure modules if possible. The export-and-using style is actually a wrapper only. And also it will be best to use import std; whenever possible.

  • Using macros to conditionally create a module in C++ is not valid and MSVC will issue warnings. So, I think the current approach of importizer is incorrect (@msqr1 do you not see these warnings with MSVC?).

@msqr1
Copy link
Author

msqr1 commented Feb 5, 2025

I'm trying my best, but both a pure module one, and a header one is tedious to maintain.

I feel like my hybrid style is not too ugly while allowing users to keep 1 version of their code. @ChuanqiXu9, do you have any suggestions? Should I continue to develop importizer in this direction?

Also, we can fake a std module, and just use that until import std becomes available widely.

@ChuanqiXu9
Copy link

I'm trying my best, but both a module, only one, and a header one is tedious to maintain.

I feel like my hybrid style is not too ugly while allowing users to keep 1 version of their code. @ChuanqiXu9, do you have any suggestions? Should I continue to develop importizer in this direction?

It depends. But the conclusion should be, we should only support hybrid style if users allowed. It is much easier to write and maintain codes in pure modules mode.

Maybe it is helpful to take a look at https://github.com/ChuanqiXu9/clang-modules-converter. I had a mode to convert headers to module partitions.

@msqr1
Copy link
Author

msqr1 commented Feb 5, 2025

It is much easier to write and maintain codes in pure modules mode.

(The hybrid style is inspired by your project second mode :))
The only difference between the hybrid style and the pure module is the preamble section that gets defined conditionally (depending on modules are supported) and the export keyword redefined into a macro conditionally.

#include "Export.hpp" 
#ifdef CPP_MODULES 
module;
import std;
export module test; 
#else
#include <vector>
#endif

EXPORT int x();

Except for the preamble, It's quite clean and maintainable below, don't you agree? @ChuanqiXu9

@ChuanqiXu9
Copy link

It is much easier to write and maintain codes in pure modules mode.

(The hybrid style is inspired by your project second mode :)) The only difference between the hybrid style and the pure module is the preamble section that gets defined conditionally (depending on modules are supported) and the export keyword redefined into a macro conditionally.

#include "Export.hpp" 
#ifdef CPP_MODULES 
module;
import std;
export module test; 
#else
#include <vector>
#endif

EXPORT int x();

Except for the preamble, It's quite clean and maintainable below, don't you agree? @ChuanqiXu9

It looks neat. But the standard don't allow #include before module;.

@msqr1
Copy link
Author

msqr1 commented Feb 6, 2025

@ChuanqiXu9 Can we discuss more here: msqr1/importizer#12

@msqr1
Copy link
Author

msqr1 commented Feb 6, 2025

@stephenberry I was compiling the source code with clang(++) with -v output:

Debian clang version 19.1.7 (++20250114103228+cd708029e0b2-1~exp1~20250114103334.78)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/lib/llvm-19/bin

And while running tests, I got this error with UBSAN/ASAN:

17/24 Test #17: lib_test .........................***Failed    0.10 sec
glaze-modularized/include/glaze/api/lib.hpp:124:63: runtime error: call to function glz_iface through pointer to incorrect function type 'std::shared_ptr<std::map<std::string, std::function<std::shared_ptr<glz::api> ()>, std::less<void>>> (*(*)())()'
(glaze-modularized/bin/libtest_lib.so+0x95a38): note: glz_iface defined here
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior glaze-modularized/include/glaze/api/lib.hpp:124:63 
glaze-modularized/include/glaze/api/lib.hpp:124:63: runtime error: call to function std::shared_ptr<std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::function<std::shared_ptr<glz::v0_0_3::api> ()>, std::less<void>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::function<std::shared_ptr<glz::v0_0_3::api> ()>>>>> (*glz::make_iface<my_api>())()::'lambda'()::__invoke[abi:cxx11]() through pointer to incorrect function type 'std::shared_ptr<std::map<std::string, std::function<std::shared_ptr<glz::api> ()>, std::less<void>>> (*)()'
(glaze-modularized/bin/libtest_lib.so+0x95bf8): note: std::shared_ptr<std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::function<std::shared_ptr<glz::v0_0_3::api> ()>, std::less<void>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::function<std::shared_ptr<glz::v0_0_3::api> ()>>>>> (*glz::make_iface<my_api>())()::'lambda'()::__invoke[abi:cxx11]() defined here
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior glaze-modularized/include/glaze/api/lib.hpp:124:63 
/usr/include/c++/v1/__tree:2357:69: runtime error: member access within null pointer of type 'std::__tree_node<std::__value_type<std::string, std::function<std::shared_ptr<glz::api> ()>>, void *>'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/include/c++/v1/__tree:2357:69 
AddressSanitizer:DEADLYSIGNAL
=================================================================
==21680==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000020 (pc 0x5565051a96a5 bp 0x7ffe543ce9e0 sp 0x7ffe543ce9c0 T0)
==21680==The signal is caused by a READ memory access.
==21680==Hint: address points to the zero page.
    #0 0x5565051a96a5 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::__is_long[abi:v160006]() const /usr/include/c++/v1/string:1682:33
    #1 0x5565051af4b4 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::__get_pointer[abi:v160006]() const /usr/include/c++/v1/string:1803:17
    #2 0x5565051ad914 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::data[abi:v160006]() const /usr/include/c++/v1/string:1462:73
    #3 0x5565051b27d0 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::operator std::__1::basic_string_view<char, std::__1::char_traits<char>>[abi:v160006]() const /usr/include/c++/v1/string:1010:65
    #4 0x5565051bb952 in auto std::__1::operator<=>[abi:v160006]<char, std::__1::char_traits<char>, std::__1::allocator<char>>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&) /usr/include/c++/v1/string:4080:47
    #5 0x5565051bb84e in decltype(std::forward<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&>(fp) < std::forward<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&>(fp0)) std::__1::less<void>::operator()[abi:v160006]<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&) const /usr/include/c++/v1/__functional/operations.h:397:52
    #6 0x5565051bb372 in std::__1::__map_value_compare<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>, std::__1::less<void>, true>::operator()[abi:v160006](std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>> const&) const /usr/include/c++/v1/map:596:17
    #7 0x5565051b9b17 in std::__1::__tree_node_base<void*>*& std::__1::__tree<std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>, std::__1::__map_value_compare<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>, std::__1::less<void>, true>, std::__1::allocator<std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>>>::__find_equal<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>(std::__1::__tree_end_node<std::__1::__tree_node_base<void*>*>*&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&) /usr/include/c++/v1/__tree:1987:17
    #8 0x5565051b93ea in void std::__1::__tree<std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>, std::__1::__map_value_compare<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>, std::__1::less<void>, true>, std::__1::allocator<std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>>>::__node_handle_merge_unique[abi:v160006]<std::__1::__tree<std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>, std::__1::__map_value_compare<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>, std::__1::less<void>, true>, std::__1::allocator<std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>>>>(std::__1::__tree<std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>, std::__1::__map_value_compare<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>, std::__1::less<void>, true>, std::__1::allocator<std::__1::__value_type<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>>>&) /usr/include/c++/v1/__tree:2357:13
    #9 0x5565051b30d8 in void std::__1::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>, std::__1::less<void>, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>>>::merge[abi:v160006]<std::__1::less<void>>(std::__1::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>, std::__1::less<void>, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const, std::__1::function<std::__1::shared_ptr<glz::v0_0_3::api> ()>>>>&) /usr/include/c++/v1/map:1418:17
    #10 0x5565051ac46e in glz::lib_loader::load_lib(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&) glaze-modularized/include/glaze/api/lib.hpp:125:24
    #11 0x5565051abce0 in glz::lib_loader::load_libs(std::__1::basic_string_view<char, std::__1::char_traits<char>>) glaze-modularized/include/glaze/api/lib.hpp:72:16
    #12 0x5565051aa69e in glz::lib_loader::load(std::__1::basic_string_view<char, std::__1::char_traits<char>>) glaze-modularized/include/glaze/api/lib.hpp:57:13
    #13 0x5565051a769c in glz::lib_loader::lib_loader(std::__1::basic_string_view<char, std::__1::char_traits<char>>) glaze-modularized/include/glaze/api/lib.hpp:85:54
    #14 0x55650519f348 in tests() glaze-modularized/tests/lib_test/lib_test.cpp:18:20
    #15 0x5565051a0bfb in main glaze-modularized/tests/lib_test/lib_test.cpp:116:4
    #16 0x7f9352868249 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #17 0x7f9352868304 in __libc_start_main csu/../csu/libc-start.c:360:3
    #18 0x5565050bd530 in _start (glaze-modularized/build/tests/lib_test/lib_test+0x44530) (BuildId: ada7b9d83dd272853abc74e618f2de4b5a7fc787)

==21680==Register values:
rax = 0x0000000000000020  rbx = 0x00007ffe543cea40  rcx = 0x0000000000000001  rdx = 0x000000008fff6fff  
rdi = 0x0000000000000020  rsi = 0x0000000000000000  rbp = 0x00007ffe543ce9e0  rsp = 0x00007ffe543ce9c0  
 r8 = 0x0000000000000000   r9 = 0x00007fffffffff01  r10 = 0xafffff00000fff01  r11 = 0x0000000000000246  
r12 = 0x0000000000000000  r13 = 0x00007ffe543cf4e8  r14 = 0x000055650521ec70  r15 = 0x00007f9352cc2020  
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV glaze-modularized/include/glaze/api/lib.hpp:125:24 in glz::lib_loader::load_lib(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&)
==21680==ABORTING

@stephenberry
Copy link
Owner

Thanks for reporting this! I think I found the issue and fixed it in this merge: #1596. The function signature on the DLL loader was missing a noexcept.

@msqr1
Copy link
Author

msqr1 commented Feb 7, 2025

I think this is just a signature mismatch that I can't reproduce reliably. Yesterday it stopped me from working, today it just worked again, with or without the noexcept... I can never reproduce it anymore. I hate these kind of bugs lol.

@stephenberry
Copy link
Owner

Oh, that is strange.

@arturbac
Copy link
Contributor

arturbac commented Feb 7, 2025

To get more out of the compile time benefits of modules using import std/std.compat gives better performance in my experience.

clang will not understand gcc build std modules format, and clang + gnu libstdc++ is the ** main ** use case on linux of clang as using llvm libc++ would require one to have full lib stack using llvm libc++ which is currently impossible in native linux builds, and when used in vcpkg libc++ has significantly slower implementations of some containers.
So if glaze evolves to module only build that will swallow gcc stdlib module import when present in system it will become completely unusable which clang on linux in most use cases.

And second I am not sure of a module size/contents of a case like this

module;
import std;
export module glaze;
// ...

As I understand it will import into hidden part of glaze module everything from std lib module

while this

module;
#include <string_view>
export module glaze;
// ...

will include string_view deps and prebuild them in hidden part of module for user, so there is only small cost at compile time of glaze.

Am I right ?

@stephenberry
Copy link
Owner

...will include string_view deps and prebuild them in hidden part of module for user, so there is only small cost at compile time of glaze.
Am I right ?

No, private module imports can do dead code elimination. The compiler uses the precompiled information (from import std) to know what’s available, and then it only compiles or links the parts that your code requires. Now, compiler implementations are likely not 100% optimized here, so I expect module sizes to improve in the future, but you shouldn't have to worry about using import std or importing other libraries.

@arturbac
Copy link
Contributor

arturbac commented Feb 7, 2025

Maybe i was not clear about scenario

  • system installed gnu libstdc++ with modu=le prebuild
  • glaze imports std

I build my code with import glaze using clang which tries to load dependency of import std prebuild with gcc from system which is not going to work at all.
So if glaze will directly use import std it will not be usable with clang and libstdc++ on linux when libstdc++ will have prebuild module unless clang will support gcc module format.

@stephenberry
Copy link
Owner

Ah, I see what you're getting at. Yes, developers will likely have to stick with one standard library. But, isn't that the case with linking libraries currently?

@arturbac
Copy link
Contributor

arturbac commented Feb 7, 2025

No it is not, as clang is compatibile with ABI with gcc and it is very widly used combo on linux.
Even system packages are build that way firefox, thunderbird, libreoffice.
And for production env clang + libstdc++ is very often used to, at least in my comapny only that combo goes into production, gcc is not used at all.

@stephenberry
Copy link
Owner

Yes, I also use clang + libstdc++. I was pointing out that it isn't recommended to mix libstdc++ and libc++ for static libraries. They do not have the same ABI.

@arturbac
Copy link
Contributor

arturbac commented Feb 7, 2025

sure they have different symbols in different namespaces.
I was pointing out only that when You have module for libstdc++ build with gcc You will not be able to use it with clang, even that clang can use libstdc++ as static or shared lib with normal includes and link it.

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

4 participants