I have some C
data interfaces (pod structs) with each in different versions with an deserialization function for each version. (full code below)
user types:
typedef struct sInterface_v1_t {
int i;
}Interface_v1_t;
typedef struct sInterface_v2_t {
int i;
int j;
}Interface_v2_t;
typedef struct sInterface_v3_t {
int j;
float u;
}Interface_v3_t;
// latest version
typedef Interface_v3_t Interface_t;
void Interface_v1_deserialize(std::stringstream& io_str, Interface_v1_t* o_d)
{
io_str >> o_d->i;
}
void Interface_v2_deserialize(std::stringstream& io_str, Interface_v2_t* o_d)
{
io_str >> o_d->i;
io_str >> o_d->j;
}
void Interface_v3_deserialize(std::stringstream& io_str, Interface_v3_t* o_d)
{
io_str >> o_d->j;
io_str >> o_d->u;
}
The serialized data stream encode the data type and the version number, so that it is easy dispatchable
// type interfaceversion data...
std::string data_1 = "I 1 5";
std::string data_2 = "I 2 7 2";
std::string data_3 = "I 3 1 3.54";
I want to create a generic deserialization that always outputs the latest type.
At the end the deserialization code should look like:
std::stringstream s(data_1); // or data_2 or data_3
s >> ts; // remove type from string
Interface_t i = { 0 };
Interface_Deserializer<Interface_t>::instance().deserialize(s, &i);
For this the user has to provide a conversion function from the previous version to the newer version.
user converter:
static void convert(const Interface_v1_t* i_d, Interface_v2_t* o_d) {
o_d->i = i_d->i;
o_d->j = 0;
}
static void convert(const Interface_v2_t* i_d, Interface_v3_t* o_d) {
o_d->j = i_d->j;
o_d->u = 0.f;
}
I created a set of templates (see in the full listing below) that can achieve this, but the user has to create some boiler plate code to get them working.
user boiler plate
template <>
struct InterfaceVersionHelper<Interface_t, 1> :InterfaceVersionHelperBase<Interface_t, Interface_v1_t, 1, &Interface_v1_deserialize>
{};
template <>
struct InterfaceVersionHelper<Interface_t, 2> :InterfaceVersionHelperBase<Interface_t, Interface_v2_t, 2, &Interface_v2_deserialize>
{};
template <>
struct InterfaceVersionHelper<Interface_t, 3> :InterfaceVersionHelperBase<Interface_t, Interface_v3_t, 3, &Interface_v3_deserialize>
{};
static bool r1 = []() {
InterfaceVersionHelper<Interface_t, 1>::registerMe();
InterfaceVersionHelper<Interface_t, 2>::registerMe();
InterfaceVersionHelper<Interface_t, 3>::registerMe();
return true;
}();
template <>
struct LatestInterface<Interface_t> :public Interface_LatestVersion<Interface_t, 3> {};
I would like to get them reduced to some declarative statement like
static bool r =
InterfaceRegistererDefintion<
Interface_t,
InterfaceV<Interface_v3_t, 3, &Interface_v3_deserialize>,
InterfaceV<Interface_v2_t, 2, &Interface_v2_deserialize>,
InterfaceV<Interface_v1_t, 1, &Interface_v1_deserialize>
>::create();
where the first type is the type Interface name, the first tuple is the latest version (that is identically with Interface_t) and the rest of the list can be as long as needed.
Is there a way to achieve this (and maybe to reduce or simplify the rest of the template code)
maybe also the Converter<T, a, b>
template can be changed in a way, that it uses a ::convert(a, b)
routine if present. currently such conversions can be only done with Converter-specializations. (like from 1 to 3 in the example below)
c++11 features are allowed completely, maybe also c++14. The newest and fanciest post-c++14 features may be allowed only if really necessary (however - I would like to see suggestion based on them, to improve the code in future).
P.S.
just to make it clear. the code should stay that generic that it can be used to handle different Interfaces with their different versions.
Complete Code
#include <list>
#include <map>
#include <iostream>
#include <sstream>
#include <memory>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <functional>
template <typename T>
using DeserFun = void(std::stringstream&, T*);
template<typename IFACE>
void DummyDeser(std::stringstream& io_str, IFACE* o_d) {};
template <typename T>
struct InterfaceVersionHelperBaseBase {
using value_type_target = T;
virtual int version()const = 0;
virtual void deserialize(std::stringstream& io_str, T* o_d) = 0;
};
template <typename T, int LatestVersion>
struct Interface_LatestVersion {
enum {
version = LatestVersion
};
using value_type = T;
};
template <typename T>
struct LatestInterface :public Interface_LatestVersion<T, 0> {};
template <typename T>
struct Interface_Deserializer {
using value_type = T;
std::map<int, std::shared_ptr<InterfaceVersionHelperBaseBase<T>>> versionMap;
static Interface_Deserializer& instance() {
static Interface_Deserializer s_instance;
return s_instance;
};
void deserialize(std::stringstream& io_str, T* o_d)const {
int v = 0;
io_str >> v;
auto it = versionMap.find(v);
if (it != versionMap.end()) {
(*it).second->deserialize(io_str, o_d);
} else {
std::cerr << "unknown version " << v << " for " << typeid(T).name() << std::endl;
}
}
private:
Interface_Deserializer() {};
};
template <typename T, typename IFACE, int IFACE_VER, DeserFun<IFACE> DESER_FUN>
struct InterfaceVersionHelperBase : public InterfaceVersionHelperBaseBase<T>
{
using value_type_current = IFACE;
enum { ver = IFACE_VER };
int version()const override { return IFACE_VER; };
/// deserializes stream and converts to latest type
void deserialize(std::stringstream& io_str, T* o_d) override {
value_type_current o;
DESER_FUN(io_str, &o);
Converter<T, IFACE_VER, LatestInterface<T>::version>::convert(&o, o_d);
return;
};
/// registers helper for this interface version on the Interface_Deserializer
static void registerMe() {
std::pair<int, std::shared_ptr<InterfaceVersionHelperBaseBase<T>>> v;
v.first = IFACE_VER;
v.second.reset(new InterfaceVersionHelperBase<T, IFACE, IFACE_VER, DESER_FUN>());
Interface_Deserializer<T>::instance().versionMap.insert( v );
}
};
/// this has to be specialized for each interface version to point to the correct types and deserializer
template <typename IFACE, int IFACE_VER>
struct InterfaceVersionHelper :public InterfaceVersionHelperBase < IFACE, IFACE, 0, &DummyDeser<IFACE> >
{
};
/// generic converter that converts between two interface versions by converting the input one version up
/// by calling user convert function
/// this can also be specialized by used to implement specific from x to latest converter
template <typename IFACE, int IFACE_VER, int IFACE_VER2>
struct Converter {
static void convert(const typename InterfaceVersionHelper < IFACE, IFACE_VER>::value_type_current* i_d,
typename InterfaceVersionHelper < IFACE, IFACE_VER2>::value_type_current* o_d)
{
InterfaceVersionHelper < IFACE, IFACE_VER + 1>::value_type_current temp;
::convert(i_d, &temp);
Converter<IFACE, IFACE_VER + 1, IFACE_VER2>::convert(&temp, o_d);
}
};
/// converter specialization that aborts conversion, if both sides are the same interface versions
template <typename IFACE, int IFACE_VER>
struct Converter<IFACE, IFACE_VER, IFACE_VER>{
static void convert(const typename InterfaceVersionHelper < IFACE, IFACE_VER>::value_type_current* i_d,
typename InterfaceVersionHelper < IFACE, IFACE_VER>::value_type_current* o_d)
{
*o_d = *i_d;
}
};
/////////////////////////////
// user types / deserializers
/////////////////////////////
typedef struct sInterface_v1_t {
int i;
}Interface_v1_t;
typedef struct sInterface_v2_t {
int i;
int j;
}Interface_v2_t;
typedef struct sInterface_v3_t {
int j;
float u;
}Interface_v3_t;
// latest version
typedef Interface_v3_t Interface_t;
void Interface_v1_deserialize(std::stringstream& io_str, Interface_v1_t* o_d)
{
io_str >> o_d->i;
}
void Interface_v2_deserialize(std::stringstream& io_str, Interface_v2_t* o_d)
{
io_str >> o_d->i;
io_str >> o_d->j;
}
void Interface_v3_deserialize(std::stringstream& io_str, Interface_v3_t* o_d)
{
io_str >> o_d->j;
io_str >> o_d->u;
}
/////////////////////////////
// user converter
/////////////////////////////
static void convert(const Interface_v1_t* i_d, Interface_v2_t* o_d) {
o_d->i = i_d->i;
o_d->j = 0;
}
static void convert(const Interface_v2_t* i_d, Interface_v3_t* o_d) {
o_d->j = i_d->j;
o_d->u = 0.f;
}
// specialization for specific conversion
template <>
struct Converter<Interface_t, 1, 3> {
static void convert(const Interface_v1_t* i_d,
Interface_v3_t* o_d)
{
o_d->u = float(i_d->i);
o_d->j = 0;
}
};
/////////////////////////////
// user boiler plate
/////////////////////////////
template <>
struct InterfaceVersionHelper<Interface_t, 1> :InterfaceVersionHelperBase<Interface_t, Interface_v1_t, 1, &Interface_v1_deserialize>
{};
template <>
struct InterfaceVersionHelper<Interface_t, 2> :InterfaceVersionHelperBase<Interface_t, Interface_v2_t, 2, &Interface_v2_deserialize>
{};
template <>
struct InterfaceVersionHelper<Interface_t, 3> :InterfaceVersionHelperBase<Interface_t, Interface_v3_t, 3, &Interface_v3_deserialize>
{};
static bool r1 = []() {
InterfaceVersionHelper<Interface_t, 1>::registerMe();
InterfaceVersionHelper<Interface_t, 2>::registerMe();
InterfaceVersionHelper<Interface_t, 3>::registerMe();
return true;
}();
template <>
struct LatestInterface<Interface_t> :public Interface_LatestVersion<Interface_t, 3> {};
/*
// desired user boiler plate:
static bool r =
InterfaceRegistererDefintion<
Interface_t,
InterfaceV<Interface_v3_t, 3, &Interface_v3_deserialize>,
InterfaceV<Interface_v2_t, 2, &Interface_v2_deserialize>,
InterfaceV<Interface_v1_t, 1, &Interface_v1_deserialize>
>::create();
*/
/////////////////////////////
// user test data
/////////////////////////////
std::string data_1 = "I 1 5";
std::string data_2 = "I 2 7 2";
std::string data_3 = "I 3 1 3.54";
std::string data_4 = "I 4 1 3.54 3.14159";
int main(int argc, char* argv[])
{
std::string ts;
int ti;
{ // test deserializer
std::stringstream s1(data_1);
std::stringstream s2(data_2);
std::stringstream s3(data_3);
s1 >> ts >> ti; // remove type and version from string
s2 >> ts >> ti; //
s3 >> ts >> ti; //
Interface_v1_t i1 = { 0 };
Interface_v2_t i2 = { 0 };
Interface_v3_t i3 = { 0 };
Interface_v1_deserialize(s1, &i1);
Interface_v2_deserialize(s2, &i2);
Interface_v3_deserialize(s3, &i3);
}
{// test helper deserialziation and conversion
std::stringstream s1(data_1);
std::stringstream s2(data_2);
std::stringstream s3(data_3);
s1 >> ts >> ti; // remove type and version from string
s2 >> ts >> ti; //
s3 >> ts >> ti; //
Interface_t i1 = { 0 };
Interface_t i2 = { 0 };
Interface_t i3 = { 0 };
InterfaceVersionHelper< Interface_t, 1> h1;
InterfaceVersionHelper< Interface_t, 2> h2;
InterfaceVersionHelper< Interface_t, 3> h3;
h1.deserialize(s1, &i1);
h2.deserialize(s2, &i2);
h3.deserialize(s3, &i3);
}
{// test Interface_Deserializer
std::stringstream s1(data_1);
std::stringstream s2(data_2);
std::stringstream s3(data_3);
std::stringstream s4(data_4);
s1 >> ts; // remove type from string
s2 >> ts; //
s3 >> ts; //
s4 >> ts; //
Interface_t i1 = { 0 };
Interface_t i2 = { 0 };
Interface_t i3 = { 0 };
Interface_t i4 = { 0 };
auto ds = Interface_Deserializer<Interface_t>::instance();
ds.deserialize(s1, &i1);
ds.deserialize(s2, &i2);
ds.deserialize(s3, &i3);
ds.deserialize(s4, &i4); // should output an error
}
return 0;
}
Aucun commentaire:
Enregistrer un commentaire