Câu hỏi thú vị. Gần đây tôi đã xem cuộc nói chuyện của Andrew Sutton về các khái niệm và trong phiên hỏi đáp có ai đó đã hỏi câu hỏi sau (dấu thời gian trong liên kết sau):
CppCon 2018: Andrew Sutton miếng Khái niệm trong 60: Mọi thứ bạn cần biết và không có gì bạn không biết
Vì vậy, câu hỏi rút ra: If I have a concept that says A && B && C, another says C && B && A, would those be equivalent?
Andrew trả lời là có, nhưng chỉ ra thực tế trình biên dịch có một số phương thức bên trong (rõ ràng đối với người dùng) để phân tách các khái niệm thành các mệnh đề logic nguyên tử ( atomic constraints
như Andrew đã diễn đạt thuật ngữ này) và kiểm tra xem chúng có tương đương.
Bây giờ hãy nhìn vào những gì cppreference nói về std::same_as
:
std::same_as<T, U>
phụ std::same_as<U, T>
và ngược lại.
Về cơ bản, đây là mối quan hệ "nếu và chỉ-nếu": chúng ngụ ý lẫn nhau. (Tương đương logic)
Phỏng đoán của tôi là ở đây các ràng buộc nguyên tử std::is_same_v<T, U>
. Cách trình biên dịch xử lý std::is_same_v
có thể khiến họ suy nghĩ std::is_same_v<T, U>
và std::is_same_v<U, T>
như hai ràng buộc khác nhau (chúng là các thực thể khác nhau!). Vì vậy, nếu bạn thực hiện std::same_as
chỉ sử dụng một trong số họ:
template< class T, class U >
concept same_as = detail::SameHelper<T, U>;
Sau đó std::same_as<T, U>
và std::same_as<U, T>
sẽ "nổ tung" với các ràng buộc nguyên tử khác nhau và trở nên không tương đương.
Vâng, tại sao trình biên dịch quan tâm?
Xem xét ví dụ này :
#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;
}
Lý tưởng nhất, my_same_as<T, U> && std::integral<T>
subsumes my_same_as<U, T>
; do đó, trình biên dịch nên chọn chuyên môn mẫu thứ hai, ngoại trừ ... nó không: trình biên dịch phát ra lỗi error: call of overloaded 'foo(int, int)' is ambiguous
.
Lý do đằng sau điều này là vì my_same_as<U, T>
và my_same_as<T, U>
không phụ thuộc lẫn nhau, my_same_as<T, U> && std::integral<T>
và my_same_as<U, T>
trở nên không thể so sánh được (trên tập các ràng buộc được sắp xếp một phần theo quan hệ của sự sụt giảm).
Tuy nhiên, nếu bạn thay thế
template< class T, class U >
concept my_same_as = SameHelper<T, U>;
với
template< class T, class U >
concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;
Mã biên dịch.
SameHelper<T, U>
có thể đúng không có nghĩa làSameHelper<U, T>
có thể.