Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Designing a C++ concept with multiple invocables / predicates

I’m trying to make a C++20 concept by using an existing class as a blueprint. The existing class has 8 member functions that each take in a predicate:

struct MyGraphClass {
    auto get_outputs(auto n, auto predicate) { /* body removed */ };
    auto get_output(auto n, auto predicate) { /* body removed */ }
    auto get_outputs_full(auto n, auto predicate) { /* body removed */ }
    auto get_output_full(auto n, auto predicate) { /* body removed */ }
    auto get_inputs(auto n, auto predicate) { /* body removed */ }
    auto get_input(auto n, auto predicate) { /* body removed */ }
    auto get_inputs_full(auto n, auto predicate) { /* body removed */}
    auto get_input_full(auto n, auto predicate) { /* body removed */ }
    /* remainder of class removed */
}

To support this in my concept, I’ve used 8 different concept parameters of std::predicate, one for each member function. The reason I did this (as opposed to using a single concept parameter for all predicates) is that the type of each predicate isn’t known before-hand (e.g. an invocation of get_outputs(n, predicate) could be using a function pointer for the predicate while an invocation of get_inputs(n, predicate) could be using a functor for the predicate).

template<typename P, typename E, typename N, typename ED>
concept EdgePredicateConcept = std::predicate<P, E, N, N, ED>;

template<typename DG, typename N, typename ND, typename E, typename ED, typename EP1, typename EP2, typename EP3, typename EP4, tpyename EP5, typename EP6, typename EP7, typename EP8>
concept ConstantDirectedGraphConcept =
    requires EdgePredicateConcept<EP1, E, N, ED>
    && requires EdgePredicateConcept<EP2, E, N, ED>
    && requires EdgePredicateConcept<EP3, E, N, ED>
    && requires EdgePredicateConcept<EP4, E, N, ED>
    && requires EdgePredicateConcept<EP5, E, N, ED>
    && requires EdgePredicateConcept<EP6, E, N, ED>
    && requires EdgePredicateConcept<EP7, E, N, ED>
    && requires EdgePredicateConcept<EP8, E, N, ED>
    && requires(DG g, N n, ND nd, E e, ED ed, EP1 ep1, EP2 ep2, EP3 ep3, EP4 ep4, EP5 ep5, EP6 ep6, EP7 ep7, EP8 ep8) {
        { g.get_outputs(n, ep1) } -> /* return removed */;
        { g.get_output(n, ep2) } -> /* return removed */;        
        { g.get_outputs_full(n, ep3) } -> /* return removed */;
        { g.get_output_full(n, ep4) } -> /* return removed */;        
        { g.get_inputs(n, ep5) } -> /* return removed */;
        { g.get_input(n, ep6) } -> /* return removed */;        
        { g.get_inputs_full(n, ep7) } -> /* return removed */;
        { g.get_input_full(n, ep8) } -> /* return removed */;
        /* remainder of requirements removed */
    };

Since I don’t know the exact types of the predicates beforehand, I’m not sure what to set EP1EP8 to when I’m applying this concept. Even if I did, the number of concept parameters makes this concept difficult to use.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Is there a sane way of getting this to work?

>Solution :

I would do it this way.

First, your graph concept has several associated types. Similar to how a range in C++ has an iterator type (amongst others). We don’t write range<R, I>, we just write range<R>. If R is a range, then it has some iterator type – we don’t ask if R is a range with iterator I. We may not know I ex ante. Similarly, we have input_iterator<I>, and not input_iterator<I, value_type, reference, difference_type>. I defines those other things (if it’s actually an iterator).

So we’ll start with some aliases:

template <class G> using node_type = /* ... */;
template <class G> using edge_type = /* ... */;
// ...

Next, you want your graph type to accept an arbitrary predicate. There’s no way to phrase arbitrary predicate in C++. But we can do the next best thing: just pick one arbitrary one. If the graph type works for some arbitrary private type that you define, it probably works for any such thing. Thus:

namespace impl {
    template <class G>
    struct some_predicate {
        // intentionally not default constructible
        // since predicate doesn't have to be
        some_predicate() = delete;

        // likewise doesn't have to be copyable, though you
        // may just want to default these anyway (to allow
        // having by-value predicates, for instance)
        some_predicate(some_predicate const&) = delete;
        some_predicate& operator=(some_predicate const&) = delete;

        // but it does need this call operator
        auto operator()(edge_type<G> const&,
                        node_type<G> const&,
                        node_type<G> const&,
                        edge_data_type<G> const&) const
            -> bool;
    };
}

We don’t need to define that call operator since we’re not using it anyway. But we can use some_predicate to build up our concept:

template <typename G>
concept ConstantDirectedGraphConcept =
    requires(G g, node_type<G> n, impl::some_predicate<G> p) {
        g.get_outputs(n, p);
        g.get_output(n, p);
        g.get_outputs_full(n, p);
        g.get_output_full(n, p);
        g.get_inputs(n, p);
        g.get_input(n, p); 
        g.get_inputs_full(n, p);
        g.get_input_full(n, p);
    };

That should get you most of the way there. If a user’s graph type works with this arbitrary predicate, then it probably will work for any arbitrary predicate.

Possibly (depending on how node_type and friends are defined) this needs to be:

template <typename G>
concept ConstantDirectedGraphConcept = requires {
        typename node_type<G>;
        typename edge_type<G>;
        typename node_data_type<G>;
        typename edge_data_type<G>;   
    } && requires(G g, node_type<G> n, impl::some_predicate<G> p) {
        // ...
    }
};
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading