设计具有多个可调用项/谓词的 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) {
// ...
}
};
我正在尝试通过使用现有 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) {
// ...
}
};