From: Ruben Perez (rubenperez038_at_[hidden])
Date: 2025-05-05 10:09:57


> > 2. It sounds strange to me that policies have two functions: a. they behave
> > behavior (e.g. what to do in case of error) and b. they act as a type
> > registry. That confused me a lot, because in other libraries I got the
> > impression that policies only do a. Is there anything preventing the
> > separation of concerns here?
>
> In general, policies can have (static) state. For example,
> vectored_error_handler contains a static std::function. Each policy that uses it
> needs its own. That's the reason for all the CRTPing, and why `fork` rebinds the
> first template argument of all the templatized facets to the new policy.
>
> All the methods in the same policy count, at dispatch time, on data created by
> initialize<Policy>(). That is why methods have to be scoped in the policy. The
> same holds for class registrars: the type_ids they store at static construction
> time are not necessarily the same thing in different policies.
>
> > 3. Some of the complexity with policies (like needing a fork function) seem
> > to be stemming from the point above. What do you think? For instance,
> > couldn't facets be made regular members? Then add/fork can be implementing
> > by just inheriting from a base policy, using regular C++.
>
> Do you mean static or instance members? If it is the latter, then policies would
> become objects. Which we can pass as template parameters, but it would run into
> difficulties, because we don't have universal template parameters yet. A simple
> example: use_classes. It uses std::is_base_of to detect if the last class is a
> policy. So a policy has got to be a type (unless we change the contracts a lot).
>
> If you mean static members, we are back to the problem of separating my_policy's
> error stream from your_policy's.
>

As I think I didn't explain myself enough in my previous message, I'd
like to expand on what I meant by this. Take the current vptr_vector
facet, for example (simplified):

template<class Policy, typename Facet = void>
class vptr_vector {
    static std::vector<element_type> vptrs;

  public:
    template<typename ForwardIterator>
    static auto register_vptrs(ForwardIterator first, ForwardIterator last);
};

And the current basic_policy implementation (also simplified):

// domain<Policy> contains static members
template<class Policy, class... Facets>
struct basic_policy : abstract_policy, domain<Policy>, Facets... {
    using facets = mp11::mp_list<Facets...>;
};

Could it be possible to write:

// Members are no longer static
template<class Policy, typename Facet = void>
class vptr_vector {
    std::vector<element_type> vptrs;

  public:
    template<typename ForwardIterator>
    auto register_vptrs(ForwardIterator first, ForwardIterator last);
};

// domain now contains regular members (no longer static)
template<class Policy, class... Facets>
struct basic_policy : abstract_policy, domain, Facets... {
    std::tuple<Facets...> facets;
};

// A method_container is linked to a policy, and is what you use to
register methods
struct debug_method_container {
    static debug_policy policy; // Instead of many static members, just this one
};

// Register classes and methods
BOOST_OPENMETHOD_CLASSES(base_node, node1, node2, debug_method_container)

If this is possible, you could simplify how you store facets, making
them regular members so you don't need add, replace and fork:

// Marker class to say "this facet it not implemented"
struct facet_not_implemented {};

struct abstract_policy {
   facet_not_implemented rtti;
   facet_not_implemented extern_vptr;
   facet_not_implemented type_hash;
   facet_not_implemented error_handler;
   facet_not_implemented runtime_checks;
   facet_not_implemented error_output;
   facet_not_implemented trace_output;
};

struct release_policy : abstract_policy {
   facet_not_implemented rtti;
   fast_perfect_hash type_hash;
   vptr_vector<fast_perfect_hash> extern_vptr;
   vectored_error_handler<facet_not_implemented> error_handler;
};

struct debug_policy : release_policy {
    runtime_checks runtime_checks;
    basic_error_output<> error_output;
    basic_trace_output<> trace_output;
    vectored_error_handler<basic_error_output<>> error_handler;
};

This avoids CRTP and all the facets machinery. It also makes
dependencies between facets explicit, which I think it's good.
Currently, replacing type_hash seems to influence how vptr_vector
behaves, but I could only arrive at this conclusion by inspecting the
source code.

What are your thoughts on this proposal?