有趣的问题。我最近看了 Andrew Sutton 关于概念的演讲,在问答环节有人问了以下问题(以下链接中的时间戳):
CppCon 2018: Andrew Sutton “Concepts in 60: Everything you need to know and nothing you don't”
所以问题归结为:If I have a concept that says A && B && C, another says C && B && A, would those be equivalent?Andrew 回答是,但指出编译器有一些内部方法(对用户透明)将概念分解为原子逻辑命题(atomic constraints as 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> 将“爆炸”到不同的原子约束并变得不等价。
那么,为什么编译器会关心?
考虑this example:
#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> 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: 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>;
代码编译。