I need to develop a manager that takes a struct
and stores its members to a database row, and that fills a struct
with values from a database row again. Objects of the same struct
are stored in the same table. This is our example strcut
.
struct Person
{
std::string Name;
int Age;
// more members that shouldn't be stored or loaded
// ...
};
My goal is to load and store struct
s into their table with one line.
// example for storing
unsigned int id = 42; // not hardcoded of course
Person paul = { "Paul", 8 }; // instantiate the struct
Data->Save<Person>(id, *paul); // table name is derived from template argument
// example for fetching
unsigned int id = 42;
Person paul;
Data->Load<Person>(id, *paul);
All struct
s could be forced to implement serialization and deserialization functions through inheritance, or I could make use of macro based reflection as suggested by Matthieu M. in the answers. But I cannot make changes to the struct
s that get stored. So I decided that those struct
s must be registered first, providing their serialization and deserialization functions. (By which I mean conversion from an instance of the struct
to a database row object and vice versa.)
class Data
{
typedef std::function<row(T*) Serialization;
typedef std::function<void(row, T*) Deserialization;
typedef std::pair<Serialization, Deserialization> Registereds;
std::unordered_map<std::type_index, Registered> List;
public:
template <typename T>
Register<T>(std::function<row(T*) Serialize,
std::function<void(row, T*) Deserialize)
{
// create a table based on the template argument
std::string name = typeid(T).name();
// ...
// add conversion functions to the list
auto index = std::type_index(typeid(T));
List.insert(index, std::make_pair(Serialize, Deserialize));
}
// load and save functions shown above go here
// ...
};
You may have already noticed the row
data type I used in this example. That exactly is my problem! For every struct
the row contains different fields of different types. So what data structure can I use for holding a row?
I though about using some kind of std::tuple
. For example if a tuple could have string keys, that would be a possibility. Then, I would have to completely templalize everything to work with different struct
s. It could look similar to this draft below. But I have to idea how to store the conversion functions of different struct
s in the same List
then, without using void*
pointers.
Data->Register<Person, std::string, int>(
[](Person *Instance) -> std::tuple<std::string, int>{ /* ... */ },
[](std::tuple<std::string, int> Row, Person *Instance) -> void{ /* ... */ }
);
How can I represent the database row object? If my template approach is a good way, how can I templatize everything?
Update in response to Matthieu M.'s answer
You suggested using type traits. This is what I tried. But I still don't know how to define the type for representing a row.
Traits
template <class T>
struct data_traits{ };
struct data_traits<Person>
{
struct row{ int Age; std::string Name; }; // right to do here?
row serialize(Person *Instance)
{
// ...
}
void deserialize(row Data, Person *Instance)
{
// ...
}
};
Data manager
class Data
{
template <typename T>
void Load(uint64_t Id, T *Instance)
{
auto traits = data_traits<T>;
std::string query = "SELECT * FROM "
+ typeid(T).name()
+ " WHERE id = "
+ std::to_string(Id);
// fetch result from database
// ...
traits::row result;
// convert SQL result into type dependent row type
// but how to do that?
// ...
traits.deserialize(result, Instance);
}
};