设计具有多个可调用项/谓词的 C++ 概念

Designing a C++ concept with multiple invocables / predicates

我正在尝试通过使用现有 class 作为蓝图来创建 C++20 概念。现有的 class 有 8 个成员函数,每个成员函数都接受一个谓词:

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 */
}

为了在我的概念中支持这一点,我使用了 std::predicate 的 8 个不同的概念参数,每个成员函数一个。我这样做的原因(与对所有谓词使用单个概念参数相反)是每个谓词的类型事先不知道(例如 get_outputs(n, predicate) 的调用可能使用函数指针谓词,而 get_inputs(n, 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 */
    };

因为我事先不知道谓词的确切类型,所以我不确定在应用此概念时将 EP1 - EP8 设置为什么。即使我这样做了,概念参数的数量也使这个概念很难使用。

有没有一种合理的方法让它发挥作用?

我会这样做。

首先,您的图形概念有几个相关类型。类似于 C++ 中的 range 具有 iterator 类型(以及其他)。我们不写range<R, I>,我们只写range<R>。如果 R 是一个范围,那么它有一些迭代器类型——我们不问 R 是否是一个带有迭代器 I 的范围。我们可能不知道 I 事前 。同样,我们有 input_iterator<I>,而不是 input_iterator<I, value_type, reference, difference_type>I 定义了其他东西(如果它实际上是 iterator)。

所以我们将从一些别名开始:

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

接下来,您希望图形类型接受任意谓词。在 C++ 中无法表达 任意谓词 。但我们可以做次优的事情:随便挑一个。如果图形类型适用于您定义的某些任意私有类型,那么它可能适用于任何此类事物。因此:

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;
    };
}

我们不需要定义那个调用运算符,因为我们无论如何都不会使用它。但是我们可以用some_predicate来建立我们的概念:

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);
    };

这应该可以帮助您完成大部分工作。如果用户的图形类型适用于此任意谓词,那么它可能适用于任何任意谓词。

可能(取决于 node_type 和朋友的定义)这需要是:

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) {
        // ...
    }
};