Mengapa konsep same_as memeriksa jenis persamaan dua kali?

19

Melihat kemungkinan implementasi konsep same_as di https://en.cppreference.com/w/cpp/concepts/same_as saya perhatikan ada sesuatu yang aneh sedang terjadi.

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>;

Pertanyaan pertama adalah mengapa sebuah SameHelperkonsep digabungkan? Yang kedua adalah mengapa same_asmemeriksa apakah Tsama Udan Usama dengan T? Bukankah itu berlebihan?

pengguna7769147
sumber
Hanya karena SameHelper<T, U>mungkin benar bukan berarti SameHelper<U, T>mungkin.
Beberapa programmer Bung
1
itu intinya, jika a sama dengan b, b sama dengan a bukan?
user7769147
@ user7769147 Ya, dan ini mendefinisikan relasi itu.
François Andrieux
4
Hmm dokumentasi untuk std :: is_same bahkan mengatakan "Commutativity puas, yaitu untuk dua jenis T dan U, is_same<T, U>::value == truejika dan hanya jika is_same<U, T>::value == true." Ini menyiratkan bahwa pemeriksaan ganda ini tidak perlu
Kevin
1
Tidak, ini salah, std :: is_same mengatakan: jika dan hanya jika kondisinya berlaku, dua jenis bersifat komutatif. Ini belum tentu demikian. Tapi saya gagal menemukan contoh dua jenis non-komutatif.
Nemanja Boric

Jawaban:

16

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 constraintsseperti 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_vmungkin 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_ashanya 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.

Rin Kaenbyou
sumber
same_as <T, U> dan same_as <U, T> juga bisa berupa perbedaan atom yang berbeda tetapi hasilnya tetap sama. Mengapa kompiler sangat peduli tentang mendefinisikan same_as sebagai dua kendala atom yang berbeda yang dari sudut pandang logis adalah sama?
user7769147
2
Compiler diperlukan untuk mempertimbangkan setiap dua ekspresi sebagai berbeda untuk subsumption kendala, tetapi dapat mempertimbangkan argumen mereka dengan cara yang jelas. Jadi kita tidak hanya membutuhkan keduanya arah (sehingga tidak peduli di mana memesan mereka bernama ketika membandingkan kendala), kita juga perlu SameHelper: itu membuat dua kegunaan dari is_same_vberasal dari ekspresi yang sama.
Davis Herring
@ user7769147 Lihat jawaban yang diperbarui.
Rin Kaenbyou
1
Tampaknya kearifan konvensional salah tentang kesetaraan konsep. Tidak seperti templat is_same<T, U>yang identik dengan is_same<U, T>, dua batasan atom tidak dianggap identik kecuali mereka juga dibentuk dari ekspresi yang sama. Maka dari itu kebutuhan untuk keduanya.
AndyG
Bagaimana dengan are_same_as? template<typename T, typename U0, typename... Un> concept are_same_as = SameAs<T, U0> && (SameAs<T, Un> && ...);akan gagal dalam beberapa kasus. Sebagai contoh are_same_as<T, U, int>akan setara dengan are_same_as<T, int, U>tetapi tidak untukare_same_as<U, T, int>
user7769147
2

std::is_same didefinisikan sebagai true jika dan hanya jika:

T dan U menamai jenis yang sama dengan kualifikasi cv yang sama

Sejauh yang saya tahu, standar tidak mendefinisikan arti "tipe yang sama", tetapi dalam bahasa alami dan logika "sama" adalah hubungan ekivalensi dan dengan demikian bersifat komutatif.

Mengingat asumsi ini, yang saya anggap, is_same_v<T, U> && is_same_v<U, V>memang akan berlebihan. Tetapi same_­astidak ditentukan dalam hal is_same_v; itu hanya untuk eksposisi.

Pemeriksaan eksplisit untuk keduanya memungkinkan implementasi untuk same-as-implmemuaskan same_­astanpa komutatif. Menentukannya dengan cara ini menjelaskan dengan tepat bagaimana konsep itu berperilaku tanpa membatasi bagaimana itu bisa diimplementasikan.

Tepatnya mengapa pendekatan ini dipilih alih-alih menentukan dalam hal is_same_v, saya tidak tahu. Keuntungan dari pendekatan yang dipilih adalah bahwa kedua definisi tersebut tidak dapat digabungkan. Satu tidak tergantung pada yang lain.

eerorika
sumber
2
Saya setuju dengan Anda, tetapi argumen terakhir ini agak sulit. Bagi saya, ini terdengar seperti: "Hei, saya memiliki komponen yang dapat digunakan kembali ini yang memberi tahu saya apakah dua jenis adalah sama. Sekarang saya memiliki komponen lain yang perlu tahu apakah jenisnya sama, tetapi, alih-alih menggunakan kembali komponen saya sebelumnya , Saya hanya akan membuat solusi ad-hoc khusus untuk kasus ini. Sekarang saya sudah 'dipisahkan' pria yang membutuhkan definisi kesetaraan dari pria yang memiliki definisi kesetaraan. Yay! "
Cássio Renan
1
@ CássioRenan Tentu. Seperti yang saya katakan, saya tidak tahu mengapa, itu hanya alasan terbaik yang bisa saya pikirkan. Penulis mungkin memiliki alasan yang lebih baik.
eerorika