I have a header-only library that provides three class (templates), namely
-
an abstract
DAGNode,struct DAGNode; using NodePtr = std::shared_ptr<DAGNode>; class DAGNode { public: virtual ~DAGNode(){} virtual std::string serialize() const = 0; auto const& get_parents() const { return m_parents; } protected: std::vector<NodePtr> m_parents; }; -
non-abstract class template
impl::Derived<T>, which is derived fromDAGNodeand which is not part of the libraries API and// this needs to be specialized by consumer template <typename T> std::string serialize(T const&) { throw std::logic_error("Not implemented."); } namespace impl { // not part of the librarys API template <typename T> class Derived : public DAGNode { public: Derived(T const& t) : m_value(t) {} std::string serialize() const override final { return ::serialize(m_value); } void add_parent(NodePtr const& ptr) { m_parents.push_back(ptr); } private: T m_value; }; } // namespace impl -
class template
DerivedHolder<T>, which stores astd::shared_ptr<impl::Derived<T>>, which is part of the libraries API.template <typename T> class DerivedHolder { public: DerivedHolder(T const& t) : m_node(std::make_shared<impl::Derived<T>>(t)) {} NodePtr holder() const { return m_node; } void add_parent(NodePtr const& p) { m_node->add_parent(p); } private: std::shared_ptr<impl::Derived<T>> m_node; };
In the current design, DAGNode has a virtual method std::string serialize() const. Derived<T> final overrides this method and delegates to the function template template <typename T> std::string ::serialize.
A consumer of this library will have to specialize the ::serialize function template for whatever types it wants to use. A typical usage scenario is that the consumer will use the visitor pattern to iterate over all DAGNodes and call the virtual serialize method.
Here is a minimal complete code example.
This approach works well, but I have now come across a use-case, where a consumer of this library needs to replace template <typename T> ::serialize with its own "magic implementation" along the lines of:
template <typename T>
std::string magic_serialize(T const& t)
{
if constexpr (std::is_same_v<T, MyMagicClass> ) {
return t.as_string();
} else {
MagicClass m(t);
return m.as_string();
}
}
Since magic_serialize is in no way more special than ::serialize, specialization won't do the trick.
Therefore, we need to change the design pattern of the serialization framework of the header-only library. We are free to change any part of the library including the API, though I would prefer any minimally invasive solution over a complete rewrite. We are restricted to C++17 for now.
How can I change the library in such a way, that the consumer has complete control over how any type is to be serialized?
Aucun commentaire:
Enregistrer un commentaire