要打印任何类型的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
:
first
和second
公共字段first_type
和second_type
个公共内部类型first
的类型== first_type
second
的类型== second_type
拥有一个concept
,我们称之为 Pair ,可以允许编写类似以下的方法:
void printPair(const Pair auto& p) {
std::cout << p.first << ", " << p.second << std::endl;
}
如何定义这样的concept
?
答案 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::pair
和first
字段的类似类second
,大小为2的std::tuple
和类似的类 std::array
确实属于两类,因为它提出了类似元组的语法,但是我们希望能够容纳暴露 first 和 second的用户类型。 字段,但未实现类似元组的语法。
为此,我们可以实现两个单独的概念,然后使用联合来创建第三个概念:
std::pair
concept
template<class P>
concept SimplePair = requires(P p) {
p.first;
p.second;
};
concept
上面的^还支持std :: array
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;
}
}
答案 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。感谢所有的学习!