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 fromDAGNode
and 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 DAGNode
s 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