`template for` is not constexpr when looping over nonstatic data members

22 hours ago 3
ARTICLE AD BOX

I am trying the new C++26 type reflection features in gcc 16.1, and I have this basic code for converting a struct/class to JSON (see at the end). However, when I try compiling, I get this:

$ /opt/gcc-16.1/bin/g++ --version g++ (GCC) 16.1.0 Copyright (C) 2026 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ /opt/gcc-16.1/bin/g++ -std=c++26 main.cpp -o app -freflection main.cpp: In instantiation of ‘constexpr auto ToJson(const ValueT&) [with ValueT = EpicClass]’: main.cpp:48:24: required from here 48 | std::cout << ToJson(g_Epic) << "\n"; | ~~~~~~^~~~~~~~ main.cpp:19:9: error: ‘std::meta::nonstatic_data_members_of(^^EpicClass, std::meta::access_context::current())’ is not a constant expression because it refers to a result of ‘operator new’ 19 | template for (constexpr std::meta::info Member : std::meta::nonstatic_data_members_of(^^ValueT, std::meta::access_context::current())) | ^~~~~~~~ In file included from /opt/gcc-16.1/include/c++/16.1.0/string:46, from /opt/gcc-16.1/include/c++/16.1.0/bits/stdexcept_throw.h:57, from /opt/gcc-16.1/include/c++/16.1.0/array:44, from /opt/gcc-16.1/include/c++/16.1.0/meta:42, from main.cpp:2: /opt/gcc-16.1/include/c++/16.1.0/bits/allocator.h:203:52: note: allocated here 203 | return static_cast<_Tp*>(::operator new(__n)); | ~~~~~~~~~~~~~~^~~~~

I tried using the expand workaround (shown in https://isocpp.org/files/papers/P2996R4.html#implementation-status), but I got another error:

main.cpp: In instantiation of ‘constexpr auto ToJson(const ValueT&) [with ValueT = EpicClass]’: main.cpp:67:24: required from here 67 | std::cout << ToJson(g_Epic) << "\n"; | ~~~~~~^~~~~~~~ main.cpp:38:12: error: uncaught exception of type ‘std::meta::exception’; ‘what()’: ‘can_substitute returned false’ 38 | [: expand(std::meta::nonstatic_data_members_of(^^ValueT, std::meta::access_context::current())) :] >> [&]<auto Member> | ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Any tips?

main.cpp:

#include <meta> #include <string> #include <iostream> namespace __impl { template<auto... vals> struct replicator_type { template<typename F> constexpr void operator>>(F body) const { (body.template operator()<vals>(), ...); } }; template<auto... vals> replicator_type<vals...> replicator = {}; } template<typename R> consteval auto expand(R range) { std::vector<std::meta::info> args; for (auto r : range) { args.push_back(^^r); } return std::meta::substitute(^^__impl::replicator, args); } template <typename ValueT> constexpr auto ToJson(const ValueT& Value) { if constexpr (not std::is_class_v<ValueT>) { return std::to_string(Value); } else { std::string OutputResult = "{\n"; template for (constexpr std::meta::info Member : std::meta::nonstatic_data_members_of(^^ValueT, std::meta::access_context::current())) { OutputResult += '"' + std::string(std::meta::identifier_of(Member)) + "\": " + ToJson(Value.[:Member:]) + ",\n"; } // or with the [: expand() :] workaround: // [: expand(std::meta::nonstatic_data_members_of(^^ValueT, std::meta::access_context::current())) :] >> [&]<auto Member> // { // OutputResult += '"' // + std::string(std::meta::identifier_of(Member)) // + "\": " // + ToJson(Value.[:Member:]) // + ",\n"; // }; return OutputResult + "}"; } } struct EpicClass { public: int MyInt = 4; struct { int OtherInt = 5; char Guy = 0; } Other; }; int main() { EpicClass g_Epic = EpicClass(); std::cout << ToJson(g_Epic) << "\n"; }
Read Entire Article