Reputation: 1653
This is my understand of how "good" serialization code should work (and please correct me if I am confused):
Thus, a very simple serializer might look like:
class Serializer {
public:
void visit(int v);
void visit(unsigned int v);
void visit(float v);
//Other primitive types
};
Then a type like:
class Timestamp {
private:
unsigned int _value;
template <typename ARCHIVE>
friend
void serialize(ARCHIVE& archive, const ComplexType& v);
};
Would have a serialization function like:
template <typename ARCHIVE>
void serialize(ARCHIVE& archive, const Timestamp & v) {
archive.visit(v._value);
}
I beleive this is similar to what boost serialization does.
This is all fine and dandy except for one problem... In my mind, serialization comes in MANY different forms. For example:
This presents a problem because the same type might have VERY different representations (of even its primitive types) depending on the serializer.
In the example above, for example, a timestamp can be stored or transmitted as merely a 4-byte integer value (on a 32-bit system). However, the string serializer should render the type as something like "2022-07-21 14:42:03.123"
This cannot be achieved with a universal serialize function as I showed above.
My one thought is "template specialization to the rescue!"
template <>
void serialize<StringSerializer>(StringSerializer& archive, const Timestamp& v) {
//I guess convert v._value to a string somehow
}
Something still seems amiss with this method though.
What is the correct solution?
Upvotes: 0
Views: 251
Reputation: 4748
You can achieve this through a combination of overloading and template specialization. For the particular example of timestamps, you can simply overload your serialize
function for the text serializer and timestamp types.
Alternatively, you could give your serialization back-ends traits, and specialize when the trait says it wants textual output. Or, you could define a to_string
method, and in certain back-ends check whether there's a to_string
method and use that to convert the structure to a string rather than recursing into its individual fields.
Much of this gets a lot easier if you are willing to use C++20, because then you can check for the presence of methods much more easily than SFINAE by using requires
clauses. For example:
template<typename T> void
serialize(TextSerializer &ts, const T &t)
{
if constexpr (requires { to_string(t); }) {
// use to_string(t) to represent serialized object
}
else {
// visit field by field
}
}
Upvotes: 1