Pertanyaan menarik. Saya baru-baru ini menyaksikan pembicaraan Andrew Sutton tentang Konsep, dan dalam sesi tanya jawab seseorang menanyakan pertanyaan berikut (cap waktu di tautan berikut):
CppCon 2018: Andrew Sutton “Konsep dalam 60: Segala sesuatu yang perlu Anda ketahui dan tidak ada yang tidak Anda lakukan”
Jadi pertanyaannya bermuara pada: If I have a concept that says A && B && C, another says C && B && A, would those be equivalent?
Andrew menjawab ya, tetapi menunjukkan fakta bahwa kompiler memiliki beberapa metode internal (yang transparan bagi pengguna) untuk menguraikan konsep menjadi proposisi logis atom ( atomic constraints
seperti kata Andrew istilah) dan memeriksa apakah mereka setara.
Sekarang lihat apa yang dikatakan cppreference std::same_as
:
std::same_as<T, U>
subsume std::same_as<U, T>
dan sebaliknya.
Ini pada dasarnya adalah hubungan "jika-dan-hanya-jika": mereka menyiratkan satu sama lain. (Kesetaraan Logis)
Dugaan saya adalah bahwa di sini kendala atomnya std::is_same_v<T, U>
. Cara memperlakukan kompiler std::is_same_v
mungkin membuat mereka berpikir std::is_same_v<T, U>
dan std::is_same_v<U, T>
sebagai dua kendala yang berbeda (mereka adalah entitas yang berbeda!). Jadi jika Anda menerapkan std::same_as
hanya menggunakan salah satu dari mereka:
template< class T, class U >
concept same_as = detail::SameHelper<T, U>;
Kemudian std::same_as<T, U>
dan std::same_as<U, T>
akan "meledak" ke berbagai batasan atom dan menjadi tidak setara.
Nah, mengapa kompiler peduli?
Pertimbangkan contoh ini :
#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;
}
Idealnya, my_same_as<T, U> && std::integral<T>
subsidi my_same_as<U, T>
; oleh karena itu, kompiler harus memilih spesialisasi templat kedua, kecuali ... tidak: kompiler memancarkan kesalahan error: call of overloaded 'foo(int, int)' is ambiguous
.
Alasan di balik ini adalah bahwa karena my_same_as<U, T>
dan my_same_as<T, U>
tidak saling menyatukan, my_same_as<T, U> && std::integral<T>
dan my_same_as<U, T>
menjadi tak tertandingi (pada set kendala yang dipesan sebagian di bawah hubungan subsumsi).
Namun, jika Anda mengganti
template< class T, class U >
concept my_same_as = SameHelper<T, U>;
dengan
template< class T, class U >
concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;
Kode mengkompilasi.
SameHelper<T, U>
mungkin benar bukan berartiSameHelper<U, T>
mungkin.