实现与C ++ 20概念配对的概念

时间:2020-02-23 00:54:18

标签: c++ c++20 c++-concepts

要打印任何类型的std::pair,我们可以实现以下方法:

template<typename First, typename Second>
void printPair(const std::pair<First, Second>& p) {
    std::cout << p.first << ", " << p.second << std::endl;
}

但是,假设我们要根据以下要求实现一种可以打印任何种类的一对的方法,不一定std::pair

  • 它有一个firstsecond公共字段
  • 它具有first_typesecond_type个公共内部类型
  • first的类型== first_type
  • second的类型== second_type

拥有一个concept,我们称之为 Pair ,可以允许编写类似以下的方法:

void printPair(const Pair auto& p) {
    std::cout << p.first << ", " << p.second << std::endl;
}

如何定义这样的concept

4 个答案:

答案 0 :(得分:8)

这里有一些有趣的微妙之处。

template<class P>
concept Pair = requires(P p) {
    typename P::first_type;
    typename P::second_type;
    p.first;
    p.second;
    requires std::same_as<decltype(p.first), typename P::first_type>;
    requires std::same_as<decltype(p.second), typename P::second_type>;
};

前四行有些多余,但是可以帮助产生更好的错误消息。 其余的行应该是不言自明的。请注意,在普通类成员访问上使用decltype会产生数据成员的声明类型。

最后两行也可以写为

    { p.first } -> std::same_as<typename P::first_type&>;
    { p.second } -> std::same_as<typename P::second_type&>;

在这里,化合物要求类型约束应用于decltype((p.first))。该表达式是一个左值,因此产生的类型是一个左值引用类型。请注意,此版本将同时接受first_type first;first_type& first;

答案 1 :(得分:3)

在@Nicol Bolas对原始问题发表评论之后,我同意缩小var arr = [ {status: "ABC", groupID: "Group 1"}, {status: "PQR", groupID: "Group 1"}, {status: "ABC", groupID: "Group 1"}, {status: "ABC", groupID: "Group 1"}, {status: "XYZ", groupID: "Group 1"}, {status: "ABC", groupID: "Group 2"}, {status: "ABC", groupID: "Group 2"}, {status: "ABC", groupID: "Group 2"}, {status: "ABC", groupID: "Group 3"}, {status: "PQR", groupID: "Group 3"}, {status: "XYZ", groupID: "Group 4"}, {status: "LMN", groupID: "Group 4"}, {status: "PQR", groupID: "Group 4"}, {status: "ABC", groupID: "Group 5"} ]; const filterByStatus = (st, array) => { const ignore = new Set(); return array.reduce((acc, {status, groupID}) => { if (status !== st) { ignore.add(groupID); acc.delete(groupID); } else if (!ignore.has(groupID)) { acc.add(groupID); } return acc; }, new Set()) } filterByStatus("ABC", arr); 以仅允许符合concept要求的Pair并不是最佳设计,最好允许以下所有选项:

  • std::pair和具有std::pairfirst字段的类似类
  • 大小为2的second,大小为2的std::tuple和类似的类

std::array确实属于两类,因为它提出了类似元组的语法,但是我们希望能够容纳暴露 first second的用户类型。 字段,但未实现类似元组的语法。

为此,我们可以实现两个单独的概念,然后使用联合来创建第三个概念:


1。 SimplePair std::pair

concept

2。 TuplePair template<class P> concept SimplePair = requires(P p) { p.first; p.second; };

concept
上面的

^还支持std :: array


3。配对template<class P> concept TuplePair = requires(P p) { requires std::tuple_size<P>::value == 2; std::get<0>(p); std::get<1>(p); };

concept

现在,我们可以在template<class P> concept Pair = TuplePair<P> || SimplePair<P>; 中使用requires clause来拥有通用的 printPair

if constexpr

用法示例

void printPair(const Pair auto& p) {
    if constexpr( SimplePair<decltype(p)> ) {
        std::cout << p.first << ", " << p.second << std::endl;
    }
    else {
        std::cout << std::get<0>(p) << ", " << std::get<1>(p) << std::endl;
    }
}

代码:https://godbolt.org/z/MXgqu3

答案 2 :(得分:1)

旧语法-用于历史目的

下面的代码在某些时间点上对概念技术规范的早期版本有效,并且可以通过实验实现进行编译,但在更高版本的TS中已更改,并且在以下版本中不再有效C ++ 20规范。出于历史原因保留在此处,并作为规格更改的注释。


Concepts TS的旧版本具有以下语法:

template<typename _pair>
concept Pair = requires(_pair p) {
    { p.first } -> typename _pair::first_type;
    { p.second } -> typename _pair::second_type;
};

以上语法在C ++ 20中无效。有关有效的C ++ 20语法,请参见此问题的其他答案。


这将允许通用printPair适用于std::pair以及符合“对”要求的任何其他用户“对”:

void printPair(const Pair auto& p) {
    std::cout << p.first << ", " << p.second << std::endl;
}

struct UserPair {
    int first = 1;
    const char* second = "hello";
    using first_type = decltype(first);
    using second_type = decltype(second);
};

int main() {
    printPair(std::make_pair(1, 3));
    printPair(UserPair{});
}

具有旧版本TS的工作代码示例:https://godbolt.org/z/x6f76D

答案 3 :(得分:0)

我真的很喜欢这个问题及其周围的讨论,特别是T.C的解决方案(我在那儿没有50分要发表评论,因此我将发表评论作为另一种解决方案)。 我只是从类似的情况出发,既需要配对概念,又需要该库同时在C ++ 17和C ++ 20上工作。

此解决方案来自T.C.适用于c ++ 17和c ++ 20。

return

其中template<class P> concept bool Pair = requires(P p) { typename P::first_type; typename P::second_type; p.first; p.second; requires my_same_as<decltype(p.first), typename P::first_type>; requires my_same_as<decltype(p.second), typename P::second_type>; }; 在c ++ 20中被定义为my_same_as

std::same_as

我尝试了几种“成对实现”,有趣的是,字段template<class Me, class Other> concept bool my_same_as = std::is_same_v<Me, Other> && std::is_same_v<Other, Me>; first可能与引用或非引用类型不同。

T.C。提到我们可以将字段替换为:

second

我发现这仅适用于c ++ 20,奇怪的是不适用于c ++ 17(它可以很好地编译,但是与概念不匹配!)。某种程度上,它与引用或非引用都不匹配(要求使用 { p.first } -> my_same_as<typename P::first_type&>; { p.second } -> my_same_as<typename P::second_type&>; ||进行复杂的实现)。

我为c ++ 17和c ++ 20找到的一个便携式解决方案是:

std::remove_reference_t

其中template<typename P> concept bool Pair = requires(P p) { typename P::first_type; typename P::second_type; { p.first } -> my_convertible_to<typename P::first_type>; { p.second } -> my_convertible_to<typename P::second_type>; }; 等同于c ++ 20中的my_convertible_to

std::convertible_to

我无法解释为什么这种微妙的行为从c ++ 17变为c ++ 20(基于template <class From, class To> concept bool my_convertible_to = std::is_convertible_v<From, To> && requires(std::add_rvalue_reference_t<From> (&f)()) { static_cast<To>(f()); }; 逻辑),但是我在这里发布是因为它可能会在类似情况下帮助其他人。我将g ++-8用于c ++ 17,将g ++-10.1用于c ++ 20。感谢所有的学习!