Motivated by Sean Parent's "Runtime Polymorphism" I implemented a Serializable
class that uses type-erasure to dispatch Serializable::serialize(...)
⟶ obj.serialize(...)
, where obj
is a wrapped object.
struct Serializable
{
template <typename T>
Serializable(T obj)
: m_self(std::make_unique<Model<T> >(std::move(obj))) {}
/// Writes itself to a write storage
void serialize(Storage& outStream)
{ return m_self->serialize(outStream); }
private:
struct Concept
{
virtual ~Concept() = default;
virtual void serialize(Storage& outStream) = 0;
};
template <typename T>
class Model final : public Concept
{
public:
Model(T x) : m_data(std::move(x)) {}
private:
void serialize(Storage& outStream) override
{ m_data.serialize(outStream); }
private:
T m_data;
};
private:
std::unique_ptr<Concept> m_self;
};
Now I would like to extend Serializable
with another model class that would dispatch Serializable::serialize(...)
to a free function with obj
as an argument: Serializable::serialize(...)
⟶ serialize(obj, ...)
Then I would like a template constructor of Serializable
to decide which model to use by checking the existence of either T::serialize(...)
or serialize(const T&, ...)
Is it possible by any means (e.g., SFINAE) to automatically construct Serializable
so that it uses a method serialization if possible and free-function serialization otherwise?
Feel free to use any C++ standard up to C++17.
You can devise your own trait to find out whether the class has the correct serialize
member. There are several ways to do it, this is one of them:
template <class T, class = void>
struct HasMemberSerialize : std::false_type
{};
template <class T>
struct HasMemberSerialize<T, std::void_t<decltype(std::declval<T>().serialize(std::declval<Storage&>()))>> : std::true_type
{};
Then, add a new template parameter to Model
and use the trait to find its argument:
struct Serializable
{
template <typename T>
Serializable(T obj)
: m_self(std::make_unique<Model<T, HasMemberSerialize<T>::value> >(std::move(obj))) {}
/// Writes itself to a write storage
void serialize(Storage& outStream)
{ return m_self->serialize(outStream); }
private:
struct Concept
{
virtual ~Concept() = default;
virtual void serialize(Storage& outStream) = 0;
};
template <typename T, bool Member>
class Model;
private:
std::unique_ptr<Concept> m_self;
};
template <typename T>
class Serializable::Model<T, true> final : public Serializable::Concept
{
public:
Model(T x) : m_data(std::move(x)) {}
private:
void serialize(Storage& outStream) override
{ m_data.serialize(outStream); }
private:
T m_data;
};
template <typename T>
class Serializable::Model<T, false> final : public Serializable::Concept
{
public:
Model(T x) : m_data(std::move(x)) {}
private:
void serialize(Storage& outStream) override
{ serialize(m_data, outStream); }
private:
T m_data;
};