Mengapa kita membutuhkan membutuhkan?

161

Salah satu sudut konsep C ++ 20 adalah bahwa ada situasi tertentu di mana Anda harus menulis requires requires. Misalnya, contoh ini dari [expr.prim.req] / 3 :

Sebuah membutuhkan ekspresi juga dapat digunakan dalam membutuhkan-klausul ([suhu]) sebagai cara menulis iklan kendala hoc pada argumen template yang seperti di bawah ini:

template<typename T>
  requires requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

Yang pertama membutuhkan memperkenalkan yang membutuhkan-klausa , dan yang kedua memperkenalkan yang membutuhkan-ekspresi .

Apa alasan teknis di balik memerlukan requireskata kunci kedua itu ? Mengapa kita tidak bisa membiarkan tulisan saja:

template<typename T>
  requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

(Catatan: tolong jangan jawab bahwa tata bahasanya requires)

Barry
sumber
25
Saran: "Apakah ada yang membutuhkan mensyaratkan?". Lebih serius, saya punya firasat bahwa itu adalah alasan yang sama noexcept(noexcept(...)).
Quentin
11
Keduanya requireshomonim menurut saya: mereka terlihat sama, mengeja sama, mencium aroma yang sama, tetapi secara intrinsik berbeda. Jika saya menyarankan perbaikan, saya akan menyarankan untuk mengganti nama salah satunya.
YSC
5
@YSC - co_requires? (Maaf, tidak bisa menahan).
StoryTeller - Unslander Monica
122
Di mana kegilaan akan berhenti? Hal berikutnya yang Anda tahu, kita akan miliki long long.
Eljay
8
@StoryTeller: "Membutuhkan diperlukan?" akan lebih aliteratif !!
PW

Jawaban:

81

Itu karena tata bahasa mengharuskannya. Itu benar.

Sebuah requireskendala tidak harus menggunakan requiresekspresi. Ia dapat menggunakan ekspresi konstanta boolean yang lebih atau kurang sembarang. Karena itu, requires (foo)harus menjadi requireskendala yang sah .

Sebuah requires ekspresi (hal yang bahwa tes apakah hal-hal tertentu mengikuti kendala tertentu) adalah konstruk yang berbeda; itu hanya diperkenalkan oleh kata kunci yang sama. requires (foo f)akan menjadi awal dari requiresekspresi yang valid .

Apa yang Anda inginkan adalah bahwa jika Anda menggunakan requiresdi tempat yang menerima kendala, Anda harus dapat membuat "batasan + ekspresi" keluar dari requiresklausa.

Jadi, inilah pertanyaannya: jika Anda menempatkan requires (foo)ke suatu tempat yang sesuai untuk kendala memerlukan ... seberapa jauh parser harus pergi sebelum dapat menyadari bahwa ini adalah kendala membutuhkan daripada ekspresi + kendala seperti yang Anda inginkan menjadi?

Pertimbangkan ini:

void bar() requires (foo)
{
  //stuff
}

Jika fooadalah tipe, maka (foo)adalah daftar parameter dari ekspresi yang diperlukan, dan semua yang ada di {}dalamnya bukan tubuh fungsi tetapi tubuh requiresekspresi itu. Kalau tidak, fooadalah ekspresi dalam requiresklausa.

Nah, Anda bisa mengatakan bahwa kompiler harus mencari tahu apa fooyang pertama. Tetapi C ++ benar - benar tidak menyukainya ketika tindakan dasar untuk mengurai urutan token mensyaratkan bahwa kompiler mengetahui apa yang dimaksud oleh pengidentifikasi sebelum dapat memahami token tersebut. Ya, C ++ peka konteks, jadi ini memang terjadi. Tetapi komite lebih suka menghindarinya jika memungkinkan.

Jadi ya, itu tata bahasa.

Nicol Bolas
sumber
2
Apakah masuk akal untuk memiliki daftar parameter dengan tipe tetapi tanpa nama?
NathanOliver
3
@ Quentin: Tentu saja ada kasus ketergantungan konteks dalam tata bahasa C ++. Tetapi panitia benar-benar mencoba untuk meminimalkan itu, dan mereka jelas tidak suka menambahkan lebih banyak .
Nicol Bolas
3
@RobertAndrzantai: Jika requiresmuncul setelah <>serangkaian argumen templat atau setelah daftar parameter fungsi, maka itu memerlukan-klausa. Jika requiresmuncul di mana ekspresi itu valid, maka itu adalah ekspresi yang diharuskan. Ini dapat ditentukan oleh struktur pohon parse, bukan isi dari pohon parse (spesifik tentang bagaimana pengidentifikasi didefinisikan akan menjadi isi pohon).
Nicol Bolas
6
@RobertAndrzantai: Tentu, ekspresi membutuhkan bisa menggunakan kata kunci yang berbeda. Tetapi kata kunci memiliki biaya besar dalam C ++, karena mereka berpotensi menghancurkan program apa pun yang menggunakan pengidentifikasi yang telah menjadi kata kunci. Proposal konsep sudah memperkenalkan dua kata kunci: conceptdan requires. Memperkenalkan yang ketiga, ketika yang kedua akan dapat mencakup kedua kasus tanpa masalah tata bahasa dan beberapa masalah yang dihadapi pengguna, hanya sia-sia. Bagaimanapun, satu-satunya masalah visual adalah bahwa kata kunci tersebut terjadi berulang dua kali.
Nicol Bolas
3
@RobertAndrzantai bagaimanapun juga adalah praktik yang buruk untuk menyejajarkan batasan seperti itu karena Anda tidak mendapatkan subsumsi seolah-olah Anda telah menulis konsep. Jadi mengambil pengenal untuk penggunaan yang rendah seperti fitur yang tidak disarankan bukanlah ide yang baik.
Rakete1111
60

Situasinya persis analog dengan noexcept(noexcept(...)). Tentu, ini terdengar lebih seperti hal yang buruk daripada hal yang baik, tetapi izinkan saya menjelaskannya. :) Kami akan mulai dengan apa yang sudah Anda ketahui:

C ++ 11 memiliki " noexcept-clauses" dan " noexcept-expressions." Mereka melakukan hal yang berbeda.

  • A- noexceptklausa mengatakan, "Fungsi ini seharusnya tidak kecuali ketika ... (suatu kondisi)." Ini berjalan pada deklarasi fungsi, mengambil parameter boolean, dan menyebabkan perubahan perilaku pada fungsi yang dideklarasikan.

  • noexceptEkspresi A mengatakan, "Kompiler, tolong katakan padaku apakah (beberapa ekspresi) ada kecuali." Itu sendiri merupakan ekspresi boolean. Ia tidak memiliki "efek samping" pada perilaku program - hanya meminta kompiler untuk menjawab pertanyaan ya / tidak. "Apakah ungkapan ini bukan pengecualian?"

Kami bisa sarang noexcept-expression di dalam noexcept-clause, tapi kami biasanya menganggap gaya buruk untuk melakukannya.

template<class T>
void incr(T t) noexcept(noexcept(++t));  // NOT SO HOT

Ini dianggap gaya yang lebih baik untuk merangkum noexceptekspresi-dalam tipe-sifat.

template<class T> inline constexpr bool is_nothrow_incrable_v =
    noexcept(++std::declval<T&>());  // BETTER, PART 1

template<class T>
void incr(T t) noexcept(is_nothrow_incrable_v<T>);  // BETTER, PART 2

C ++ 2a Working Draft memiliki " requires-clauses" dan " requires-expressions." Mereka melakukan hal yang berbeda.

  • A- requiresklausa mengatakan, "Fungsi ini harus berpartisipasi dalam resolusi kelebihan ketika ... (suatu kondisi)." Ini berjalan pada deklarasi fungsi, mengambil parameter boolean, dan menyebabkan perubahan perilaku pada fungsi yang dideklarasikan.

  • requiresEkspresi A mengatakan, "Kompiler, tolong katakan padaku apakah (beberapa ekspresi) terbentuk dengan baik." Itu sendiri merupakan ekspresi boolean. Ia tidak memiliki "efek samping" pada perilaku program - hanya meminta kompiler untuk menjawab pertanyaan ya / tidak. "Apakah ungkapan ini terbentuk dengan baik?"

Kami bisa sarang requires-expression di dalam requires-clause, tapi kami biasanya menganggap gaya buruk untuk melakukannya.

template<class T>
void incr(T t) requires (requires(T t) { ++t; });  // NOT SO HOT

Ini dianggap gaya yang lebih baik untuk merangkum requiresekspresi-dalam tipe-karakter ...

template<class T> inline constexpr bool is_incrable_v =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires is_incrable_v<T>;  // BETTER, PART 2

... atau dalam konsep (C ++ 2a Working Draft).

template<class T> concept Incrable =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires Incrable<T>;  // BETTER, PART 2
Quuxplusone
sumber
1
Saya tidak benar-benar membeli argumen ini. noexceptmemiliki masalah yang noexcept(f())bisa berarti baik menafsirkan f()sebagai boolean yang kami gunakan untuk mengatur spesifikasi atau memeriksa apakah atau tidak f()adalah noexcept. requirestidak memiliki ambiguitas ini karena ekspresi yang memeriksa validitasnya harus sudah diperkenalkan dengan {}s. Setelah itu, argumen pada dasarnya adalah "tata bahasa mengatakan demikian."
Barry
@ Barry: Lihat komentar ini . Tampaknya {}bersifat opsional.
Eric
1
@ Eric Ini {}bukan opsional, bukan itu yang ditampilkan komentar. Namun, itu adalah komentar yang bagus yang menunjukkan ambiguitas penguraian. Mungkin akan menerima komentar itu (dengan beberapa penjelasan) sebagai jawaban mandiri
Barry
1
requires is_nothrow_incrable_v<T>;seharusnyarequires is_incrable_v<T>;
Ruslan
16

Saya pikir halaman konsep cppreference menjelaskan hal ini. Saya bisa menjelaskan dengan "matematika" sehingga bisa dikatakan, mengapa ini harus seperti ini:

Jika Anda ingin mendefinisikan konsep, Anda melakukan ini:

template<typename T>
concept Addable = requires (T x) { x + x; }; // requires-expression

Jika Anda ingin mendeklarasikan fungsi yang menggunakan konsep itu, Anda melakukan ini:

template<typename T> requires Addable<T> // requires-clause, not requires-expression
T add(T a, T b) { return a + b; }

Sekarang jika Anda tidak ingin mendefinisikan konsep secara terpisah, saya kira yang harus Anda lakukan adalah beberapa substitusi. Ambil bagian ini requires (T x) { x + x; };dan ganti Addable<T>bagian itu, dan Anda akan mendapatkan:

template<typename T> requires requires (T x) { x + x; }
T add(T a, T b) { return a + b; }

itulah yang Anda tanyakan.

Fisikawan Kuantum
sumber
4
Saya tidak berpikir apa yang ditanyakan. Ini menjelaskan tata bahasa, kurang lebih.
Passer By
Tetapi mengapa Anda tidak dapat memiliki template<typename T> requires (T x) { x + x; }dan mensyaratkan bahwa persyaratan dapat berupa klausa dan ungkapan?
NathanOliver
2
@NathanOliver: Karena Anda memaksa kompiler untuk menginterpretasikan satu konstruk sebagai konstruk lainnya. Sebuah requiresklausul -as-kendala tidak harus menjadi requires-expression. Itu hanya satu kemungkinan penggunaannya.
Nicol Bolas
2
@TheQuantumPhysicist Apa yang saya maksud dengan komentar saya adalah jawaban ini hanya menjelaskan sintaks. Bukan alasan teknis sebenarnya yang harus kita lakukan requires requires. Mereka bisa menambahkan sesuatu ke tata bahasa untuk memungkinkan template<typename T> requires (T x) { x + x; }tetapi mereka tidak. Barry ingin tahu mengapa mereka tidak
NathanOliver
3
Jika kita benar-benar bermain ambiguitas find-the-grammar di sini, oke, saya akan gigit. godbolt.org/z/i6n8kM template<class T> void f(T) requires requires(T (x)) { (void)x; }; berarti sesuatu yang berbeda jika Anda menghapus salah satu dari requireses.
Quuxplusone
12

Saya menemukan komentar dari Andrew Sutton (salah satu penulis Konsep, yang mengimplementasikannya dalam gcc) sangat membantu dalam hal ini, jadi saya pikir saya akan mengutipnya di sini secara keseluruhan:

Belum lama ini membutuhkan-ekspresi (frase yang diperkenalkan oleh yang kedua membutuhkan) tidak diperbolehkan dalam ekspresi-kendala (frase yang diperkenalkan oleh yang pertama membutuhkan). Itu hanya bisa muncul dalam definisi konsep. Bahkan, inilah yang diusulkan di bagian makalah itu di mana klaim itu muncul.

Namun, pada 2016, ada proposal untuk melonggarkan pembatasan itu [Catatan Editor: P0266 ]. Perhatikan coretan paragraf 4 di bagian 4 makalah ini. Dan dengan demikian lahir membutuhkan membutuhkan.

Sejujurnya, saya tidak pernah benar-benar menerapkan pembatasan itu di GCC, jadi itu selalu mungkin. Saya pikir Walter mungkin telah menemukannya dan bermanfaat, mengarah ke kertas itu.

Agar siapa pun berpikir bahwa saya tidak sensitif terhadap penulisan memerlukan dua kali, saya memang meluangkan waktu mencoba untuk menentukan apakah itu dapat disederhanakan. Jawaban singkat: tidak.

Masalahnya adalah bahwa ada dua konstruksi gramatikal yang perlu diperkenalkan setelah daftar parameter templat: sangat umum ekspresi kendala (seperti P && Q) dan kadang-kadang persyaratan sintaksis (seperti requires (T a) { ... }). Itu disebut membutuhkan-ekspresi.

Yang pertama membutuhkan memperkenalkan kendala. Yang kedua membutuhkan pengenalan yang membutuhkan-ekspresi. Itulah cara tata bahasa menyusun. Saya tidak menganggapnya membingungkan sama sekali.

Saya mencoba, pada satu titik, untuk menciutkan ini ke satu kebutuhan. Sayangnya, itu mengarah pada beberapa masalah penguraian yang sangat sulit. Anda tidak dapat dengan mudah memberi tahu, misalnya jika suatu (keharusan setelah menunjukkan subekspresi bersarang atau daftar parameter. Saya tidak percaya bahwa ada disambiguasi sempurna dari sintaks tersebut (lihat alasan untuk sintaks inisialisasi seragam; masalah ini ada di sana juga).

Jadi Anda membuat pilihan: make mengharuskan memperkenalkan ekspresi (seperti sekarang) atau membuatnya memperkenalkan daftar persyaratan parameter.

Saya memilih pendekatan saat ini karena sebagian besar waktu (seperti dalam hampir 100% dari waktu), saya menginginkan sesuatu selain ekspresi yang membutuhkan. Dan dalam kasus yang sangat langka saya memang menginginkan ekspresi yang membutuhkan kendala ad hoc, saya benar-benar tidak keberatan menulis kata itu dua kali. Ini adalah indikator yang jelas bahwa saya belum mengembangkan abstraksi suara yang cukup untuk template. (Karena jika aku punya, itu akan punya nama.)

Saya bisa memilih untuk membuat perkenalan membutuhkan sebuah ekspresi. Itu sebenarnya lebih buruk, karena praktis semua kendala Anda akan mulai terlihat seperti ini:

template<typename T>
  requires { requires Eq<T>; }
void f(T a, T b);

Di sini, persyaratan ke-2 disebut persyaratan bersarang; ia mengevaluasi ekspresinya (kode lain dalam blok ekspresi-kebutuhan tidak dievaluasi). Saya pikir ini jauh lebih buruk daripada status quo. Sekarang, Anda bisa menulis membutuhkan dua kali di mana-mana.

Saya juga bisa menggunakan lebih banyak kata kunci. Ini masalah tersendiri --- dan bukan hanya menumpahkan sepeda. Mungkin ada cara untuk "mendistribusikan kembali" kata kunci untuk menghindari duplikasi, tapi saya belum memikirkan itu. Tapi itu tidak benar-benar mengubah esensi masalah.

Barry
sumber
-10

Karena Anda mengatakan bahwa benda A memiliki persyaratan B, dan persyaratan B memiliki persyaratan C.

Hal A membutuhkan B yang pada gilirannya membutuhkan C.

Klausa "membutuhkan" itu sendiri membutuhkan sesuatu.

Anda memiliki benda A (membutuhkan B (membutuhkan C)).

Ah. :)

Lightness Races di Orbit
sumber
4
Tetapi menurut jawaban yang lain, yang pertama dan yang kedua requiressecara konseptual bukanlah hal yang sama (satu adalah klausa, satu ungkapan). Bahkan, jika saya mengerti dengan benar, dua set ()dalam requires (requires (T x) { x + x; })memiliki makna yang sangat berbeda (bagian luar adalah opsional dan selalu berisi boolean constexpr; bagian dalam menjadi bagian wajib dari memperkenalkan ekspresi yang membutuhkan dan tidak memungkinkan ekspresi yang sebenarnya).
Max Langhof
2
@ MaxLanghof Apakah Anda mengatakan bahwa persyaratannya berbeda? : D
Lightness Races dalam Orbit