在https://en.cppreference.com/w/cpp/concepts/same_as上查看same_as概念的可能实现时,我注意到发生了一些奇怪的事情。
namespace detail {
template< class T, class U >
concept SameHelper = std::is_same_v<T, U>;
}
template< class T, class U >
concept same_as = detail::SameHelper<T, U> && detail::SameHelper<U, T>;
第一个问题是为什么要定义samehelper
概念? 第二个是为什么same_as
检查t
是否与u
相同,u
是否与t
相同? 这不是多余的吗?
有趣的问题。 我最近看了Andrew Sutton关于概念的演讲,在问答环节有人问了以下问题(以下链接中的时间戳):CppCon 2018:Andrew Sutton“60年的概念:你需要知道的一切和你不知道的一切”
所以问题可以归结为:如果我有一个概念,它表示一个&&&; B和; C,另一个说C&&; B和; A,它们是否等价?
Andrew回答是的,但指出编译器有一些内部方法(对用户是透明的)来将概念分解成原子逻辑命题(原子约束
Andrew对术语的措辞)并检查它们是否等价。
现在看看cppreference对std::same_as
:
std::same_as
包含std::same_as
,反之亦然。
它基本上是一种“如果和仅当”的关系:它们相互暗示。 (逻辑等价)
我猜想这里的原子约束是std::is_same_v
。 编译器处理std::is_same_v
的方式可能会使它们认为std::is_same_v
和std::is_same_v
是两个不同的约束(它们是不同的实体!)。 因此,如果您只使用其中一个实现std::same_as
:
template< class T, class U >
concept same_as = detail::SameHelper<T, U>;
那么std::same_as
和std::same_as
将“爆炸”到不同的原子约束,变得不等价。
编译器为什么要关心呢?
请考虑以下示例:
#include <type_traits>
#include <iostream>
#include <concepts>
template< class T, class U >
concept SameHelper = std::is_same_v<T, U>;
template< class T, class U >
concept my_same_as = SameHelper<T, U>;
// template< class T, class U >
// concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;
template< class T, class U> requires my_same_as<U, T>
void foo(T a, U b) {
std::cout << "Not integral" << std::endl;
}
template< class T, class U> requires (my_same_as<T, U> && std::integral<T>)
void foo(T a, U b) {
std::cout << "Integral" << std::endl;
}
int main() {
foo(1, 2);
return 0;
}
理想情况下,my_same_as
包含my_same_as
; 因此,编译器应该选择第二个模板专门化,除了。。。它没有:编译器发出一个错误错误:重载'foo(int,int)‘的调用是不明确的
。
这背后的原因是,由于my_same_as
和my_same_as
不包含彼此,因此my_same_as
和my_same_as
变得不可比(在包含关系下的偏序约束集上)。
但是,如果替换
template< class T, class U >
concept my_same_as = SameHelper<T, U>;
与
template< class T, class U >
concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;
代码将编译。
std::is_same
当且仅当:
T和U使用相同的CV资格命名相同的类型
据我所知,标准并没有定义“同类型”的含义,但在自然语言和逻辑中,“同”是一种等价关系,因而是可交换的。
给出了这个假设,是_same_v
确实是多余的。 但是is_same_v
中没有指定same_as
; 那只是为了说明。
两者的显式检查允许same-as-impl
的实现满足same-as
而不是可交换的。 以这种方式指定它可以准确地描述概念的行为方式,而不会限制它的实现方式。
我不知道为什么选择这种方法而不是用is_same_v
来指定。 所选方法的一个优点是可以证明这两个定义是分离的。 一个不依赖另一个。