Recursive Serialization, decltype and is_visitable behaving strangely
churchianity opened this issue · 7 comments
I'm trying to write a function which, given some passed-in visitable struct, will recursively serialize all of its members that are marked visitable (I use the instrusive method).
template<typename T>
char* visitSerialize(const T& type) {
visit_struct::for_each(type, [](const char* name, const auto & value) {
if (visit_struct::traits::is_visitable<decltype(value)>::value) {
println("visitable! %s", name);
}
});
return ""; // @TODO
}
my plan was to recursively call visitSerialize
on members of the currently-being-visited struct, if the member I am currently inspecting is visitable. However, value
in the arguments to my callback is never considered visitable by the expression visit_struct::traits::is_visitable<decltype(value)>::value
, despite them being marked as such in my header file(s). Here's a simple example:
I start by calling visitSerialize
on an instance of this:
typedef struct Level {
BEGIN_VISITABLES(Level);
VISITABLE(char*, name);
VISITABLE(Terrain*, terrain);
VISITABLE(WaterRect*, waterRects);
VISITABLE(Entity*, entities);
VISITABLE(Skybox*, skybox);
VISITABLE(PointLight*, pointLights);
VISITABLE(DirLight*, dirLights);
VISITABLE(SpotLight*, spotLights);
VISITABLE(u32, numWaterRects);
VISITABLE(u32, numEntities);
VISITABLE(u32, numPointLights);
VISITABLE(u32, numDirLights);
VISITABLE(u32, numSpotLights);
u32 terrainShaderId;
u32 entityShaderId;
END_VISITABLES;
} Level;
None of the struct types there are considered visitable. Here's water.h
:
typedef struct WaterRect {
BEGIN_VISITABLES(WaterRect);
VISITABLE(glm::vec3, origin);
VISITABLE(float, xwidth);
VISITABLE(float, zwidth);
VISITABLE(float, moveSpeed);
VISITABLE(float, moveFactor);
VISITABLE(float, waveStrength);
u32 refractionFramebuffer;
u32 refractionTextureId;
u32 refractionDepthTextureId;
u32 reflectionFramebuffer;
u32 reflectionTextureId;
VISITABLE(glm::vec4, color);
END_VISITABLES;
} WaterRect;
Am I missing something? Is there a better way? Does decltype not work the way I thought?
I'm not a C++ template guy so a lot of this stuff is black magic to me. Thanks.
I'm using clang v13.0.0 on macosx catalina, c++14
I must be doing something fundamentally wrong, because I just tested using the VISITABLE_STRUCT
method instead of the instrusive method and everything is still reading as 'not visitable'.
The issue might be that decltype(value)
gives you a const reference type. Maybe try visit_struct::traits::is_visitable<std::decay_t<decltype(value)>>::value
. "decay" should remove the const reference and give you the underlying type.
Thanks for the reply. No dice it seems. Minimally reproducible example:
#include <stdio.h>
#include <stdlib.h>
#include <visit_struct/visit_struct.hpp>
#include <visit_struct/visit_struct_instrusive.hpp>
typedef struct Water {
BEGIN_VISITABLES(Water);
VISITABLE(float, x);
VISITABLE(float, y);
VISITABLE(float, z);
END_VISITABLES;
} Water;
typedef struct Level {
BEGIN_VISITABLES(Level);
VISITABLE(Water*, waterTiles);
VISITABLE(int, numWaterTiles);
END_VISITABLES;
} Level;
template<typename T>
static void visitSerialize(const T& type) {
visit_struct::for_each(type, [](const char* name, const auto & value) {
if (visit_struct::traits::is_visitable<std::decay_t<decltype(value)>>::value) {
printf("is visitable! %s", name);
}
});
}
int main(void) {
Level level;
level.waterTiles = (Water*) malloc(sizeof(Water) * 12);
level.numWaterTiles = 12;
visitSerialize(level);
return 0;
}
clang main.cpp -std=c++14 -I . -o out
(assuming you have visit struct in that dir)
I tried using std::decay_t
as well as just decltype
as I did before.
I recognize this isn't exactly an issue with the library itself, but I was assuming there would be a way to visit recursively on structs. Was I mistaken, or is there a better way to do it?
Inspecting some code you have written @NikolausDemmel in one of your public repositories, it certainly seems like it is possible. Looking at this example:
// convert visitable struct to json; supports types that can be assigned to
// nlohmann::json, such as strings, doubles, as well as recursive std::vector
// and visistable structs;
template <class Value>
auto visitable_to_json(const Value& value) {
nlohmann::json result;
if constexpr (visit_struct::traits::is_visitable<Value>::value) {
// visitable struct --> visit each member and save as json object
visit_struct::for_each(value, [&](const char* name, const auto& member) {
result[name] = visitable_to_json(member);
});
} else if constexpr (is_vector_v<Value>) {
// vector --> recursively save elements to json array
for (const auto& elem : value) {
result.push_back(visitable_to_json(elem));
}
} else if constexpr (wise_enum::is_wise_enum_v<Value>) {
// enum --> save as string
result = wise_enum::to_string(value);
} else {
// other basic value (string, number, ...)
result = value;
}
return result;
}
Trying to work through comparing your assumedly working code and mine.
Just noticed that your member is a pointer to an array of Water
. The pointer is of course not visitable. You need to dereference and visit the array elements.
Thanks. I managed to get it working by essentially copying up to the first if-block in your JSON serialization example, AND making sure that I always pass my structs to visitSerialize
by value.
If I call visitSerialize(Level* level)
it doesn't work, but if I call visitSerialize(Level level)
, it does.
Makes sense to me in retrospect. I'll close the issue, appreciate the help. Ideally I'd like to be able to pass the struct in by pointer too, but I can probably figure that out.