有趣的问题。我最近看了安德鲁·萨顿(Andrew Sutton)关于概念的演讲,在问答环节中,有人问了以下问题(以下链接中的时间戳):
CppCon 2018:安德鲁·萨顿“ 60岁的概念:您需要知道的一切,而您不需要的一切”
因此问题归结为:If I have a concept that says A && B && C, another says C && B && A, would those be equivalent?
Andrew回答“是”,但指出了一个事实,即编译器具有一些内部方法(对用户透明),可将这些概念分解为原子逻辑命题(atomic constraints
如Andrew所说的术语),并检查它们是否当量。
现在看看cppreference的内容std::same_as
:
std::same_as<T, U>
归类std::same_as<U, T>
,反之亦然。
基本上,这是一种“如果且仅当”的关系:它们相互暗示。(逻辑等效)
我的猜测是,这里的原子约束是std::is_same_v<T, U>
。编译器对待的方式std::is_same_v
可能会使他们思考std::is_same_v<T, U>
并被std::is_same_v<U, T>
视为两个不同的约束(它们是不同的实体!)。因此,如果std::same_as
仅使用其中之一来实现:
template< class T, class U >
concept same_as = detail::SameHelper<T, U>;
然后std::same_as<T, U>
,std::same_as<U, T>
将“爆炸”到不同的原子约束,并且变得不等效。
好吧,为什么编译器会关心?
考虑这个例子:
#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<T, U> && std::integral<T>
包含在内my_same_as<U, T>
;因此,编译器应选择第二个模板特化,除非...否则:编译器会发出error error: call of overloaded 'foo(int, int)' is ambiguous
。
其背后的原因是,由于my_same_as<U, T>
和my_same_as<T, U>
不会相互包含,my_same_as<T, U> && std::integral<T>
并且my_same_as<U, T>
变得不可比拟(在包含关系下部分约束的约束集上)。
但是,如果您更换
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>;
代码会编译。
SameHelper<T, U>
可能是真实的并不意味着SameHelper<U, T>
可能就会如此。