实现与 C++20 概念配对的概念
Implementing a concept for Pair with C++20 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
,基于以下要求:
- 它有一个
first
和 second
public 字段
- 它有一个
first_type
和 second_type
public 内部类型
first
的类型 == first_type
second
的类型 == second_type
有一个 concept
,我们称它为 Pair,可以允许编写如下方法:
void printPair(const Pair auto& p) {
std::cout << p.first << ", " << p.second << std::endl;
}
如何定义这样的concept
?
旧语法 - 用于历史目的
下面的代码在 概念技术规范 的早期版本的某个时间点有效,并通过实验实现进行编译,但在 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
以及符合 Pair 要求的任何其他用户 "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
这里有一些有趣的微妙之处。
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>;
};
前四行有些多余,但可以帮助生成更好的错误消息。
其余行应该是不言自明的。请注意,在普通 class 成员访问上使用 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;
.
在@Nicol Bolas 对原始问题的评论之后,我同意将 concept
缩小到只允许符合 std::pair
要求的 Pair 并不是最好的设计,最好允许所有以下:
std::pair
和类似 类 与 first
和 second
字段
std::tuple
尺码 2,std::array
尺码 2 和类似 类
确实 std::pair
属于这两个类别,因为它提出了类似元组的语法,但是我们希望能够容纳先公开 的用户类型和 second 字段,但未实现类似元组的语法。
为此,我们可以实现两个独立的概念,然后使用连词创建第三个:
1。简单配对 concept
template<class P>
concept SimplePair = requires(P p) {
p.first;
p.second;
};
2。元组对 concept
template<class P>
concept TuplePair = requires(P p) {
requires std::tuple_size<P>::value == 2;
std::get<0>(p);
std::get<1>(p);
};
^以上也支持std::array
3。一对 concept
template<class P>
concept Pair = TuplePair<P> || SimplePair<P>;
现在我们可以有一个通用的 printPair,在 if constexpr
:
中使用 requires clause
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;
}
}
使用示例
struct MyPair {
int first = 5;
const char* second = "six";
};
int main() {
printPair(std::make_tuple(1, "two")); // 1, two
printPair(std::make_pair(3, 4)); // 3, 4
printPair(MyPair{}); // 5, six
printPair(std::array{7, 8}); // 7, 8
// not allowed, compilation error:
// printPair(std::array{9, 10, 11});
// printPair(std::make_tuple("one"));
// printPair(std::make_tuple(1, 2, 3));
}
我真的很喜欢这个问题和围绕它的讨论,特别是来自 T.C 的解决方案(我没有 50 分在那里评论,所以我将 post 评论作为另一个解决方案).
我刚刚经历了类似的情况,需要一对概念,但还需要库在 C++17 和 C++20 上工作。
此解决方案来自 T.C。适用于 c++17 和 c++20。
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>;
};
其中 my_same_as
定义为来自 c++20 的 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>;
我已经尝试了几个 "implementations of pairs",有趣的是字段 first
和 second
可能因参考或非参考类型而异。
T.C。提到我们可以将字段替换为:
{ p.first } -> my_same_as<typename P::first_type&>;
{ p.second } -> my_same_as<typename P::second_type&>;
我发现这只适用于 c++20,奇怪的是不适用于 c++17(它编译很好,但与概念不匹配!)。不知何故,它既不匹配引用也不匹配非引用(需要使用 ||
和 std::remove_reference_t
的复杂实现)。
我为 c++17 和 c++20 找到的一个可移植解决方案是:
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>;
};
其中 my_convertible_to
等同于 c++20 中的 std::convertible_to
:
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());
};
我无法解释为什么这种微妙的行为从 c++17 变为 c++20(在 is_same_v
逻辑上),但我 post 在这里,因为它可能会帮助其他人类似的情况。我对 c++17 使用 g++-8,对 c++20 使用 g++-10.1。感谢大家的学习!
要打印任何类型的 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
public 字段 - 它有一个
first_type
和second_type
public 内部类型 first
的类型 ==first_type
second
的类型 ==second_type
有一个 concept
,我们称它为 Pair,可以允许编写如下方法:
void printPair(const Pair auto& p) {
std::cout << p.first << ", " << p.second << std::endl;
}
如何定义这样的concept
?
旧语法 - 用于历史目的
下面的代码在 概念技术规范 的早期版本的某个时间点有效,并通过实验实现进行编译,但在 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
以及符合 Pair 要求的任何其他用户 "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
这里有一些有趣的微妙之处。
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>;
};
前四行有些多余,但可以帮助生成更好的错误消息。
其余行应该是不言自明的。请注意,在普通 class 成员访问上使用 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;
.
在@Nicol Bolas 对原始问题的评论之后,我同意将 concept
缩小到只允许符合 std::pair
要求的 Pair 并不是最好的设计,最好允许所有以下:
std::pair
和类似 类 与first
和second
字段std::tuple
尺码 2,std::array
尺码 2 和类似 类
确实 std::pair
属于这两个类别,因为它提出了类似元组的语法,但是我们希望能够容纳先公开 的用户类型和 second 字段,但未实现类似元组的语法。
为此,我们可以实现两个独立的概念,然后使用连词创建第三个:
1。简单配对 concept
template<class P>
concept SimplePair = requires(P p) {
p.first;
p.second;
};
2。元组对 concept
template<class P>
concept TuplePair = requires(P p) {
requires std::tuple_size<P>::value == 2;
std::get<0>(p);
std::get<1>(p);
};
^以上也支持std::array
3。一对 concept
template<class P>
concept Pair = TuplePair<P> || SimplePair<P>;
现在我们可以有一个通用的 printPair,在 if constexpr
:
requires clause
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;
}
}
使用示例
struct MyPair {
int first = 5;
const char* second = "six";
};
int main() {
printPair(std::make_tuple(1, "two")); // 1, two
printPair(std::make_pair(3, 4)); // 3, 4
printPair(MyPair{}); // 5, six
printPair(std::array{7, 8}); // 7, 8
// not allowed, compilation error:
// printPair(std::array{9, 10, 11});
// printPair(std::make_tuple("one"));
// printPair(std::make_tuple(1, 2, 3));
}
我真的很喜欢这个问题和围绕它的讨论,特别是来自 T.C 的解决方案(我没有 50 分在那里评论,所以我将 post 评论作为另一个解决方案). 我刚刚经历了类似的情况,需要一对概念,但还需要库在 C++17 和 C++20 上工作。
此解决方案来自 T.C。适用于 c++17 和 c++20。
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>;
};
其中 my_same_as
定义为来自 c++20 的 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>;
我已经尝试了几个 "implementations of pairs",有趣的是字段 first
和 second
可能因参考或非参考类型而异。
T.C。提到我们可以将字段替换为:
{ p.first } -> my_same_as<typename P::first_type&>;
{ p.second } -> my_same_as<typename P::second_type&>;
我发现这只适用于 c++20,奇怪的是不适用于 c++17(它编译很好,但与概念不匹配!)。不知何故,它既不匹配引用也不匹配非引用(需要使用 ||
和 std::remove_reference_t
的复杂实现)。
我为 c++17 和 c++20 找到的一个可移植解决方案是:
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>;
};
其中 my_convertible_to
等同于 c++20 中的 std::convertible_to
:
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());
};
我无法解释为什么这种微妙的行为从 c++17 变为 c++20(在 is_same_v
逻辑上),但我 post 在这里,因为它可能会帮助其他人类似的情况。我对 c++17 使用 g++-8,对 c++20 使用 g++-10.1。感谢大家的学习!