#pragma once #include #include #include #include namespace jvalidate::extension { /** * @brief A stub visitor type for building the ExtensionConstraint processing * hierarchy. Exposed for use with {@see ConstraintBase::visit}. */ struct VisitorBase { virtual ~VisitorBase() = default; }; template concept Constraint = std::is_base_of_v; namespace detail { /** * @brief A stub visitor type for proxying the ExtensionConstraint concrete * implementation type into a visitor. * Exists as a base class so that {@see jvalidate::extension::ConstraintBase} * can cast a VisitorBase to this. * * The call graph (including abstract -> virtual resolution) is as follows: * ExtensionConstraint::Impl::visit(VisitorBase const &) * -> ConstraintBase::visit(VisitorBase const &) * ---> TypedVisitor::visit(E const &) * -----> TypedVisitorImpl::visit(E const &) * -------> Visitor::Impl::dispatch(E const &) * ---------> Visitor::visit(E const &, ...) * * @tparam E The ExtensionConstraint type that this visitor applies to */ template struct TypedVisitor : VisitorBase { virtual Status visit(E const & cons) const = 0; }; /** * @brief The cleverness of this virtual class hierarchy. * Connects {@see jvalidate::extension::Visitor::Impl} to one of the extension * constraint types provided in its type signature. * * The call graph (including abstract -> virtual resolution) is as follows: * ExtensionConstraint::Impl::visit(VisitorBase const &) * -> ConstraintBase::visit(VisitorBase const &) * ---> TypedVisitor::visit(E const &) * -----> TypedVisitorImpl::visit(E const &) * -------> Visitor::Impl::dispatch(E const &) * ---------> Visitor::visit(E const &, ...) * * @tparam E The ExtensionConstraint type that this visitor applies to * @tparam CRTP A "Curiously Recurring Template Pattern" class (the actually * concrete class implementation of this Visitor). * Must implement a function "dispatch" which can accept at least E. */ template struct TypedVisitorImpl : TypedVisitor { Status visit(E const & cons) const final { return static_cast(this)->dispatch(cons); } }; } /** * @brief The preferred base class of all user-defined constraints. Using the * "Curiously Recurring Template Pattern", it is able to unwrap RTTI (Run Time * Type Information) through {@see TypedVisitor}. * * The call graph (including abstract -> virtual resolution) is as follows: * ExtensionConstraint::Impl::visit(VisitorBase const &) * -> ConstraintBase::visit(VisitorBase const &) * ---> TypedVisitor::visit(E const &) * -----> TypedVisitorImpl::visit(E const &) * -------> Visitor::Impl::dispatch(E const &) * ---------> Visitor::visit(E const &, ...) * * @tparam CRTP A "Curiously Recurring Template Pattern" class (the actually * concrete class implementation of this ExtensionConstraint). */ template struct ConstraintBase : constraint::ExtensionConstraint::Impl { Status visit(VisitorBase const & visitor) const final { return dynamic_cast const &>(visitor).visit( static_cast(*this)); } }; /** * @brief The visitor class responsible for performing validation on some list * of ExtensionConstraints as desired by the user. * * @tparam CRTP A "Curiously Recurring Template Pattern" class (the actually * concrete class implementation of this Visitor). * This class must implement the visit function for each extension constraint * in the Es... type list, as described by Visitor::Impl::dispatch. * * @tparam Es... A number of ExtensionConstraint implementations. Each of these * must fulfill the {@see jvalidate::extension::Constraint} contract. */ template class Visitor { private: template class Impl : public detail::TypedVisitorImpl>... { public: Impl(Visitor const * self, A const & document, V const & visitor) : self_(self), document_(document), visitor_(visitor) {} using detail::TypedVisitorImpl::visit...; template Status dispatch(E const & cons) const { // static_assert(Visitable, "Must implement all visitation functions"); return static_cast(self_)->visit(cons, document_, visitor_); } private: Visitor const * self_; A const & document_; V const & visitor_; }; public: template Status operator()(constraint::ExtensionConstraint const & cons, A const & document, V const & visitor) const { return cons.pimpl->visit(Impl{this, document, visitor}); } }; }